+{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE QuasiQuotes #-}
import Data.String.Utils ( split )
import Data.Time ( UTCTime(..) )
import Data.Tuple.Curry ( uncurryN )
+import qualified Data.Vector.HFixed as H ( HVector, convert )
import Database.Groundhog (
countAll,
deleteAll,
import Database.Groundhog.TH (
groundhog,
mkPersist )
+import qualified GHC.Generics as GHC ( Generic )
import Test.Tasty ( TestTree, testGroup )
import Test.Tasty.HUnit ( (@?=), testCase )
import Text.XML.HXT.Core (
-- Local imports
import TSN.Codegen ( tsn_codegen_config )
+import TSN.Database ( insert_or_select )
import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate )
import TSN.Picklers (
xp_date,
xp_date_padded,
xp_datetime,
- xp_time,
+ xp_tba_time,
xp_time_dots,
xp_time_stamp )
-import TSN.Team ( Team(..) )
+import TSN.Team (
+ FromXmlFkTeams(..),
+ HTeam(..),
+ Team(..),
+ VTeam(..) )
import TSN.XmlImport (
XmlImport(..),
XmlImportFkTeams(..) )
import Xml (
Child(..),
FromXml(..),
- FromXmlFkTeams(..),
ToDb(..),
pickle_unpickle,
unpickleable,
xml_sport :: String,
xml_gamelist :: JFileGameListXml,
xml_time_stamp :: UTCTime }
- deriving (Eq, Show)
+ deriving (Eq, GHC.Generic, Show)
+
+
+-- | For 'H.convert'.
+--
+instance H.HVector Message
instance ToDb Message where
instance XmlImport Message
--- * JFileGameAwayTeamXml / JFileGameHomeTeamXml
-
--- | The XML representation of a JFile away team. Its corresponding
--- database representation (along with that of the home team) is a
--- TSN.Team, but their XML representations are different.
-data JFileGameAwayTeamXml =
- JFileGameAwayTeamXml {
- away_team_id :: String,
- away_team_abbreviation :: Maybe String,
- away_team_name :: Maybe String }
- deriving (Eq, Show)
-
-instance ToDb JFileGameAwayTeamXml where
- -- | The database analogue of an 'JFileGameAwayTeamXml' is
- -- a 'Team'.
- --
- type Db JFileGameAwayTeamXml = Team
-
-instance FromXml JFileGameAwayTeamXml where
- -- | To convert a 'JFileGameAwayTeamXml' to a 'Team', we do just
- -- about nothing.
- --
- from_xml JFileGameAwayTeamXml{..} =
- Team {
- team_id = away_team_id,
- abbreviation = away_team_abbreviation,
- name = away_team_name }
-
--- | Allow us to import JFileGameAwayTeamXml directly.
-instance XmlImport JFileGameAwayTeamXml
-
-
--- | The XML representation of a JFile home team. Its corresponding
--- database representation (along with that of the away team) is a
--- TSN.Team, but their XML representations are different.
-data JFileGameHomeTeamXml =
- JFileGameHomeTeamXml {
- home_team_id :: String,
- home_team_abbreviation :: Maybe String,
- home_team_name :: Maybe String }
- deriving (Eq, Show)
-
-instance ToDb JFileGameHomeTeamXml where
- -- | The database analogue of an 'JFileGameHomeTeamXml' is
- -- a 'Team'.
- --
- type Db JFileGameHomeTeamXml = Team
-
-instance FromXml JFileGameHomeTeamXml where
- -- | To convert a 'JFileGameHomeTeamXml' to a 'Team', we do just
- -- about nothing.
- --
- from_xml JFileGameHomeTeamXml{..} =
- Team {
- team_id = home_team_id,
- abbreviation = home_team_abbreviation,
- name = home_team_name }
-
--- | Allow us to import JFileGameHomeTeamXml directly.
-instance XmlImport JFileGameHomeTeamXml
-
-- * JFileGame/JFileGameXml
deriving (Eq, Show)
+
-- | Database representation of a \<game\> contained within a
-- \<message\>, and, implicitly, a \<gamelist\>.
--
db_schedule_id :: Int,
db_odds_info :: JFileGameOddsInfo,
db_season_type :: Maybe String,
- db_game_time :: UTCTime,
+ db_game_time :: Maybe UTCTime,
db_vleague :: Maybe String,
db_hleague :: Maybe String,
db_vscore :: Int,
xml_odds_info :: JFileGameOddsInfo,
xml_season_type :: Maybe String,
xml_game_date :: UTCTime,
- xml_game_time :: UTCTime,
- xml_vteam :: JFileGameAwayTeamXml,
+ xml_game_time :: Maybe UTCTime,
+ xml_vteam :: VTeam,
xml_vleague :: Maybe String,
- xml_hteam :: JFileGameHomeTeamXml,
+ xml_hteam :: HTeam,
xml_hleague :: Maybe String,
xml_vscore :: Int,
xml_hscore :: Int,
xml_time_remaining :: Maybe String,
xml_game_status :: JFileGameStatus }
- deriving (Eq, Show)
+ deriving (Eq, GHC.Generic, Show)
+
+
+-- | For 'H.convert'.
+--
+instance H.HVector JFileGameXml
-- * JFileGameListXml
db_time_remaining = xml_time_remaining,
db_game_status = xml_game_status }
where
- -- | Make the database \"game time\" from the XML
- -- date/time. Simply take the day part from one and the time
- -- from the other.
- --
- make_game_time d t = UTCTime (utctDay d) (utctDayTime t)
+ -- | Construct the database game time from the XML \<Game_Date\>
+ -- and \<Game_Time\> elements. The \<Game_Time\> elements
+ -- sometimes have a value of \"TBA\"; in that case, we don't
+ -- want to pretend that we know the time by setting it to
+ -- e.g. midnight, so instead we make the entire date/time
+ -- Nothing.
+ make_game_time :: UTCTime -> Maybe UTCTime -> Maybe UTCTime
+ make_game_time _ Nothing = Nothing
+ make_game_time d (Just t) = Just $ UTCTime (utctDay d) (utctDayTime t)
-- | This allows us to insert the XML representation
-- Now loop through the message's games
forM_ (xml_games $ xml_gamelist m) $ \game -> do
-- First we insert the home and away teams.
- away_team_id <- insert_xml_or_select (xml_vteam game)
- home_team_id <- insert_xml_or_select (xml_hteam game)
+ away_team_id <- insert_or_select (vteam $ xml_vteam game)
+ home_team_id <- insert_or_select (hteam $ xml_hteam game)
- -- First insert the game, keyed to the "jfile",
+ -- Now insert the game keyed to the "jfile" and its teams.
insert_xml_fk_teams_ msg_id away_team_id home_team_id game
- name: db_status
dbName: status
-# Many of the JFileGameOddsInfo fields are redundant and have
-# been left out.
+ # Many of the JFileGameOddsInfo fields are redundant and have
+ # been left out.
- embedded: JFileGameOddsInfo
fields:
- name: db_list_date
pickle_message :: PU Message
pickle_message =
xpElem "message" $
- xpWrap (from_tuple, to_tuple) $
+ xpWrap (from_tuple, H.convert) $
xp6Tuple (xpElem "XML_File_ID" xpInt)
(xpElem "heading" xpText)
(xpElem "category" xpText)
(xpElem "time_stamp" xp_time_stamp)
where
from_tuple = uncurryN Message
- to_tuple m = (xml_xml_file_id m,
- xml_heading m,
- xml_category m,
- xml_sport m,
- xml_gamelist m,
- xml_time_stamp m)
+
pickle_gamelist :: PU JFileGameListXml
pickle_gamelist =
pickle_game :: PU JFileGameXml
pickle_game =
xpElem "game" $
- xpWrap (from_tuple, to_tuple) $
+ xpWrap (from_tuple, H.convert) $
xp14Tuple (xpElem "game_id" xpInt)
(xpElem "schedule_id" xpInt)
pickle_odds_info
(xpElem "seasontype" (xpOption xpText))
(xpElem "Game_Date" xp_date_padded)
- (xpElem "Game_Time" xp_time)
+ (xpElem "Game_Time" xp_tba_time)
pickle_away_team
(xpOption $ xpElem "vleague" xpText)
pickle_home_team
pickle_status
where
from_tuple = uncurryN JFileGameXml
- to_tuple m = (xml_game_id m,
- xml_schedule_id m,
- xml_odds_info m,
- xml_season_type m,
- xml_game_date m,
- xml_game_time m,
- xml_vteam m,
- xml_vleague m,
- xml_hteam m,
- xml_hleague m,
- xml_vscore m,
- xml_hscore m,
- xml_time_remaining m,
- xml_game_status m)
+
pickle_odds_info :: PU JFileGameOddsInfo
pickle_odds_info =
xpElem "Odds_Info" $
- xpWrap (from_tuple, to_tuple) $
+ xpWrap (from_tuple, to_tuple') $
xp19Tuple (xpElem "ListDate" (xpOption xp_date))
(xpElem "HomeTeamID" (xpOption xpText))
(xpElem "AwayTeamID" (xpOption xpText))
where
notes = intercalate "\n" [n1,n2,n3,n4,n5]
- to_tuple o = (db_list_date o,
- db_info_home_team_id o,
- db_info_away_team_id o,
- db_home_abbr o,
- db_away_abbr o,
- db_home_team_name o,
- db_away_team_name o,
- db_home_starter o,
- db_away_starter o,
- db_game_date o,
- db_home_game_key o,
- db_away_game_key o,
- db_current_timestamp o,
- db_live o,
- n1,n2,n3,n4,n5)
+ to_tuple' o = (db_list_date o,
+ db_info_home_team_id o,
+ db_info_away_team_id o,
+ db_home_abbr o,
+ db_away_abbr o,
+ db_home_team_name o,
+ db_away_team_name o,
+ db_home_starter o,
+ db_away_starter o,
+ db_game_date o,
+ db_home_game_key o,
+ db_away_game_key o,
+ db_current_timestamp o,
+ db_live o,
+ n1,n2,n3,n4,n5)
where
note_lines = split "\n" (db_notes o)
n1 = case note_lines of
(_:_:_:_:notes5:_) -> notes5
_ -> ""
-pickle_home_team :: PU JFileGameHomeTeamXml
+-- | (Un)pickle a home team to/from the dual XML/DB representation
+-- 'Team'.
+--
+pickle_home_team :: PU HTeam
pickle_home_team =
xpElem "hteam" $
- xpWrap (from_tuple, to_tuple) $
+ xpWrap (from_tuple, to_tuple') $
xpTriple (xpAttr "teamid" xpText) -- Yeah, they're text.
(xpAttr "abbr" (xpOption xpText)) -- Some are blank
(xpOption xpText) -- Yup, some are nameless
where
- from_tuple = uncurryN JFileGameHomeTeamXml
- to_tuple t = (home_team_id t,
- home_team_abbreviation t,
- home_team_name t)
+ from_tuple = HTeam . (uncurryN Team)
+ to_tuple' (HTeam t) = H.convert t
-pickle_away_team :: PU JFileGameAwayTeamXml
+-- | (Un)pickle an away team to/from the dual XML/DB representation
+-- 'Team'.
+--
+pickle_away_team :: PU VTeam
pickle_away_team =
xpElem "vteam" $
- xpWrap (from_tuple, to_tuple) $
+ xpWrap (from_tuple, to_tuple') $
xpTriple (xpAttr "teamid" xpText) -- Yeah, they're text.
(xpAttr "abbr" (xpOption xpText)) -- Some are blank
(xpOption xpText) -- Yup, some are nameless
where
- from_tuple = uncurryN JFileGameAwayTeamXml
- to_tuple t = (away_team_id t,
- away_team_abbreviation t,
- away_team_name t)
+ from_tuple = VTeam . (uncurryN Team)
+ to_tuple' (VTeam t) = H.convert t
pickle_status :: PU JFileGameStatus
pickle_status =
xpElem "status" $
- xpWrap (from_tuple, to_tuple) $
+ xpWrap (from_tuple, to_tuple') $
xpPair (xpAttr "numeral" xpInt)
(xpOption xpText)
where
from_tuple = uncurry JFileGameStatus
- to_tuple s = (db_status_numeral s,
- db_status s)
+ -- Avoid unused field warnings.
+ to_tuple' JFileGameStatus{..} = (db_status_numeral, db_status)
--
[ check "pickle composed with unpickle is the identity"
"test/xml/jfilexml.xml",
check "pickle composed with unpickle is the identity (missing fields)"
- "test/xml/jfilexml-missing-fields.xml" ]
+ "test/xml/jfilexml-missing-fields.xml",
+
+ check "pickle composed with unpickle is the identity (TBA game time)"
+ "test/xml/jfilexml-tba-game-time.xml"]
where
check desc path = testCase desc $ do
(expected, actual) <- pickle_unpickle pickle_message path
test_unpickle_succeeds :: TestTree
test_unpickle_succeeds = testGroup "unpickle tests"
[ check "unpickling succeeds" "test/xml/jfilexml.xml",
+
check "unpickling succeeds (missing fields)"
- "test/xml/jfilexml-missing-fields.xml" ]
+ "test/xml/jfilexml-missing-fields.xml",
+
+ check "unpickling succeeds (TBA game time)"
+ "test/xml/jfilexml-tba-game-time.xml" ]
where
check desc path = testCase desc $ do
actual <- unpickleable path pickle_message
test_on_delete_cascade = testGroup "cascading delete tests"
[ check "deleting auto_racing_results deletes its children"
"test/xml/jfilexml.xml"
- 20,
+ 20, -- teams
+
check "deleting auto_racing_results deletes its children (missing fields)"
"test/xml/jfilexml-missing-fields.xml"
- 44 ]
+ 44,
+
+ check "deleting auto_racing_results deletes its children (TBA game time)"
+ "test/xml/jfilexml-tba-game-time.xml"
+ 8 ]
where
check desc path expected = testCase desc $ do
results <- unsafe_unpickle path pickle_message