Drop ExceptT from functions

This commit is contained in:
Tomasz Rybarczyk 2018-12-09 21:50:13 +01:00
parent 71bbf44227
commit 87d6f7fc95
2 changed files with 83 additions and 93 deletions

View File

@ -44,14 +44,15 @@ We assume here that postgres is running on a standard local port
with `ident` authentication so configuration can be nearly empty (`defaultPoolConfiguration`). with `ident` authentication so configuration can be nearly empty (`defaultPoolConfiguration`).
It requires only database name which we pass to `newPool` function. It requires only database name which we pass to `newPool` function.
We setup also `idleTimeoutMillis` value because this code We setup also `idleTimeoutMillis` value because this code
is run by our test suite and we want to exit after execution quickly ;-) is run by our test suite and we want to exit after its execution quickly ;-)
```purescript ```purescript
run ∷ PG Unit run ∷ PG Unit
run = do run = do
pool <- liftEffect $ newPool ((defaultPoolConfiguration "purspg") { idleTimeoutMillis = Just 1000 }) pool <- liftEffect $ newPool
((defaultPoolConfiguration "purspg") { idleTimeoutMillis = Just 1000 })
withConnection pool \conn -> do withConnection pool \conn -> do
``` ```

View File

@ -1,24 +1,24 @@
module Database.PostgreSQL module Database.PostgreSQL where
( module Row -- ( module Row
, module Value -- , module Value
, PG -- , PG
, PGError(..) -- , PGError(..)
, PGErrorDetail -- , PGErrorDetail
, Database -- , Database
, PoolConfiguration -- , PoolConfiguration
, Pool -- , Pool
, Connection -- , Connection
, Query(..) -- , Query(..)
, newPool -- , newPool
, withConnection -- , withConnection
, withTransaction -- , withTransaction
, defaultPoolConfiguration -- , defaultPoolConfiguration
, command -- , command
, execute -- , execute
, query -- , query
, scalar -- , scalar
, onIntegrityError -- , onIntegrityError
) where -- ) where
import Prelude import Prelude
@ -26,7 +26,8 @@ import Control.Monad.Error.Class (catchError, throwError, try)
import Control.Monad.Except.Trans (ExceptT, except, runExceptT) import Control.Monad.Except.Trans (ExceptT, except, runExceptT)
import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Class (lift)
import Data.Array (head) import Data.Array (head)
import Data.Either (Either(..)) import Data.Bifunctor (lmap)
import Data.Either (Either(..), either, hush)
import Data.Generic.Rep (class Generic) import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Show (genericShow) import Data.Generic.Rep.Show (genericShow)
import Data.Maybe (Maybe(..), maybe) import Data.Maybe (Maybe(..), maybe)
@ -34,7 +35,7 @@ import Data.Newtype (class Newtype)
import Data.Nullable (Nullable, toMaybe, toNullable) import Data.Nullable (Nullable, toMaybe, toNullable)
import Data.String (Pattern(..)) import Data.String (Pattern(..))
import Data.String as String import Data.String as String
import Data.Traversable (traverse) import Data.Traversable (sequence, traverse)
import Database.PostgreSQL.Row (class FromSQLRow, class ToSQLRow, Row0(..), Row1(..), Row10(..), Row11(..), Row12(..), Row13(..), Row14(..), Row15(..), Row16(..), Row17(..), Row18(..), Row19(..), Row2(..), Row3(..), Row4(..), Row5(..), Row6(..), Row7(..), Row8(..), Row9(..), fromSQLRow, toSQLRow) as Row import Database.PostgreSQL.Row (class FromSQLRow, class ToSQLRow, Row0(..), Row1(..), Row10(..), Row11(..), Row12(..), Row13(..), Row14(..), Row15(..), Row16(..), Row17(..), Row18(..), Row19(..), Row2(..), Row3(..), Row4(..), Row5(..), Row6(..), Row7(..), Row8(..), Row9(..), fromSQLRow, toSQLRow) as Row
import Database.PostgreSQL.Row (class FromSQLRow, class ToSQLRow, Row0(..), Row1(..), fromSQLRow, toSQLRow) import Database.PostgreSQL.Row (class FromSQLRow, class ToSQLRow, Row0(..), Row1(..), fromSQLRow, toSQLRow)
import Database.PostgreSQL.Value (class FromSQLValue) import Database.PostgreSQL.Value (class FromSQLValue)
@ -48,15 +49,6 @@ import Foreign (Foreign)
type Database = String type Database = String
-- | PostgreSQL computations run in the `PG` monad. It's just `Aff` stacked with
-- | `ExceptT` to provide error handling.
-- |
-- | Errors originating from database queries or connection to the database are
-- | modeled with the `PGError` type. Use `runExceptT` from
-- | `Control.Monad.Except.Trans` to turn a `PG a` action into `Aff (Either
-- | PGError a)`.
type PG a = ExceptT PGError Aff a
-- | PostgreSQL connection pool configuration. -- | PostgreSQL connection pool configuration.
type PoolConfiguration = type PoolConfiguration =
{ database :: Database { database :: Database
@ -123,18 +115,18 @@ foreign import ffiNewPool
-- | Run an action with a connection. The connection is released to the pool -- | Run an action with a connection. The connection is released to the pool
-- | when the action returns. -- | when the action returns.
withConnection withConnection
:: a :: forall a
. Pool . Pool
-> (Connection -> PG a) -> (Either PGError Connection -> Aff a)
-> PG a -> Aff a
withConnection p k = withConnection p k =
except <=< lift $ bracket (connect p) cleanup run bracket (connect p) cleanup run
where where
cleanup (Left _) = pure unit cleanup (Left _) = pure unit
cleanup (Right { done }) = liftEffect done cleanup (Right { done }) = liftEffect done
run (Left err) = pure $ Left err run (Left err) = k (Left err)
run (Right { connection }) = runExceptT $ k connection run (Right { connection }) = k (Right connection)
connect connect
:: Pool :: Pool
@ -152,92 +144,89 @@ type ConnectResult =
} }
foreign import ffiConnect foreign import ffiConnect
:: a :: forall a
. { nullableLeft :: Error -> Nullable (Either PGError ConnectResult) . { nullableLeft :: Error -> Nullable (Either PGError ConnectResult)
, right :: a -> Either PGError ConnectResult , right :: a -> Either PGError ConnectResult
} }
-> Pool -> Pool
-> EffectFnAff (Either PGError ConnectResult) -> EffectFnAff (Either PGError ConnectResult)
-- | Run an action within a transaction. The transaction is committed if the -- -- | Run an action within a transaction. The transaction is committed if the
-- | action returns cleanly, and rolled back if the action throws (either a -- -- | action returns cleanly, and rolled back if the action throws (either a
-- | `PGError` or a JavaScript exception in the Aff context). If you want to -- -- | `PGError` or a JavaScript exception in the Aff context). If you want to
-- | change the transaction mode, issue a separate `SET TRANSACTION` statement -- -- | change the transaction mode, issue a separate `SET TRANSACTION` statement
-- | within the transaction. -- -- | within the transaction.
withTransaction -- withTransaction
:: a -- :: forall a
. Connection -- . Connection
-> PG a -- -> (Maybe PGError -> Aff a)
-> PG a -- -> Aff a
withTransaction conn action = -- withTransaction conn action =
begin *> lift (try $ runExceptT action) >>= case _ of -- begin *> lift (try $ runExceptT action) >>= case _ of
Left jsErr -> do -- Left jsErr -> do
rollback -- rollback
lift $ throwError jsErr -- lift $ throwError jsErr
Right (Left pgErr) -> do -- Right (Left pgErr) -> do
rollback -- rollback
throwError pgErr -- throwError pgErr
Right (Right value) -> do -- Right (Right value) -> do
commit -- commit
pure value -- pure value
--
where -- where
begin = execute conn (Query "BEGIN TRANSACTION") Row0 -- begin = execute conn (Query "BEGIN TRANSACTION") Row0
commit = execute conn (Query "COMMIT TRANSACTION") Row0 -- commit = execute conn (Query "COMMIT TRANSACTION") Row0
rollback = execute conn (Query "ROLLBACK TRANSACTION") Row0 -- rollback = execute conn (Query "ROLLBACK TRANSACTION") Row0
-- | Execute a PostgreSQL query and discard its results. -- | Execute a PostgreSQL query and discard its results.
execute execute
:: i o :: forall i o
. (ToSQLRow i) . (ToSQLRow i)
=> Connection => Connection
-> Query i o -> Query i o
-> i -> i
-> PG Unit -> Aff (Maybe PGError)
execute conn (Query sql) values = execute conn (Query sql) values =
void $ unsafeQuery conn sql (toSQLRow values) hush <<< either Right Left <$> unsafeQuery conn sql (toSQLRow values)
-- | Execute a PostgreSQL query and return its results. -- | Execute a PostgreSQL query and return its results.
query query
:: i o :: forall i o
. ToSQLRow i . ToSQLRow i
=> FromSQLRow o => FromSQLRow o
=> Connection => Connection
-> Query i o -> Query i o
-> i -> i
-> PG (Array o) -> Aff (Either PGError (Array o))
query conn (Query sql) values = do query conn (Query sql) values = do
_.rows <$> unsafeQuery conn sql (toSQLRow values) r <- unsafeQuery conn sql (toSQLRow values)
>>= traverse (fromSQLRow >>> case _ of pure $ r >>= _.rows >>> traverse (fromSQLRow >>> lmap ConversionError)
Right row -> pure row
Left msg -> throwError $ ConversionError msg)
-- | Execute a PostgreSQL query and return the first field of the first row in -- | Execute a PostgreSQL query and return the first field of the first row in
-- | the result. -- | the result.
scalar scalar
:: i o :: forall i o
. ToSQLRow i . ToSQLRow i
=> FromSQLValue o => FromSQLValue o
=> Connection => Connection
-> Query i (Row1 o) -> Query i (Row1 o)
-> i -> i
-> PG (Maybe o) -> Aff (Either PGError (Maybe o))
scalar conn sql values = scalar conn sql values =
query conn sql values query conn sql values <#> map (head >>> map (case _ of Row1 a -> a))
<#> map (case _ of Row1 a -> a) <<< head
-- | Execute a PostgreSQL query and return its command tag value -- | Execute a PostgreSQL query and return its command tag value
-- | (how many rows were affected by the query). This may be useful -- | (how many rows were affected by the query). This may be useful
-- | for example with `DELETE` or `UPDATE` queries. -- | for example with `DELETE` or `UPDATE` queries.
command command
:: i :: forall i
. ToSQLRow i . ToSQLRow i
=> Connection => Connection
-> Query i Int -> Query i Int
-> i -> i
-> PG Int -> Aff (Either PGError Int)
command conn (Query sql) values = command conn (Query sql) values =
_.rowCount <$> unsafeQuery conn sql (toSQLRow values) map _.rowCount <$> unsafeQuery conn sql (toSQLRow values)
type QueryResult = type QueryResult =
{ rows :: Array (Array Foreign) { rows :: Array (Array Foreign)
@ -248,9 +237,9 @@ unsafeQuery
:: Connection :: Connection
-> String -> String
-> Array Foreign -> Array Foreign
-> PG QueryResult -> Aff (Either PGError QueryResult)
unsafeQuery c s = unsafeQuery c s =
except <=< lift <<< fromEffectFnAff <<< ffiUnsafeQuery p c s fromEffectFnAff <<< ffiUnsafeQuery p c s
where where
p = p =
{ nullableLeft: toNullable <<< map Left <<< convertError { nullableLeft: toNullable <<< map Left <<< convertError
@ -340,11 +329,11 @@ convertError err =
prefix p = prefix p =
maybe false (_ == 0) <<< String.indexOf (Pattern p) maybe false (_ == 0) <<< String.indexOf (Pattern p)
onIntegrityError :: forall a. PG a -> PG a -> PG a -- onIntegrityError :: forall a. PG a -> PG a -> PG a
onIntegrityError errorResult db = -- onIntegrityError errorResult db =
catchError db handleError -- catchError db handleError
where -- where
handleError e = -- handleError e =
case e of -- case e of
IntegrityError _ -> errorResult -- IntegrityError _ -> errorResult
_ -> throwError e -- _ -> throwError e