{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.ByteString.Char8 (pack)
import Data.Functor.Contravariant ((>$<))
import Data.Int (Int32)
import Data.Text (Text)
import Effectful
import Effectful.Error.Static
import Effectful.Hasql
import Hasql.Decoders qualified as D
import Hasql.Encoders qualified as E
import Hasql.Pool (Pool, UsageError, acquire)
import Hasql.Session (Session, statement)
import Hasql.Statement (Statement (..))
import Hasql.Transaction qualified as T
import Hasql.Transaction.Sessions
import System.Environment (lookupEnv)
data User = User
{ firstName :: Text,
lastName :: Text,
userId :: Maybe Int32
}
deriving stock (Show)
userDecoder :: D.Row User
userDecoder =
User
<$> D.column (D.nonNullable D.text)
<*> D.column (D.nonNullable D.text)
<*> D.column (D.nullable D.int4)
userEncoder :: E.Params User
userEncoder =
(firstName >$< E.param (E.nonNullable E.text))
<> (lastName >$< E.param (E.nonNullable E.text))
insertUser_ :: Statement User Int32
insertUser_ =
Statement sql encoder decoder True
where
sql = "INSERT INTO users (first_name, last_name) VALUES ($1, $2) RETURNING id"
encoder = userEncoder
decoder = D.singleRow ((D.column . D.nonNullable) D.int4)
findUserById_ :: Statement Int32 (Maybe User)
findUserById_ =
Statement sql encoder decoder True
where
sql = "SELECT first_name, last_name, id FROM users WHERE id = $1"
encoder = E.param $ E.nonNullable E.int4
decoder = D.rowMaybe userDecoder
findUserById :: Int32 -> Session (Maybe User)
findUserById userId = statement userId findUserById_
insertUser :: User -> Session Int32
insertUser user = transaction Serializable Write $ do
T.statement user insertUser_
createUser :: (Hasql :> es, Error UsageError :> es) => User -> Eff es Int32
createUser user = do
runSession $ insertUser user
findUser :: (Hasql :> es, Error UsageError :> es) => Int32 -> Eff es (Maybe User)
findUser userId = do
runSession $ findUserById userId
mkPool :: IO Pool
mkPool = do
pgHost <- lookupEnv "PG_CONNECTION_STRING"
case pgHost of
Just h -> acquire 3 10 1_800 1_800 (pack h)
Nothing -> error "PG_CONNECTION_STRING env is not defined or is invalid"
createTableStatement :: Statement () ()
createTableStatement = Statement sql mempty D.noResult False
where
sql = "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, first_name TEXT, last_name TEXT)"
createTable :: (Hasql :> es, Error UsageError :> es) => Eff es ()
createTable = do
runSession $ statement () createTableStatement
newUser :: User
newUser = User "Sung-kyung" "Lee" Nothing
sampleApp :: (Hasql :> es, Error UsageError :> es) => Eff es (Maybe User)
sampleApp = do
uId <- createUser newUser
findUser uId
runToIO :: Pool -> Eff [Hasql, Error UsageError, IOE] a -> IO a
runToIO pool = runEff . runErrorWith @UsageError handleUsageError . runHasqlIO pool
handleUsageError :: (IOE :> es) => CallStack -> UsageError -> Eff es a
handleUsageError callStack err = do
liftIO $ putStrLn $ "Usage error" <> show err <> prettyCallStack callStack
error "hasql usage error"
main :: IO ()
main = do
pool <- mkPool
_ <- runToIO pool createTable
u <- runToIO pool sampleApp
print u