1 {-# LANGUAGE FlexibleInstances #-}
3 {-# LANGUAGE QuasiQuotes #-}
4 {-# LANGUAGE RecordWildCards #-}
5 {-# LANGUAGE TemplateHaskell #-}
6 {-# LANGUAGE TypeFamilies #-}
8 -- | Parse TSN XML for the DTD "Injuries_Detail_XML.dtd". Each
9 -- document contains a root element \<message\> that in turn
10 -- contains zero or more \<Listing\>s (note: capitalization). The
11 -- \<Listing\>s contain \<PlayerListing\>s which then contain the
14 module TSN.XML.InjuriesDetail (
18 injuries_detail_tests,
19 -- * WARNING: these are private but exported to silence warnings
20 InjuriesDetailConstructor(..),
21 InjuriesDetailListingConstructor(..),
22 InjuriesDetailListingPlayerListingConstructor(..) )
26 import Control.Monad ( forM_ )
27 import Data.Time ( UTCTime )
28 import Data.Tuple.Curry ( uncurryN )
29 import Database.Groundhog (
35 silentMigrationLogger )
36 import Database.Groundhog.Generic ( runDbConn )
37 import Database.Groundhog.Sqlite ( withSqliteConn )
38 import Database.Groundhog.TH (
41 import Test.Tasty ( TestTree, testGroup )
42 import Test.Tasty.HUnit ( (@?=), testCase )
43 import Text.XML.HXT.Core (
57 import TSN.Codegen ( tsn_codegen_config )
58 import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate )
59 import TSN.Picklers( xp_date, xp_time_stamp )
60 import TSN.XmlImport ( XmlImport(..), XmlImportFk(..) )
72 -- | The DTD to which this module corresponds. Used to invoke dbimport.
75 dtd = "Injuries_Detail_XML.dtd"
83 -- * InjuriesDetail/Message
86 -- | XML representation of the top-level \<message\> element. These
87 -- are not stored; the data type is used only for parsing.
91 xml_xml_file_id :: Int,
92 xml_heading :: String,
93 xml_category :: String,
95 xml_listings :: [InjuriesDetailListingXml],
96 xml_time_stamp :: UTCTime }
99 -- | Database representation of a 'Message'.
101 data InjuriesDetail =
103 db_xml_file_id :: Int,
105 db_time_stamp :: UTCTime }
108 instance ToDb Message where
109 -- | The database representation of a 'Message' is an
112 type Db Message = InjuriesDetail
114 instance FromXml Message where
115 -- | To convert a 'Message' into an 'InjuriesDetail', we simply drop
118 from_xml Message{..} =
120 db_xml_file_id = xml_xml_file_id,
121 db_sport = xml_sport,
122 db_time_stamp = xml_time_stamp }
125 -- | This allows us to insert the XML representation 'Message'
128 instance XmlImport Message
132 -- * InjuriesDetailListing/InjuriesDetailListingXml
134 -- | Database representation of a \<Listing\> element. It has a
135 -- foreign key pointing to its parent 'InjuriesDetail', and does not
136 -- contain the list of 'xml_player_listings' (which get their own
139 data InjuriesDetailListing =
140 InjuriesDetailListing {
141 db_injuries_detail_id :: DefaultKey InjuriesDetail,
142 db_team_id :: String,
143 db_full_name :: String }
146 -- | XML incarnation of a \<Listing\> element. We don't store these;
147 -- the data type is used only for parsing.
149 data InjuriesDetailListingXml =
150 InjuriesDetailListingXml {
151 xml_team_id :: String, -- ^ TeamIDs are (apparently) three
152 -- characters long and not necessarily
155 xml_full_name :: String, -- ^ Team full name
156 xml_player_listings :: [InjuriesDetailListingPlayerListingXml] }
159 instance ToDb InjuriesDetailListingXml where
160 -- | The database analogue of an 'InjuriesDetailListingXml' is a
161 -- 'InjuriesDetailListing'.
162 type Db InjuriesDetailListingXml = InjuriesDetailListing
165 instance Child InjuriesDetailListingXml where
166 -- | Each 'InjuriesDetailListingXml' is contained in an
168 type Parent InjuriesDetailListingXml = InjuriesDetail
171 instance FromXmlFk InjuriesDetailListingXml where
172 -- | Construct a 'InjuriesDetailListing' from a
173 -- 'InjuriesDetailListingXml' and a foreign key to a
176 from_xml_fk fk InjuriesDetailListingXml{..} =
177 InjuriesDetailListing {
178 db_injuries_detail_id = fk,
179 db_team_id = xml_team_id,
180 db_full_name = xml_full_name }
182 -- | This allows us to insert the XML representation
183 -- 'InjuriesDetailListingXml' directly.
185 instance XmlImportFk InjuriesDetailListingXml
188 -- * InjuriesDetailListingPlayerListing
190 -- | XML representation of a \<PlayerListing\>, the main type of
191 -- element contains in Injuries_Detail_XML messages.
193 data InjuriesDetailListingPlayerListingXml =
194 InjuriesDetailListingPlayerListingXml {
195 xml_player_team_id :: String, -- ^ TeamIDs are (apparently) three
196 -- characters long and not
197 -- necessarily numeric. Postgres
198 -- imposes no performance penalty
199 -- on a lengthless text field, so
200 -- we ignore the likely upper
201 -- bound of three characters.
202 -- We add the \"player\" to avoid conflict
203 -- with 'InjuriesDetailListingXml'.
204 xml_player_id :: Int,
208 xml_injury :: String,
209 xml_status :: String,
210 xml_fantasy :: Maybe String, -- ^ Nobody knows what this is.
217 -- | Database representation of a
218 -- 'InjuriesDetailListingPlayerListingXml'. We drop the team_id
219 -- because it's redundant.
221 data InjuriesDetailListingPlayerListing =
222 InjuriesDetailListingPlayerListing {
223 db_injuries_detail_listings_id :: DefaultKey InjuriesDetailListing,
230 db_fantasy :: Maybe String, -- ^ Nobody knows what this is.
235 instance ToDb InjuriesDetailListingPlayerListingXml where
236 -- | The DB analogue of a 'InjuriesDetailListingPlayerListingXml' is
237 -- 'InjuriesDetailListingPlayerListing'.
238 type Db InjuriesDetailListingPlayerListingXml =
239 InjuriesDetailListingPlayerListing
242 instance Child InjuriesDetailListingPlayerListingXml where
243 -- | Each 'InjuriesDetailListingPlayerListingXml' is contained in an
244 -- 'InjuriesDetailListing'.
246 type Parent InjuriesDetailListingPlayerListingXml = InjuriesDetailListing
249 instance FromXmlFk InjuriesDetailListingPlayerListingXml where
250 -- | To construct a 'InjuriesDetailListingPlayerListing' from a
251 -- 'InjuriesDetailListingPlayerListingXml' we need to supply a
252 -- foreign key to an 'InjuriesDetailListing'.
254 from_xml_fk fk InjuriesDetailListingPlayerListingXml{..} =
255 InjuriesDetailListingPlayerListing {
256 db_injuries_detail_listings_id = fk,
257 db_player_id = xml_player_id,
261 db_injury = xml_injury,
262 db_status = xml_status,
263 db_fantasy = xml_fantasy,
264 db_injured = xml_injured,
267 -- | This lets us insert the XML representation
268 -- 'InjuriesDetailListingPlayerListingXml' directly.
270 instance XmlImportFk InjuriesDetailListingPlayerListingXml
277 instance DbImport Message where
280 migrate (undefined :: InjuriesDetail)
281 migrate (undefined :: InjuriesDetailListing)
282 migrate (undefined :: InjuriesDetailListingPlayerListing)
284 -- | To import a 'Message', we import all of its
285 -- 'InjuriesDetailListingPlayerListingXml's, which we have to dig
286 -- out of its 'Listing's.
289 msg_id <- insert_xml msg
291 forM_ (xml_listings msg) $ \listing -> do
292 l_id <- insert_xml_fk msg_id listing
293 mapM_ (insert_xml_fk_ l_id) (xml_player_listings listing)
295 return ImportSucceeded
298 mkPersist tsn_codegen_config [groundhog|
299 - entity: InjuriesDetail
300 dbName: injuries_detail
302 - name: InjuriesDetail
304 - name: unique_injuries_detail
306 # Prevent multiple imports of the same message.
307 fields: [db_xml_file_id]
309 - entity: InjuriesDetailListing
310 dbName: injuries_detail_listings
312 - name: InjuriesDetailListing
314 - name: db_injuries_detail_id
318 - entity: InjuriesDetailListingPlayerListing
319 dbName: injuries_detail_listings_player_listings
321 - name: InjuriesDetailListingPlayerListing
323 - name: db_injuries_detail_listings_id
334 -- | Convert 'InjuriesDetailListingPlayerListingXml's to/from XML.
336 pickle_player_listing :: PU InjuriesDetailListingPlayerListingXml
337 pickle_player_listing =
338 xpElem "PlayerListing" $
339 xpWrap (from_tuple, to_tuple) $
340 xp10Tuple (xpElem "TeamID" xpText)
341 (xpElem "PlayerID" xpInt)
342 (xpElem "Date" xp_date)
343 (xpElem "Pos" xpText)
344 (xpElem "Name" xpText)
345 (xpElem "Injury" xpText)
346 (xpElem "Status" xpText)
347 (xpElem "Fantasy" $ xpOption xpText)
348 (xpElem "Injured" xpPrim)
349 (xpElem "Type" xpText)
351 from_tuple = uncurryN InjuriesDetailListingPlayerListingXml
352 to_tuple pl = (xml_player_team_id pl,
364 -- | Convert 'Listing's to/from XML.
366 pickle_listing :: PU InjuriesDetailListingXml
369 xpWrap (from_tuple, to_tuple) $
370 xpTriple (xpElem "TeamID" xpText)
371 (xpElem "FullName" xpText)
372 (xpList pickle_player_listing)
374 from_tuple = uncurryN InjuriesDetailListingXml
375 to_tuple l = (xml_team_id l,
377 xml_player_listings l)
380 -- | Convert 'Message's to/from XML.
382 pickle_message :: PU Message
385 xpWrap (from_tuple, to_tuple) $
386 xp6Tuple (xpElem "XML_File_ID" xpInt)
387 (xpElem "heading" xpText)
388 (xpElem "category" xpText)
389 (xpElem "sport" xpText)
390 (xpList pickle_listing)
391 (xpElem "time_stamp" xp_time_stamp)
393 from_tuple = uncurryN Message
394 to_tuple m = (xml_xml_file_id m,
406 -- | A list of all tests for this module.
408 injuries_detail_tests :: TestTree
409 injuries_detail_tests =
411 "InjuriesDetail tests"
412 [ test_on_delete_cascade,
413 test_pickle_of_unpickle_is_identity,
414 test_unpickle_succeeds ]
417 -- | If we unpickle something and then pickle it, we should wind up
418 -- with the same thing we started with. WARNING: success of this
419 -- test does not mean that unpickling succeeded.
421 test_pickle_of_unpickle_is_identity :: TestTree
422 test_pickle_of_unpickle_is_identity = testGroup "pickle-unpickle tests"
423 [ check "pickle composed with unpickle is the identity"
424 "test/xml/Injuries_Detail_XML.xml",
426 check "pickle composed with unpickle is the identity (non-int team_id)"
427 "test/xml/Injuries_Detail_XML-noninteger-team-id.xml" ]
429 check desc path = testCase desc $ do
430 (expected, actual) <- pickle_unpickle pickle_message path
434 -- | Make sure we can actually unpickle these things.
436 test_unpickle_succeeds :: TestTree
437 test_unpickle_succeeds = testGroup "unpickle tests"
438 [ check "unpickling succeeds"
439 "test/xml/Injuries_Detail_XML.xml",
441 check "unpickling succeeds (non-int team_id)"
442 "test/xml/Injuries_Detail_XML-noninteger-team-id.xml" ]
444 check desc path = testCase desc $ do
445 actual <- unpickleable path pickle_message
450 -- | Make sure everything gets deleted when we delete the top-level
453 test_on_delete_cascade :: TestTree
454 test_on_delete_cascade = testGroup "cascading delete tests"
455 [ check "delete of injuries_detail deletes its children"
456 "test/xml/Injuries_Detail_XML.xml",
458 check "delete of injuries_detail deletes its children (non-int team_id)"
459 "test/xml/Injuries_Detail_XML-noninteger-team-id.xml" ]
461 check desc path = testCase desc $ do
462 inj <- unsafe_unpickle path pickle_message
463 let a = undefined :: InjuriesDetail
464 let b = undefined :: InjuriesDetailListing
465 let c = undefined :: InjuriesDetailListingPlayerListing
466 actual <- withSqliteConn ":memory:" $ runDbConn $ do
467 runMigration silentMigrationLogger $ do
473 count_a <- countAll a
474 count_b <- countAll b
475 count_c <- countAll c
476 return $ count_a + count_b + count_c