1
- module Database.Postgres where
1
+ module Database.Postgres
2
+ ( Query (..)
3
+ , Client ()
4
+ , DB ()
5
+ , ConnectionInfo ()
6
+ , connect
7
+ , end
8
+ , execute
9
+ , query
10
+ , queryOne
11
+ , queryValue
12
+ , withConnection
13
+ ) where
2
14
3
15
import Control.Alt
4
16
import Control.Monad.Eff
5
- import Control.Monad.Cont.Trans
6
17
import Control.Monad.Trans
7
18
import Data.Either
8
19
import Data.Array
9
20
import Data.Foreign
10
21
import Data.Foreign.Class
11
- import Util (readSingularProperty )
22
+ import Data.Maybe
23
+ import Control.Monad.Aff
24
+ import Control.Monad.Eff.Class
25
+ import Control.Monad.Eff.Exception (Error (), error )
26
+ import Control.Monad.Error.Class (throwError )
27
+ import Data.Traversable (sequence )
12
28
13
29
newtype Query a = Query String
14
30
15
31
foreign import data Client :: *
16
32
17
33
foreign import data DB :: !
18
34
19
- type DBEff eff = Eff (db :: DB | eff )
20
-
21
35
type ConnectionInfo =
22
36
{ host :: String
23
37
, db :: String
@@ -26,81 +40,107 @@ type ConnectionInfo =
26
40
, password :: String
27
41
}
28
42
29
-
30
- -- Low-level API. --------------------------------------------------------------
31
-
32
- connect :: forall eff . ConnectionInfo -> DBEff eff Client
33
- connect ci = connectJS $ " postgres://" <> ci.user <> " :" <> ci.password <> " @" <> ci.host <> " :" <> show ci.port <> " /" <> ci.db
34
-
35
- runQuery :: forall row eff . (IsForeign row ) =>
36
- Query row -> Client -> ([F row ] -> DBEff eff Unit ) -> DBEff eff Unit
37
- runQuery (Query query) client handleRows = runQueryRowsForeignRaw query client (map deserialize >>> handleRows)
38
- where deserialize foreignVal = read foreignVal <|> readSingularProperty foreignVal
39
-
40
-
41
- foreign import connectJS " " "
42
- function connectJS(conString) {
43
- return function() {
43
+ -- | Makes a connection to the database.
44
+ connect :: forall eff . ConnectionInfo -> Aff (db :: DB | eff ) Client
45
+ connect ci = connect'
46
+ $ " postgres://"
47
+ <> ci.user <> " :"
48
+ <> ci.password <> " @"
49
+ <> ci.host <> " :"
50
+ <> show ci.port <> " /"
51
+ <> ci.db
52
+
53
+ -- | Runs a query and returns nothing.
54
+ execute :: forall eff a . Query a -> Client -> Aff (db :: DB | eff ) Unit
55
+ execute (Query sql) client = void $ runQuery sql client
56
+
57
+ -- | Runs a query and returns all results.
58
+ query :: forall eff a . (IsForeign a ) => Query a -> Client -> Aff (db :: DB | eff ) [a ]
59
+ query (Query sql) client = do
60
+ rows <- runQuery sql client
61
+ either liftError pure (sequence $ read <$> rows)
62
+
63
+ -- | Runs a query and returns the first row, if any
64
+ queryOne :: forall eff a . (IsForeign a ) => Query a -> Client -> Aff (db :: DB | eff ) (Maybe a )
65
+ queryOne (Query sql) client = do
66
+ rows <- runQuery sql client
67
+ maybe (pure Nothing ) (either liftError (pure <<< Just )) $ read <$> (rows !! 0 )
68
+
69
+ -- | Runs a query and returns a single value, if any.
70
+ queryValue :: forall eff a . (IsForeign a ) => Query a -> Client -> Aff (db :: DB | eff ) (Maybe a )
71
+ queryValue (Query sql) client = do
72
+ val <- runQueryValue sql client
73
+ either liftError (pure <<< Just ) $ read val
74
+
75
+ -- | Connects to the database, calls the provided function with the client
76
+ -- | and returns the results.
77
+ withConnection :: forall eff a
78
+ . ConnectionInfo
79
+ -> (Client -> Aff (db :: DB | eff ) a )
80
+ -> Aff (db :: DB | eff ) a
81
+ withConnection info p = do
82
+ client <- connect info
83
+ finally (p client) $ liftEff (end client)
84
+
85
+ liftError :: forall e a . ForeignError -> Aff e a
86
+ liftError err = throwError $ error (show err)
87
+
88
+ finally :: forall eff a . Aff eff a -> Aff eff Unit -> Aff eff a
89
+ finally a sequel = do
90
+ res <- attempt a
91
+ sequel
92
+ either throwError pure res
93
+
94
+ foreign import connect' " " "
95
+ function connect$prime(conString) {
96
+ return function(success, error) {
44
97
var pg = require('pg');
45
98
var client = new pg.Client(conString);
99
+ client.connect(function(err) {
100
+ if (err) {
101
+ error(err);
102
+ } else {
103
+ success(client);
104
+ }
105
+ })
46
106
return client;
107
+ }
108
+ }
109
+ " " " :: forall eff . String -> Aff (db :: DB | eff ) Client
110
+
111
+ foreign import runQuery " " "
112
+ function runQuery(queryStr) {
113
+ return function(client) {
114
+ return function(success, error) {
115
+ client.query(queryStr, function(err, result) {
116
+ if (err) {
117
+ error(err);
118
+ } else {
119
+ success(result.rows);
120
+ }
121
+ })
122
+ };
47
123
};
48
124
}
49
- " " " :: forall eff . String -> DBEff eff Client
50
-
51
-
52
- foreign import connectClient " " "
53
- function connectClient(client) {
54
- return function() {
55
- client.connect();
125
+ " " " :: forall eff . String -> Client -> Aff (db :: DB | eff ) [Foreign ]
126
+
127
+ foreign import runQueryValue " " "
128
+ function runQueryValue(queryStr) {
129
+ return function(client) {
130
+ return function(success, error) {
131
+ client.query(queryStr, function(err, result) {
132
+ if (err) return error(err);
133
+ success(result.rows.length > 0 ? result.rows[0][result.fields[0].name] : undefined);
134
+ })
135
+ };
56
136
};
57
137
}
58
- " " " :: forall eff . Client -> DBEff eff Unit
138
+ " " " :: forall eff . String -> Client -> Aff ( db :: DB | eff ) Foreign
59
139
60
- foreign import endClient " " "
61
- function endClient (client) {
140
+ foreign import end " " "
141
+ function end (client) {
62
142
return function() {
63
143
client.end();
64
144
};
65
145
}
66
- " " " :: forall eff . Client -> DBEff eff Unit
67
-
68
-
69
- foreign import runQueryRowsForeignRaw " " "
70
- function runQueryRowsForeignRaw(queryStr) {
71
- return function (client) {
72
- return function(handleRows) {
73
- return function() {
74
- client.query(queryStr, function(err, result) {
75
- if (err) {
76
- console.error('error running query', err);
77
- } else {
78
- handleRows(result.rows)();
79
- }
80
- });
81
- };
82
- };
83
- };
84
- }
85
- " " " :: forall eff eff' . String -> Client -> ([Foreign ] -> eff' ) -> DBEff eff Unit
86
-
87
-
88
- -- Continuation-based API. -----------------------------------------------------
89
-
90
- runQueryCont :: forall eff a . (IsForeign a ) => Query a -> Client -> ContT Unit (DBEff eff ) [F a ]
91
- runQueryCont query client = ContT $ runQuery query client
92
-
93
- runQueryCont_ :: forall eff . Query Unit -> Client -> ContT Unit (DBEff eff ) [F Unit ]
94
- runQueryCont_ query client = ContT $ runQuery query client
95
-
96
- withConnectionCont :: forall eff a . ConnectionInfo -> (Client -> ContT Unit (DBEff eff ) a ) -> ContT Unit (DBEff eff ) a
97
- withConnectionCont connectionInfo dbProg = do
98
- client <- lift $ connect connectionInfo
99
- lift $ connectClient client
100
- res <- dbProg client
101
- lift $ endClient client
102
- return res
103
-
104
- -- needed for runQuery_
105
- instance unitIsForeign :: IsForeign Unit where
106
- read _ = Right unit
146
+ " " " :: forall eff . Client -> Eff (db :: DB | eff ) Unit
0 commit comments