-- * Tests
odds_tests,
-- * WARNING: these are private but exported to silence warnings
- Odds_OddsGameConstructor(..),
OddsCasinoConstructor(..),
OddsConstructor(..),
OddsGame_OddsGameTeamConstructor(..),
(=.),
(==.),
insert_,
- insertByAll,
migrate,
update )
import Database.Groundhog.Core ( DefaultKey )
import TSN.Codegen (
tsn_codegen_config )
import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate )
-import TSN.Picklers ( xp_date, xp_time )
-import TSN.XmlImport ( XmlImport(..) )
-import Xml ( FromXml(..), ToDb(..), pickle_unpickle, unpickleable )
+import TSN.Picklers ( xp_date, xp_time, xp_time_stamp )
+import TSN.XmlImport ( XmlImport(..), XmlImportFk(..) )
+import Xml (
+ FromXml(..),
+ FromXmlFk(..),
+ ToDb(..),
+ pickle_unpickle,
+ unpickleable )
+--
+-- DB/XML data types
+--
+
+-- * OddsGameCasino/OddsGameCasinoXml
+
+
+-- | The casinos should have their own table, but the lines don't
+-- belong in that table (there should be another table joining the
+-- casinos and the thing the lines are for together.)
+--
+-- We drop the 'Game' prefix because the Casinos really aren't
+-- children of the games; the XML just makes it seem that way.
+--
+data OddsCasino =
+ OddsCasino {
+ casino_client_id :: Int,
+ casino_name :: String }
+ deriving (Eq, Show)
+
-- | The home/away lines are 'Double's, but the over/under lines are
-- textual. If we want to use one data type for both, we have to go
home_away_line = join . (fmap readMaybe) . xml_casino_line
--- | The casinos should have their own table, but the lines don't
--- belong in that table (there should be another table joining the
--- casinos and the thing the lines are for together.)
---
--- We drop the 'Game' prefix because the Casinos really aren't
--- children of the games; the XML just makes it seem that way.
---
-data OddsCasino =
- OddsCasino {
- casino_client_id :: Int,
- casino_name :: String }
- deriving (Eq, Show)
-
instance ToDb OddsGameCasinoXml where
-- | The database representation of an 'OddsGameCasinoXml' is an
--
type Db OddsGameCasinoXml = OddsCasino
+
instance FromXml OddsGameCasinoXml where
-- | We convert from XML to the database by dropping the line field.
from_xml OddsGameCasinoXml{..} =
casino_client_id = xml_casino_client_id,
casino_name = xml_casino_name }
+
-- | This allows us to call 'insert_xml' on an 'OddsGameCasinoXml'
-- without first converting it to the database representation.
instance XmlImport OddsGameCasinoXml
+-- * OddsGameTeam
+
+
-- | The database representation of teams as they appear in odds
-- games.
--
deriving (Eq, Show)
+-- * OddsGameHomeTeam/OddsGameHomeTeamXml
+
-- | The XML representation of a \<HomeTeam\>, as found in \<Game\>s.
--
data OddsGameHomeTeamXml =
instance XmlImport OddsGameHomeTeamXml where
+-- * OddsGameAwayTeam/OddsGameAwayTeamXml
+
-- | The XML representation of a \<AwayTeam\>, as found in \<Game\>s.
--
data OddsGameAwayTeamXml =
instance XmlImport OddsGameAwayTeamXml where
+-- * OddsGame_OddsGameTeam
+
-- | Database mapping between games and their home/away teams.
data OddsGame_OddsGameTeam =
OddsGame_OddsGameTeam {
ogogt_home_team_id :: DefaultKey OddsGameTeam }
+-- * OddsGameOverUnderXml
+
-- | XML representation of the over/under. A wrapper around a bunch of
-- casino elements.
--
deriving (Eq, Show)
+-- * OddsGameLine
+
-- | This database representation of the casino lines can't be
-- constructed from the one in the XML. The casinos within
-- Game>HomeTeam, Game>AwayTeam, and Game>Over_Under are all more or
ogl_home_line :: Maybe Double }
+-- * OddsGame/OddsGameXml
+
-- | Database representation of a game. We retain the rotation number
-- of the home/away teams, since those are specific to the game and
-- not the teams.
--
data OddsGame =
OddsGame {
+ db_odds_id :: DefaultKey Odds,
db_game_id :: Int,
db_game_time :: UTCTime, -- ^ Contains both the date and time.
db_game_away_team_rotation_number :: Int,
db_game_home_team_rotation_number :: Int }
- deriving (Eq, Show)
+
-- | XML representation of a game.
--
--
type Db OddsGameXml = OddsGame
-instance FromXml OddsGameXml where
+instance FromXmlFk OddsGameXml where
+ type Parent OddsGameXml = Odds
+
-- | To convert from the XML representation to the database one, we
-- drop the home/away teams and the casino lines, but retain the
-- home/away rotation numbers.
--
- from_xml OddsGameXml{..} =
+ from_xml_fk fk OddsGameXml{..} =
OddsGame {
+ db_odds_id = fk,
db_game_id = xml_game_id,
db_game_time = UTCTime
db_game_home_team_rotation_number =
(xml_home_rotation_number xml_game_home_team) }
--- | This lets us call 'insert_xml' directly on an 'OddsGameXml'
+-- | This lets us call 'insert_xml_fk' directly on an 'OddsGameXml'
-- without converting it to the database representation explicitly.
--
-instance XmlImport OddsGameXml
+instance XmlImportFk OddsGameXml
--- | Database and representation of the top-level Odds object (a
--- 'Message').
-data Odds =
- Odds {
- db_sport :: String,
- db_title :: String,
- db_line_time :: String -- ^ We don't parse these as a 'UTCTime'
- -- because their timezones are ambiguous
- -- (and the date is less than useful when
- -- it might be off by an hour).
- }
-
-
--- | Map 'Odds' to their children 'OddsGame's.
---
-data Odds_OddsGame = Odds_OddsGame
- (DefaultKey Odds)
- (DefaultKey OddsGame)
-
+-- * OddsGameWithNotes
-- | This is our best guess at what occurs in the Odds_XML
-- documents. It looks like each consecutive set of games can
game :: OddsGameXml }
deriving (Eq, Show)
+
+-- * Odds/Message
+
+-- | Database and representation of the top-level Odds object (a
+-- 'Message').
+data Odds =
+ Odds {
+ db_xml_file_id :: Int,
+ db_sport :: String,
+ db_title :: String,
+ db_line_time :: String, -- ^ We don't parse these as a 'UTCTime'
+ -- because their timezones are ambiguous
+ -- (and the date is less than useful when
+ -- it might be off by an hour).
+ db_time_stamp :: UTCTime }
+
+
-- | The XML representation of 'Odds'.
data Message =
Message {
xml_title :: String,
xml_line_time :: String,
xml_games_with_notes :: [OddsGameWithNotes],
- xml_time_stamp :: String }
+ xml_time_stamp :: UTCTime }
deriving (Eq, Show)
-- | Pseudo-field that lets us get the 'OddsGame's out of
--
from_xml Message{..} =
Odds {
+ db_xml_file_id = xml_xml_file_id,
db_sport = xml_sport,
db_title = xml_title,
- db_line_time = xml_line_time }
+ db_line_time = xml_line_time,
+ db_time_stamp = xml_time_stamp }
-- | This lets us call 'insert_xml' on a Message directly, without
-- having to convert it to its database representation explicitly.
instance XmlImport Message
+--
+-- Database code
+--
-- Groundhog database schema. This must come before the DbImport
--- instance definition.
+-- instance definition. Don't know why.
mkPersist tsn_codegen_config [groundhog|
- entity: Odds
+ constructors:
+ - name: Odds
+ uniques:
+ - name: unique_odds
+ type: constraint
+ # Prevent multiple imports of the same message.
+ fields: [db_xml_file_id]
- entity: OddsCasino
dbName: odds_casinos
dbName: odds_games
constructors:
- name: OddsGame
- uniques:
- - name: unique_odds_game
- type: constraint
- fields: [db_game_id]
+ fields:
+ - name: db_odds_id
+ reference:
+ onDelete: cascade
- entity: OddsGameLine
dbName: odds_games_lines
-
-- entity: Odds_OddsGame
- dbName: odds__odds_games
constructors:
- - name: Odds_OddsGame
+ - name: OddsGameLine
fields:
- - name: odds_OddsGame0 # Default created by mkNormalFieldName
- dbName: odds_id
- reference:
+ - name: ogl_odds_games_id
+ references:
onDelete: cascade
- - name: odds_OddsGame1 # Default created by mkNormalFieldName
- dbName: odds_games_id
- reference:
+ - name: ogl_odds_casinos_id
+ references:
onDelete: cascade
- entity: OddsGame_OddsGameTeam
migrate (undefined :: OddsCasino)
migrate (undefined :: OddsGameTeam)
migrate (undefined :: OddsGame)
- migrate (undefined :: Odds_OddsGame)
migrate (undefined :: OddsGame_OddsGameTeam)
migrate (undefined :: OddsGameLine)
-- Insert the root "odds" element and acquire its primary key (id).
odds_id <- insert_xml m
- -- Next, we insert the home and away teams. We do this before
- -- inserting the game itself because the game has two foreign keys
- -- pointing to odds_games_teams.
forM_ (xml_games m) $ \g -> do
- game_id <- insert_xml_or_select g
- -- Insert a record into odds__odds_game mapping this game
- -- to its parent in the odds table.
- insert_ (Odds_OddsGame odds_id game_id)
-
+ -- Next, we insert the home and away teams. We do this before
+ -- inserting the game itself because the game has two foreign keys
+ -- pointing to odds_games_teams.
-- Next to insert the home and away teams.
away_team_id <- insert_xml_or_select (xml_game_away_team g)
home_team_id <- insert_xml_or_select (xml_game_home_team g)
+ -- Now insert the game, keyed to the "odds",
+ game_id <- insert_xml_fk odds_id g
+
-- Insert a record into odds_games__odds_games_teams mapping the
-- home/away teams to this game. Use the full record syntax
-- because the types would let us mix up the home/away teams.
ogl_away_line = Nothing,
ogl_home_line = Nothing }
- insertByAll ogl
+ insert_ ogl
-- ...but then when we insert the home/away team lines, we
-- prefer to update the existing entry rather than overwrite it
-- or add a new record.
- forM_ (xml_away_casinos $ xml_game_away_team g) $ \c ->do
+ forM_ (xml_away_casinos $ xml_game_away_team g) $ \c -> do
-- insert, or more likely retrieve the existing, casino
a_casino_id <- insert_xml_or_select c
return ImportSucceeded
+
+--
+-- Pickling
+--
+
-- | Pickler for an 'OddsGame' optionally preceded by some notes.
--
pickle_game_with_notes :: PU OddsGameWithNotes
(xpElem "Title" xpText)
(xpElem "Line_Time" xpText)
(xpList pickle_game_with_notes)
- (xpElem "time_stamp" xpText)
+ (xpElem "time_stamp" xp_time_stamp)
where
from_tuple = uncurryN Message
to_tuple m = (xml_xml_file_id m,