Db.hs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. {-# OPTIONS -funbox-strict-fields #-}
  2. {-# LANGUAGE OverloadedStrings #-}
  3. module TFB.Db (
  4. Pool
  5. , mkPool
  6. , Config(..)
  7. , queryWorldById
  8. , queryWorldByIds
  9. , updateWorlds
  10. , queryFortunes
  11. , Error
  12. ) where
  13. import qualified TFB.Types as Types
  14. import qualified Data.Either as Either
  15. import qualified System.IO.Error as Error
  16. import Control.Monad (replicateM, forM)
  17. import qualified Data.Pool as Pool
  18. import Data.ByteString (ByteString)
  19. import qualified Data.ByteString.Char8 as BSC
  20. import qualified Database.PostgreSQL.Driver as PG
  21. import qualified Database.PostgreSQL.Protocol.Types as PGT
  22. import qualified Database.PostgreSQL.Protocol.DataRows as PGD
  23. import qualified Database.PostgreSQL.Protocol.Store.Decode as PGSD
  24. import qualified Database.PostgreSQL.Protocol.Store.Encode as PGSE
  25. import qualified Database.PostgreSQL.Protocol.Codecs.Decoders as PGCD
  26. import qualified Database.PostgreSQL.Protocol.Codecs.Encoders as PGCE
  27. import qualified Database.PostgreSQL.Protocol.Codecs.PgTypes as PGCT
  28. import qualified Data.Vector as V
  29. import Data.Text (Text)
  30. import qualified Data.Text.Encoding as TE
  31. -------------------------------------------------------------------------------
  32. -- * Database
  33. data Config
  34. = Config
  35. { configHost :: String
  36. , configName :: ByteString
  37. , configUser :: ByteString
  38. , configPass :: ByteString
  39. , configStripes :: Int
  40. , configPoolSize :: Int
  41. }
  42. instance Show Config where
  43. show c
  44. = "Config {"
  45. <> " configHost = " <> configHost c
  46. <> ", configName = " <> BSC.unpack (configName c)
  47. <> ", configUser = " <> BSC.unpack (configUser c)
  48. <> ", configPass = REDACTED"
  49. <> ", configStripes = " <> show (configStripes c)
  50. <> ", configPoolSize = " <> show (configPoolSize c)
  51. <> " }"
  52. type Connection = PG.Connection
  53. type Pool = Pool.Pool Connection
  54. data Error
  55. = DbError PG.Error
  56. | DbErrors [PG.Error]
  57. | NotFound
  58. deriving Show
  59. connect :: Config -> IO Connection
  60. connect c = simplifyError =<< PG.connect pgc
  61. where
  62. simplifyError = Either.either (Error.ioError . Error.userError . show) pure
  63. pgc = PG.defaultConnectionSettings
  64. { PG.settingsHost = BSC.pack $ configHost c
  65. , PG.settingsDatabase = configName c
  66. , PG.settingsUser = configUser c
  67. , PG.settingsPassword = configPass c
  68. }
  69. close :: Connection -> IO ()
  70. close = PG.close
  71. mkPool :: Config -> IO Pool
  72. mkPool c = Pool.createPool (connect c) close (configStripes c) 0.5 (configPoolSize c)
  73. runQuery :: Connection -> PGSD.Decode a -> PG.Query -> IO (Either PG.Error (V.Vector a))
  74. runQuery conn dec q = do
  75. PG.sendBatchAndSync conn [q]
  76. eRows <- PG.readNextData conn
  77. _ <- PG.waitReadyForQuery conn
  78. return $ fmap (PGD.decodeManyRows dec) eRows
  79. decodeInt :: PGSD.Decode Int
  80. decodeInt = fromIntegral <$> PGCD.getNonNullable PGCD.int4
  81. decodeText :: PGSD.Decode Text
  82. decodeText = TE.decodeUtf8 <$> PGCD.getNonNullable PGCD.bytea
  83. encodeInt :: Integral a => a -> (PGCT.Oids, PGSE.Encode)
  84. encodeInt qId = (PGCT.int2, PGCE.int2 $ fromIntegral qId)
  85. mkQuery :: ByteString -> [(PGCT.Oids, PGSE.Encode)] -> PG.Query
  86. mkQuery q es = PG.Query q ps PGT.Binary PGT.Binary PG.NeverCache
  87. where
  88. mkP (oid, e) = (PGCT.oidType oid, Just e)
  89. ps = fmap mkP es
  90. -------------------------------------------------------------------------------
  91. -- * World
  92. decodeWorld :: PGSD.Decode Types.World
  93. decodeWorld = PGCD.dataRowHeader *> decoder
  94. where
  95. decoder = Types.World
  96. <$> decodeInt
  97. <*> decodeInt
  98. queryWorldById :: Pool -> Types.QId -> IO (Either Error Types.World)
  99. queryWorldById dbPool wId = Pool.withResource dbPool $ \conn -> do
  100. fmap go $ runQuery conn decodeWorld q
  101. where
  102. s = "SELECT * FROM World WHERE id = $1"
  103. q = mkQuery s [encodeInt wId]
  104. mkW [] = Left NotFound
  105. mkW ws = pure . head $ ws
  106. go = Either.either (Left . DbError) (mkW . V.toList)
  107. queryWorldByIds :: Pool -> [Types.QId] -> IO (Either Error [Types.World])
  108. queryWorldByIds _ [] = pure . pure $ mempty
  109. queryWorldByIds dbPool wIds = Pool.withResource dbPool $ \conn -> do
  110. let s = "SELECT * FROM World WHERE id = $1"
  111. let mkQ wId = mkQuery s [encodeInt wId]
  112. let qs = fmap mkQ wIds
  113. PG.sendBatchAndSync conn qs
  114. eRowsMany <- replicateM (length qs) $ PG.readNextData conn
  115. _ <- PG.waitReadyForQuery conn
  116. let (errs, rowsList) = Either.partitionEithers eRowsMany
  117. return $ case errs of
  118. [] -> pure . mconcat $ fmap (V.toList . PGD.decodeManyRows decodeWorld) rowsList
  119. _ -> Left . DbErrors $ errs
  120. updateWorlds :: Pool -> [(Types.World, Int)] -> IO (Either Error [Types.World])
  121. updateWorlds _ [] = pure . pure $ mempty
  122. updateWorlds dbPool wsUpdates = Pool.withResource dbPool $ \conn -> do
  123. let ws = fmap updateW wsUpdates
  124. let qs = fmap mkQ ws
  125. eRowsMany <- forM qs $ \q -> do
  126. PG.sendBatchAndSync conn [q]
  127. eRows <- PG.readNextData conn
  128. _ <- PG.waitReadyForQuery conn
  129. return eRows
  130. let (errs, _) = Either.partitionEithers eRowsMany
  131. return $ case errs of
  132. [] -> pure ws
  133. _ -> Left . DbErrors $ errs
  134. where
  135. s = "UPDATE World SET randomNumber = $1 WHERE id = $2"
  136. updateW (w,wNum) = w { Types.wRandomNumber = wNum }
  137. mkQ w = mkQuery s [encodeInt . Types.wRandomNumber $ w, encodeInt . Types.wId $ w]
  138. -------------------------------------------------------------------------------
  139. -- * Fortunes
  140. decodeFortune :: PGSD.Decode Types.Fortune
  141. decodeFortune = PGCD.dataRowHeader *> decoder
  142. where
  143. decoder = Types.Fortune
  144. <$> decodeInt
  145. <*> decodeText
  146. queryFortunes :: Pool -> IO (Either Error [Types.Fortune])
  147. queryFortunes dbPool = Pool.withResource dbPool $ \conn -> do
  148. fmap go $ runQuery conn decodeFortune q
  149. where
  150. s = "SELECT * FROM Fortune"
  151. q = mkQuery s []
  152. go = Either.either (Left . DbError) (pure . V.toList)