Db.hs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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 Control.Monad (forM, forM_)
  15. import qualified Hasql.Decoders as HasqlDec
  16. import qualified Hasql.Encoders as HasqlEnc
  17. import Hasql.Pool (Pool, acquire, UsageError, use)
  18. import qualified Hasql.Statement as HasqlStatement
  19. import Hasql.Session (statement)
  20. import Hasql.Connection (settings, Settings)
  21. import Data.Functor.Contravariant (contramap)
  22. import Data.ByteString (ByteString)
  23. import qualified Data.ByteString.Char8 as BSC
  24. -------------------------------------------------------------------------------
  25. -- * Database
  26. data Config
  27. = Config
  28. { configHost :: String
  29. , configName :: ByteString
  30. , configUser :: ByteString
  31. , configPass :: ByteString
  32. , configStripes :: Int
  33. , configPoolSize :: Int
  34. }
  35. instance Show Config where
  36. show c
  37. = "Config {"
  38. <> " configHost = " <> configHost c
  39. <> ", configName = " <> BSC.unpack (configName c)
  40. <> ", configUser = " <> BSC.unpack (configUser c)
  41. <> ", configPass = REDACTED"
  42. <> ", configStripes = " <> show (configStripes c)
  43. <> ", configPoolSize = " <> show (configPoolSize c)
  44. <> " }"
  45. type Error = UsageError
  46. mkSettings :: Config -> Settings
  47. mkSettings c = settings (BSC.pack $ configHost c) 5432 (configUser c) (configPass c) (configName c)
  48. mkPool :: Config -> IO Pool
  49. mkPool c = acquire (configPoolSize c, 0.5, mkSettings c)
  50. intValEnc :: HasqlEnc.Params Types.QId
  51. intValEnc = contramap fromIntegral $ HasqlEnc.param HasqlEnc.int2
  52. intValDec :: HasqlDec.Row Types.QId
  53. intValDec = fmap fromIntegral $ HasqlDec.column HasqlDec.int2
  54. -------------------------------------------------------------------------------
  55. -- * World
  56. selectSingle :: HasqlStatement.Statement Types.QId Types.World
  57. selectSingle = HasqlStatement.Statement q intValEnc decoder True
  58. where
  59. q = "SELECT * FROM World WHERE (id = $1)"
  60. decoder = HasqlDec.singleRow $ Types.World <$> intValDec <*> intValDec
  61. queryWorldById :: Pool -> Types.QId -> IO (Either Error Types.World)
  62. queryWorldById pool wId = use pool (statement wId selectSingle)
  63. queryWorldByIds :: Pool -> [Types.QId] -> IO (Either Error [Types.World])
  64. queryWorldByIds _ [] = pure . pure $ mempty
  65. queryWorldByIds pool wIds = use pool $ do
  66. forM wIds $ \wId -> statement wId selectSingle
  67. updateSingle :: HasqlStatement.Statement (Types.QId, Types.QId) ()
  68. updateSingle = HasqlStatement.Statement q encoder decoder True
  69. where
  70. q = "UPDATE World SET randomNumber = $1 WHERE id = $2"
  71. encoder = contramap fst intValEnc <> contramap snd intValEnc
  72. decoder = HasqlDec.unit
  73. updateWorlds :: Pool -> [(Types.World, Types.QId)] -> IO (Either Error [Types.World])
  74. updateWorlds _ [] = pure . pure $ mempty
  75. updateWorlds pool wsUpdates = use pool $ do
  76. let ws = fmap updateW wsUpdates
  77. forM_ wsUpdates $ \(w, wNum) -> do
  78. statement (Types.wId w, wNum) updateSingle
  79. return ws
  80. where
  81. updateW (w,wNum) = w { Types.wRandomNumber = wNum }
  82. -------------------------------------------------------------------------------
  83. -- * Fortunes
  84. selectFortunes :: HasqlStatement.Statement () [Types.Fortune]
  85. selectFortunes = HasqlStatement.Statement q encoder decoder True
  86. where
  87. q = "SELECT * FROM Fortune"
  88. encoder = HasqlEnc.unit
  89. -- TODO: investigate whether 'rowList' is worth the more expensive 'cons'.
  90. decoder = HasqlDec.rowList $ Types.Fortune <$> intValDec <*> HasqlDec.column HasqlDec.text
  91. {-# INLINE selectFortunes #-}
  92. queryFortunes :: Pool -> IO (Either Error [Types.Fortune])
  93. queryFortunes pool = use pool (statement () selectFortunes)