Skip to content

Commit

Permalink
Update code to use new Aff 4.0 APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyd256 committed Oct 16, 2017
1 parent 274110c commit 86ae9a5
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 120 deletions.
4 changes: 2 additions & 2 deletions bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "purescript-node-postgres",
"version": "2.0.0",
"version": "4.0.0",
"moduleType": [
"node"
],
Expand Down Expand Up @@ -40,7 +40,7 @@
"purescript-foreign-generic": "^5.0.0"
},
"devDependencies": {
"purescript-spec": "git@github.com:justinwoo/purescript-spec.git#3a0ac612af654ff74e9183fd1d46b8ef8b505f8f",
"purescript-spec": "^2.0.0",
"purescript-generics": "^4.0.0",
"purescript-js-date": "^5.1.0"
}
Expand Down
83 changes: 43 additions & 40 deletions src/Database/Postgres.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,95 +3,98 @@

var pg = require('pg');

exports["connect'"] = function (conString) {
return function(success, error) {
var client = new pg.Client(conString);
client.connect(function(err) {
if (err) {
error(err);
} else {
success(client);
}
})
return client;
exports.mkPool = function (conInfo) {
return function () {
return new pg.Pool(conInfo);
};
}

exports._withClient = function (conString, cb) {
return function(success, error) {
pg.connect(conString, function(err, client, done) {
exports["connect'"] = function (pool) {
return function(error, success) {
pool.connect(function(err, client) {
if (err) {
done(true);
return error(err);
}
cb(client)(function(v) {
done();
success(v);
}, function(err) {
done();
error(err);
})
} else {
success(client);
}
});
return function(cancelError, onCancelerError, onCancelerSuccess) {
onCancelerSuccess();
};
};
}

exports.runQuery_ = function (queryStr) {
exports.runQuery_ = function(queryStr) {
return function(client) {
return function(success, error) {
return function(error, success) {
client.query(queryStr, function(err, result) {
if (err) {
error(err);
} else {
success(result.rows);
}
})
});
return function(cancelError, onCancelerError, onCancelerSuccess) {
onCancelerSuccess();
};
};
};
}

exports.runQuery = function (queryStr) {
exports.runQuery = function(queryStr) {
return function(params) {
return function(client) {
return function(success, error) {
return function(error, success) {
client.query(queryStr, params, function(err, result) {
if (err) return error(err);
success(result.rows);
})
});
return function(cancelError, onCancelerError, onCancelerSuccess) {
onCancelerSuccess();
};
};
};
};
}

exports.runQueryValue_ = function (queryStr) {
exports.runQueryValue_ = function(queryStr) {
return function(client) {
return function(success, error) {
return function(error, success) {
client.query(queryStr, function(err, result) {
if (err) return error(err);
success(result.rows.length > 0 ? result.rows[0][result.fields[0].name] : undefined);
})
});
return function(cancelError, onCancelerError, onCancelerSuccess) {
onCancelerSuccess();
};
};
};
}

exports.runQueryValue = function (queryStr) {
exports.runQueryValue = function(queryStr) {
return function(params) {
return function(client) {
return function(success, error) {
return function(error, success) {
client.query(queryStr, params, function(err, result) {
if (err) return error(err);
success(result.rows.length > 0 ? result.rows[0][result.fields[0].name] : undefined);
})
});
return function(cancelError, onCancelerError, onCancelerSuccess) {
onCancelerSuccess();
};
};
};
};
}

exports.end = function (client) {
return function() {
client.end();
exports.release = function (client) {
return function () {
client.release();
};
}

exports.disconnect = function () {
pg.end();
exports.end = function(pool) {
return function() {
pool.end();
};
}
143 changes: 85 additions & 58 deletions src/Database/Postgres.purs
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
module Database.Postgres
( Query(..)
, Client()
, DB()
, ConnectionInfo()
, ConnectionString()
, mkConnectionString
, Client
, Pool
, DB
, ConnectionInfo
, ClientConfig
, PoolConfig
, ConnectionString
, connectionInfoFromConfig
, connectionInfoFromString
, defaultPoolConfig
, connect
, disconnect
, release
, end
, execute, execute_
, query, query_
, queryValue, queryValue_
, queryOne, queryOne_
, withConnection
, withClient
, mkPool
) where

import Prelude

import Control.Monad.Aff (Aff, bracket)
import Control.Monad.Aff.Compat (EffFnAff, fromEffFnAff)
import Control.Monad.Eff (kind Effect, Eff)
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Exception (error)
Expand All @@ -28,128 +34,149 @@ import Data.Array ((!!))
import Data.Either (Either, either)
import Data.Foreign (Foreign, MultipleErrors)
import Data.Foreign.Class (class Decode, decode)
import Data.Function.Uncurried (Fn2, runFn2)
import Data.Maybe (Maybe(Just, Nothing), maybe)
import Data.Traversable (sequence)
import Database.Postgres.SqlValue (SqlValue)
import Unsafe.Coerce (unsafeCoerce)

newtype Query a = Query String

foreign import data Pool :: Type

foreign import data Client :: Type

foreign import data DB :: Effect

foreign import data ConnectionInfo :: Type

type ConnectionString = String

type ConnectionInfo =
connectionInfoFromString :: ConnectionString -> ConnectionInfo
connectionInfoFromString s = unsafeCoerce { connectionString: s }

type ClientConfig =
{ host :: String
, db :: String
, database :: String
, port :: Int
, user :: String
, password :: String
, ssl :: Boolean
}

mkConnectionString :: ConnectionInfo -> ConnectionString
mkConnectionString ci =
"postgres://"
<> ci.user <> ":"
<> ci.password <> "@"
<> ci.host <> ":"
<> show ci.port <> "/"
<> ci.db
type PoolConfig =
{ connectionTimeoutMillis :: Int
, idleTimeoutMillis :: Int
, max :: Int
}

-- | Makes a connection to the database.
connect :: forall eff. ConnectionInfo -> Aff (db :: DB | eff) Client
connect = connect' <<< mkConnectionString
defaultPoolConfig :: PoolConfig
defaultPoolConfig =
{ connectionTimeoutMillis: 0
, idleTimeoutMillis: 30000
, max: 10
}

connectionInfoFromConfig :: ClientConfig -> PoolConfig -> ConnectionInfo
connectionInfoFromConfig c p = unsafeCoerce
{ host: c.host
, database: c.database
, port: c.port
, user: c.user
, password: c.password
, ssl: c.ssl
, connectionTimeoutMillis: p.connectionTimeoutMillis
, idleTimeoutMillis: p.idleTimeoutMillis
, max: p.max
}

-- | Makes a connection to the database via a Client.
connect :: forall eff. Pool -> Aff (db :: DB | eff) Client
connect = fromEffFnAff <<< connect'

-- | Runs a query and returns nothing.
execute :: forall eff a. Query a -> Array SqlValue -> Client -> Aff (db :: DB | eff) Unit
execute (Query sql) params client = void $ runQuery sql params client
execute (Query sql) params client = void $ fromEffFnAff $ runQuery sql params client

-- | Runs a query and returns nothing
execute_ :: forall eff a. Query a -> Client -> Aff (db :: DB | eff) Unit
execute_ (Query sql) client = void $ runQuery_ sql client
execute_ (Query sql) client = void $ fromEffFnAff $ runQuery_ sql client

-- | Runs a query and returns all results.
query :: forall eff a
. (Decode a)
. Decode a
=> Query a -> Array SqlValue -> Client -> Aff (db :: DB | eff) (Array a)
query (Query sql) params client = do
rows <- runQuery sql params client
rows <- fromEffFnAff $ runQuery sql params client
either liftError pure (runExcept (sequence $ decode <$> rows))

-- | Just like `query` but does not make any param replacement
query_ :: forall eff a. (Decode a) => Query a -> Client -> Aff (db :: DB | eff) (Array a)
query_ :: forall eff a
. Decode a
=> Query a -> Client -> Aff (db :: DB | eff) (Array a)
query_ (Query sql) client = do
rows <- runQuery_ sql client
rows <- fromEffFnAff $ runQuery_ sql client
either liftError pure (runExcept (sequence $ decode <$> rows))

-- | Runs a query and returns the first row, if any
queryOne :: forall eff a
. (Decode a)
. Decode a
=> Query a -> Array SqlValue -> Client -> Aff (db :: DB | eff) (Maybe a)
queryOne (Query sql) params client = do
rows <- runQuery sql params client
rows <- fromEffFnAff $ runQuery sql params client
maybe (pure Nothing) (either liftError (pure <<< Just)) (decodeFirst rows)

-- | Just like `queryOne` but does not make any param replacement
queryOne_ :: forall eff a. (Decode a) => Query a -> Client -> Aff (db :: DB | eff) (Maybe a)
queryOne_ :: forall eff a
. Decode a
=> Query a -> Client -> Aff (db :: DB | eff) (Maybe a)
queryOne_ (Query sql) client = do
rows <- runQuery_ sql client
rows <- fromEffFnAff $ runQuery_ sql client
maybe (pure Nothing) (either liftError (pure <<< Just)) (decodeFirst rows)

-- | Runs a query and returns a single value, if any.
queryValue :: forall eff a
. (Decode a)
. Decode a
=> Query a -> Array SqlValue -> Client -> Aff (db :: DB | eff) (Maybe a)
queryValue (Query sql) params client = do
val <- runQueryValue sql params client
val <- fromEffFnAff $ runQueryValue sql params client
pure $ either (const Nothing) Just (runExcept (decode val))

-- | Just like `queryValue` but does not make any param replacement
queryValue_ :: forall eff a. (Decode a) => Query a -> Client -> Aff (db :: DB | eff) (Maybe a)
queryValue_ :: forall eff a
. Decode a
=> Query a -> Client -> Aff (db :: DB | eff) (Maybe a)
queryValue_ (Query sql) client = do
val <- runQueryValue_ sql client
val <- fromEffFnAff $ runQueryValue_ sql client
either liftError (pure <<< Just) $ runExcept (decode val)

-- | Connects to the database, calls the provided function with the client
-- | and returns the results.
withConnection :: forall eff a
. ConnectionInfo
-> (Client -> Aff (db :: DB | eff) a)
-> Aff (db :: DB | eff) a
withConnection info p =
withClient :: forall eff a
. Pool -> (Client -> Aff (db :: DB | eff) a) -> Aff (db :: DB | eff) a
withClient pool p =
bracket
(connect info)
(liftEff <<< end)
(connect pool)
(liftEff <<< release)
p

-- | Takes a Client from the connection pool, runs the given function with
-- | the client and returns the results.
withClient :: forall eff a
. ConnectionInfo
-> (Client -> Aff (db :: DB | eff) a)
-> Aff (db :: DB | eff) a
withClient info p = runFn2 _withClient (mkConnectionString info) p

decodeFirst :: forall a. Decode a => Array Foreign -> Maybe (Either MultipleErrors a)
decodeFirst rows = runExcept <<< decode <$> (rows !! 0)

liftError :: forall e a. MultipleErrors -> Aff e a
liftError errs = throwError $ error (show errs)

foreign import connect' :: forall eff. String -> Aff (db :: DB | eff) Client
foreign import mkPool :: forall eff. ConnectionInfo -> Eff (db :: DB | eff) Pool

foreign import _withClient :: forall eff a. Fn2 ConnectionString (Client -> Aff (db :: DB | eff) a) (Aff (db :: DB | eff) a)
foreign import connect' :: forall eff. Pool -> EffFnAff (db :: DB | eff) Client

foreign import runQuery_ :: forall eff. String -> Client -> Aff (db :: DB | eff) (Array Foreign)
foreign import runQuery_ :: forall eff. String -> Client -> EffFnAff (db :: DB | eff) (Array Foreign)

foreign import runQuery :: forall eff. String -> Array SqlValue -> Client -> Aff (db :: DB | eff) (Array Foreign)
foreign import runQuery :: forall eff. String -> Array SqlValue -> Client -> EffFnAff (db :: DB | eff) (Array Foreign)

foreign import runQueryValue_ :: forall eff. String -> Client -> Aff (db :: DB | eff) Foreign
foreign import runQueryValue_ :: forall eff. String -> Client -> EffFnAff (db :: DB | eff) Foreign

foreign import runQueryValue :: forall eff. String -> Array SqlValue -> Client -> Aff (db :: DB | eff) Foreign
foreign import runQueryValue :: forall eff. String -> Array SqlValue -> Client -> EffFnAff (db :: DB | eff) Foreign

foreign import end :: forall eff. Client -> Eff (db :: DB | eff) Unit
foreign import release :: forall eff. Client -> Eff (db :: DB | eff) Unit

foreign import disconnect :: forall eff. Eff (db :: DB | eff) Unit
foreign import end :: forall eff. Pool -> Eff (db :: DB | eff) Unit
Loading

0 comments on commit 86ae9a5

Please sign in to comment.