|
@@ -1,76 +1,93 @@
|
|
|
-{-# OPTIONS -funbox-strict-fields #-}
|
|
|
|
|
-{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
-
|
|
|
|
|
-module TFB.Db (
|
|
|
|
|
- Pool
|
|
|
|
|
- , mkPool
|
|
|
|
|
- , Config(..)
|
|
|
|
|
- , queryWorldById
|
|
|
|
|
- , queryWorldByIds
|
|
|
|
|
- , updateWorlds
|
|
|
|
|
- , queryFortunes
|
|
|
|
|
- , Error
|
|
|
|
|
-) where
|
|
|
|
|
-
|
|
|
|
|
-import qualified TFB.Types as Types
|
|
|
|
|
-import qualified Data.Either as Either
|
|
|
|
|
-import Control.Monad (forM, forM_)
|
|
|
|
|
-
|
|
|
|
|
-import qualified Data.Pool as Pool
|
|
|
|
|
-import Data.ByteString (ByteString)
|
|
|
|
|
-import qualified Data.ByteString.Char8 as BSC
|
|
|
|
|
-import qualified Database.MySQL.Base as MySQL
|
|
|
|
|
-import qualified System.IO.Streams as Streams
|
|
|
|
|
-import Data.Text (Text)
|
|
|
|
|
-import qualified Data.Text as Text
|
|
|
|
|
|
|
+{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
+
|
|
|
|
|
+module TFB.Db
|
|
|
|
|
+ ( Pool,
|
|
|
|
|
+ mkPool,
|
|
|
|
|
+ Config (..),
|
|
|
|
|
+ queryWorldById,
|
|
|
|
|
+ queryWorldByIds,
|
|
|
|
|
+ updateWorlds,
|
|
|
|
|
+ queryFortunes,
|
|
|
|
|
+ Error,
|
|
|
|
|
+ )
|
|
|
|
|
+where
|
|
|
|
|
+
|
|
|
|
|
+import Control.Monad (forM, forM_)
|
|
|
|
|
+import Data.ByteString (ByteString)
|
|
|
|
|
+import Data.ByteString.Char8 qualified as BSC
|
|
|
|
|
+import Data.Either qualified as Either
|
|
|
|
|
+import Data.Pool qualified as Pool
|
|
|
|
|
+import Data.Text (Text)
|
|
|
|
|
+import Data.Text qualified as Text
|
|
|
|
|
+import Database.MySQL.Base qualified as MySQL
|
|
|
|
|
+import System.IO.Streams qualified as Streams
|
|
|
|
|
+import TFB.Types qualified as Types
|
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
-- * Database
|
|
-- * Database
|
|
|
|
|
|
|
|
data Config
|
|
data Config
|
|
|
= Config
|
|
= Config
|
|
|
- { configHost :: String
|
|
|
|
|
- , configName :: ByteString
|
|
|
|
|
- , configUser :: ByteString
|
|
|
|
|
- , configPass :: ByteString
|
|
|
|
|
- , configStripes :: Int
|
|
|
|
|
- , configPoolSize :: Int
|
|
|
|
|
|
|
+ { configHost :: String,
|
|
|
|
|
+ configName :: ByteString,
|
|
|
|
|
+ configUser :: ByteString,
|
|
|
|
|
+ configPass :: ByteString,
|
|
|
|
|
+ configStripes :: Int,
|
|
|
|
|
+ configPoolSize :: Int
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
instance Show Config where
|
|
instance Show Config where
|
|
|
- show c
|
|
|
|
|
- = "Config {"
|
|
|
|
|
- <> " configHost = " <> configHost c
|
|
|
|
|
- <> ", configName = " <> BSC.unpack (configName c)
|
|
|
|
|
- <> ", configUser = " <> BSC.unpack (configUser c)
|
|
|
|
|
- <> ", configPass = REDACTED"
|
|
|
|
|
- <> ", configStripes = " <> show (configStripes c)
|
|
|
|
|
- <> ", configPoolSize = " <> show (configPoolSize c)
|
|
|
|
|
- <> " }"
|
|
|
|
|
|
|
+ show c =
|
|
|
|
|
+ "Config {"
|
|
|
|
|
+ <> " configHost = "
|
|
|
|
|
+ <> configHost c
|
|
|
|
|
+ <> ", configName = "
|
|
|
|
|
+ <> BSC.unpack (configName c)
|
|
|
|
|
+ <> ", configUser = "
|
|
|
|
|
+ <> BSC.unpack (configUser c)
|
|
|
|
|
+ <> ", configPass = REDACTED"
|
|
|
|
|
+ <> ", configStripes = "
|
|
|
|
|
+ <> show (configStripes c)
|
|
|
|
|
+ <> ", configPoolSize = "
|
|
|
|
|
+ <> show (configPoolSize c)
|
|
|
|
|
+ <> " }"
|
|
|
|
|
|
|
|
type Connection = MySQL.MySQLConn
|
|
type Connection = MySQL.MySQLConn
|
|
|
|
|
+
|
|
|
type Pool = Pool.Pool Connection
|
|
type Pool = Pool.Pool Connection
|
|
|
|
|
+
|
|
|
type Error = Text
|
|
type Error = Text
|
|
|
|
|
+
|
|
|
type DbRow = [MySQL.MySQLValue]
|
|
type DbRow = [MySQL.MySQLValue]
|
|
|
|
|
|
|
|
connect :: Config -> IO Connection
|
|
connect :: Config -> IO Connection
|
|
|
connect c = MySQL.connect myc
|
|
connect c = MySQL.connect myc
|
|
|
where
|
|
where
|
|
|
- myc = MySQL.defaultConnectInfoMB4
|
|
|
|
|
- { MySQL.ciHost = configHost c
|
|
|
|
|
- , MySQL.ciDatabase = configName c
|
|
|
|
|
- , MySQL.ciUser = configUser c
|
|
|
|
|
- , MySQL.ciPassword = configPass c
|
|
|
|
|
|
|
+ myc =
|
|
|
|
|
+ MySQL.defaultConnectInfoMB4
|
|
|
|
|
+ { MySQL.ciHost = configHost c,
|
|
|
|
|
+ MySQL.ciDatabase = configName c,
|
|
|
|
|
+ MySQL.ciUser = configUser c,
|
|
|
|
|
+ MySQL.ciPassword = configPass c
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
close :: Connection -> IO ()
|
|
close :: Connection -> IO ()
|
|
|
close = MySQL.close
|
|
close = MySQL.close
|
|
|
|
|
|
|
|
mkPool :: Config -> IO Pool
|
|
mkPool :: Config -> IO Pool
|
|
|
-mkPool c = Pool.createPool (connect c) close (configStripes c) 0.5 (configPoolSize c)
|
|
|
|
|
|
|
+mkPool c =
|
|
|
|
|
+ Pool.newPool $
|
|
|
|
|
+ Pool.setNumStripes (Just $ configStripes c) $
|
|
|
|
|
+ Pool.defaultPoolConfig
|
|
|
|
|
+ (connect c)
|
|
|
|
|
+ close
|
|
|
|
|
+ 0.5
|
|
|
|
|
+ (configPoolSize c)
|
|
|
|
|
|
|
|
{-# SPECIALIZE intValEnc :: Int -> MySQL.MySQLValue #-}
|
|
{-# SPECIALIZE intValEnc :: Int -> MySQL.MySQLValue #-}
|
|
|
{-# SPECIALIZE intValEnc :: Types.QId -> MySQL.MySQLValue #-}
|
|
{-# SPECIALIZE intValEnc :: Types.QId -> MySQL.MySQLValue #-}
|
|
|
-intValEnc :: Integral a => a -> MySQL.MySQLValue
|
|
|
|
|
|
|
+intValEnc :: (Integral a) => a -> MySQL.MySQLValue
|
|
|
intValEnc = MySQL.MySQLInt16U . fromIntegral
|
|
intValEnc = MySQL.MySQLInt16U . fromIntegral
|
|
|
|
|
|
|
|
intValDec :: MySQL.MySQLValue -> Either Text Int
|
|
intValDec :: MySQL.MySQLValue -> Either Text Int
|
|
@@ -82,19 +99,20 @@ intValDec (MySQL.MySQLInt32U i) = pure . fromIntegral $ i
|
|
|
intValDec (MySQL.MySQLInt32 i) = pure . fromIntegral $ i
|
|
intValDec (MySQL.MySQLInt32 i) = pure . fromIntegral $ i
|
|
|
intValDec (MySQL.MySQLInt64U i) = pure . fromIntegral $ i
|
|
intValDec (MySQL.MySQLInt64U i) = pure . fromIntegral $ i
|
|
|
intValDec (MySQL.MySQLInt64 i) = pure . fromIntegral $ i
|
|
intValDec (MySQL.MySQLInt64 i) = pure . fromIntegral $ i
|
|
|
-intValDec x = Left $ "Expected MySQLInt*, received" <> (Text.pack $ show x)
|
|
|
|
|
|
|
+intValDec x = Left $ "Expected MySQLInt*, received" <> Text.pack (show x)
|
|
|
|
|
|
|
|
textValDec :: MySQL.MySQLValue -> Either Text Text
|
|
textValDec :: MySQL.MySQLValue -> Either Text Text
|
|
|
textValDec (MySQL.MySQLText t) = pure t
|
|
textValDec (MySQL.MySQLText t) = pure t
|
|
|
-textValDec x = Left $ "Expected Text, received" <> (Text.pack $ show x)
|
|
|
|
|
|
|
+textValDec x = Left $ "Expected Text, received" <> Text.pack (show x)
|
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
-- * World
|
|
-- * World
|
|
|
|
|
|
|
|
decodeWorld :: DbRow -> Either Error Types.World
|
|
decodeWorld :: DbRow -> Either Error Types.World
|
|
|
decodeWorld [] = Left "MarshalError: Expected 2 columns for World, found 0"
|
|
decodeWorld [] = Left "MarshalError: Expected 2 columns for World, found 0"
|
|
|
-decodeWorld (_:[]) = Left "MarshalError: Expected 2 columns for World, found 1"
|
|
|
|
|
-decodeWorld (c1:c2:_) = Types.World <$> intValDec c1 <*> intValDec c2
|
|
|
|
|
|
|
+decodeWorld [_] = Left "MarshalError: Expected 2 columns for World, found 1"
|
|
|
|
|
+decodeWorld (c1 : c2 : _) = Types.World <$> intValDec c1 <*> intValDec c2
|
|
|
|
|
|
|
|
queryWorldById :: Pool -> Types.QId -> IO (Either Error Types.World)
|
|
queryWorldById :: Pool -> Types.QId -> IO (Either Error Types.World)
|
|
|
queryWorldById dbPool wId = Pool.withResource dbPool $ \conn -> do
|
|
queryWorldById dbPool wId = Pool.withResource dbPool $ \conn -> do
|
|
@@ -105,7 +123,7 @@ queryWorldById dbPool wId = Pool.withResource dbPool $ \conn -> do
|
|
|
return $ case err of
|
|
return $ case err of
|
|
|
[] -> case oks of
|
|
[] -> case oks of
|
|
|
[] -> Left "World not found!"
|
|
[] -> Left "World not found!"
|
|
|
- ws -> pure $ head ws
|
|
|
|
|
|
|
+ w : _ -> pure w
|
|
|
_ -> Left . mconcat $ err
|
|
_ -> Left . mconcat $ err
|
|
|
where
|
|
where
|
|
|
s = "SELECT * FROM World WHERE id = ?"
|
|
s = "SELECT * FROM World WHERE id = ?"
|
|
@@ -134,15 +152,16 @@ updateWorlds dbPool wsUpdates = Pool.withResource dbPool $ \conn -> do
|
|
|
MySQL.closeStmt conn sId
|
|
MySQL.closeStmt conn sId
|
|
|
return . pure $ ws
|
|
return . pure $ ws
|
|
|
where
|
|
where
|
|
|
- updateW (w,wNum) = w { Types.wRandomNumber = wNum }
|
|
|
|
|
|
|
+ updateW (w, wNum) = w {Types.wRandomNumber = wNum}
|
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
-- * Fortunes
|
|
-- * Fortunes
|
|
|
|
|
|
|
|
decodeFortune :: DbRow -> Either Error Types.Fortune
|
|
decodeFortune :: DbRow -> Either Error Types.Fortune
|
|
|
decodeFortune [] = Left "MarshalError: Expected 2 columns for Fortune, found 0"
|
|
decodeFortune [] = Left "MarshalError: Expected 2 columns for Fortune, found 0"
|
|
|
-decodeFortune (_:[]) = Left "MarshalError: Expected 2 columns for Fortune, found 1"
|
|
|
|
|
-decodeFortune (c1:c2:_) = Types.Fortune <$> intValDec c1 <*> textValDec c2
|
|
|
|
|
|
|
+decodeFortune [_] = Left "MarshalError: Expected 2 columns for Fortune, found 1"
|
|
|
|
|
+decodeFortune (c1 : c2 : _) = Types.Fortune <$> intValDec c1 <*> textValDec c2
|
|
|
|
|
|
|
|
queryFortunes :: Pool -> IO (Either Error [Types.Fortune])
|
|
queryFortunes :: Pool -> IO (Either Error [Types.Fortune])
|
|
|
queryFortunes dbPool = Pool.withResource dbPool $ \conn -> do
|
|
queryFortunes dbPool = Pool.withResource dbPool $ \conn -> do
|
|
@@ -152,4 +171,4 @@ queryFortunes dbPool = Pool.withResource dbPool $ \conn -> do
|
|
|
let (err, oks) = Either.partitionEithers eFortunes
|
|
let (err, oks) = Either.partitionEithers eFortunes
|
|
|
return $ case err of
|
|
return $ case err of
|
|
|
[] -> pure oks
|
|
[] -> pure oks
|
|
|
- _ -> Left $ head err
|
|
|
|
|
|
|
+ w : _ -> Left w
|