-
Notifications
You must be signed in to change notification settings - Fork 26
/
Functions.hs
140 lines (118 loc) · 4.01 KB
/
Functions.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
-- | MonadReader wrappers around @postgresql-simple@ library.
module Lib.Db.Functions
( WithDb
, initialisePool
-- * Sql functions
, query
, queryRaw
, queryNamed
, execute
, executeRaw
, executeMany
, executeNamed
, returning
-- * Error handling
, asSingleRow
, singleRowError
) where
import PgNamed (NamedParam, PgNamedError)
import Lib.App.Env (DbPool, Has, grab)
import Lib.App.Error (AppErrorType, WithError, dbError, dbNamedError, throwError, throwOnNothingM)
import qualified Data.Pool as Pool
import qualified Database.PostgreSQL.Simple as Sql
import qualified PgNamed as Sql
-- | Constraint for monadic actions that wants access to database.
type WithDb env m = (MonadReader env m, Has DbPool env, MonadIO m)
-- | Create 'Pool.Pool' by given credentials.
initialisePool :: ByteString -> IO DbPool
initialisePool credentials = Pool.createPool (Sql.connectPostgreSQL credentials) Sql.close 10 5 10
-- | Performs a query without arguments and returns the resulting rows.
queryRaw
:: forall res env m .
(WithDb env m, FromRow res)
=> Sql.Query
-> m [res]
queryRaw q = withPool $ \conn -> Sql.query_ conn q
{-# INLINE queryRaw #-}
-- | Performs a query with arguments and returns the resulting rows with the
-- given parameters.
query
:: forall res args env m .
(WithDb env m, ToRow args, FromRow res)
=> Sql.Query
-> args
-> m [res]
query q args = withPool $ \conn -> Sql.query conn q args
{-# INLINE query #-}
-- | Performs a query with named parameters and returns a list of rows.
queryNamed
:: (WithError m, WithDb env m, FromRow res)
=> Sql.Query
-> [NamedParam]
-> m [res]
queryNamed q params = withPool (\conn -> runExceptT $ Sql.queryNamed conn q params)
>>= liftDbError
{-# INLINE queryNamed #-}
-- | Executes a query without arguments that is not expected to return results.
executeRaw
:: (WithDb env m)
=> Sql.Query
-> m ()
executeRaw q = withPool $ \conn -> void $ Sql.execute_ conn q
{-# INLINE executeRaw #-}
-- | Executes a query with parameters that is not expected to return results.
execute
:: forall args env m .
(WithDb env m, ToRow args)
=> Sql.Query
-> args
-> m ()
execute q args = withPool $ \conn -> void $ Sql.execute conn q args
{-# INLINE execute #-}
-- | Executes a multi-row query that is not expected to return results.
executeMany
:: (WithDb env m, ToRow args)
=> Sql.Query
-> [args]
-> m ()
executeMany q args = withPool $ \conn -> void $ Sql.executeMany conn q args
{-# INLINE executeMany #-}
{- | Executes a query with named parameters, returning the
number of rows affected
-}
executeNamed
:: (WithError m, WithDb env m)
=> Sql.Query
-> [NamedParam]
-> m Int64
executeNamed q params = withPool (\conn -> runExceptT $ Sql.executeNamed conn q params)
>>= liftDbError
{-# INLINE executeNamed #-}
returning
:: (WithDb env m, ToRow args, FromRow res)
=> Sql.Query
-> [args]
-> m [res]
returning q args = withPool $ \conn -> Sql.returning conn q args
{-# INLINE returning #-}
-- | Perform action that needs database connection.
withPool :: WithDb env m => (Sql.Connection -> IO b) -> m b
withPool f = do
pool <- grab @DbPool
liftIO $ Pool.withResource pool f
{-# INLINE withPool #-}
-- | Lift database named parameters errors.
liftDbError :: WithError m => Either PgNamedError a -> m a
liftDbError = either (throwError . dbNamedError) pure
{-# INLINE liftDbError #-}
----------------------------------------------------------------------------
-- Error helpers
----------------------------------------------------------------------------
-- | Helper function working with results from a database when you expect
-- only one row to be returned.
asSingleRow :: (WithError m) => m [a] -> m a
asSingleRow res = withFrozenCallStack $ throwOnNothingM
singleRowError
(viaNonEmpty head <$> res)
singleRowError :: AppErrorType
singleRowError = dbError "Expected a single row, but got none"