Skip to content

Commit 5d3be54

Browse files
committed
Add withTransaction
1 parent 52d4185 commit 5d3be54

File tree

3 files changed

+58
-1
lines changed

3 files changed

+58
-1
lines changed

MODULE.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,25 @@ withClient :: forall eff a. ConnectionInfo -> (Client -> Aff (db :: DB | eff) a)
135135
Takes a Client from the connection pool, runs the given function with
136136
the client and returns the results.
137137

138+
#### `withTransaction`
139+
140+
``` purescript
141+
withTransaction :: forall eff a. (Client -> Aff (db :: DB | eff) a) -> Client -> Aff (db :: DB | eff) a
142+
```
143+
144+
Runs an asynchronous action in a database transaction. The transaction
145+
will be rolled back if the computation fails and committed otherwise.
146+
147+
Here the first insert will be rolled back:
148+
149+
```purescript
150+
moneyTransfer :: forall e. (Client -> Aff e Unit) -> Client -> Aff e Unit
151+
moneyTransfer = withTransaction $ \c -> do
152+
execute_ (Query "insert into accounts ...") c
153+
throwError $ error "Something went wrong"
154+
execute_ (Query "insert into accounts ...") c
155+
```
156+
138157
#### `end`
139158

140159
``` purescript

src/Database/Postgres.purs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ module Database.Postgres
1414
, queryOne, queryOne_
1515
, withConnection
1616
, withClient
17+
, withTransaction
1718
) where
1819

1920
import Control.Alt
21+
import Control.Apply ((*>))
2022
import Control.Monad.Eff
2123
import Control.Monad.Trans
2224
import Data.Either
@@ -28,7 +30,7 @@ import Data.Maybe
2830
import Control.Monad.Aff
2931
import Control.Monad.Eff.Class
3032
import Control.Monad.Eff.Exception(Error(), error)
31-
import Control.Monad.Error.Class (throwError)
33+
import Control.Monad.Error.Class (throwError, catchError)
3234
import Data.Traversable (sequence)
3335

3436
import Database.Postgres.SqlValue
@@ -130,6 +132,27 @@ withClient :: forall eff a
130132
-> Aff (db :: DB | eff) a
131133
withClient info p = runFn2 _withClient (mkConnectionString info) p
132134

135+
-- | Runs an asynchronous action in a database transaction. The transaction
136+
-- | will be rolled back if the computation fails and committed otherwise.
137+
-- |
138+
-- | Here the first insert will be rolled back:
139+
-- |
140+
-- | ```purescript
141+
-- | moneyTransfer :: forall e. (Client -> Aff e Unit) -> Client -> Aff e Unit
142+
-- | moneyTransfer = withTransaction $ \c -> do
143+
-- | execute_ (Query "insert into accounts ...") c
144+
-- | throwError $ error "Something went wrong"
145+
-- | execute_ (Query "insert into accounts ...") c
146+
-- | ```
147+
withTransaction :: forall eff a. (Client -> Aff (db :: DB | eff) a) -> Client -> Aff (db :: DB | eff) a
148+
withTransaction act client = do
149+
execute_ (Query "BEGIN TRANSACTION") client
150+
res <- attempt (act client)
151+
either rollback commit res
152+
where
153+
rollback err = execute_ (Query "ROLLBACK") client *> throwError err
154+
commit v = execute_ (Query "COMMIT") client *> pure v
155+
133156
liftError :: forall e a. ForeignError -> Aff e a
134157
liftError err = throwError $ error (show err)
135158

test/Main.purs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import Control.Monad.Eff
77
import Control.Monad.Eff.Class
88
import Control.Monad.Cont.Trans
99
import Control.Monad.Trans
10+
import Control.Monad.Error.Class (throwError)
11+
import Control.Monad.Eff.Exception (error)
1012
import Data.Array
1113
import Data.Foldable
1214
import Data.Either
@@ -26,6 +28,8 @@ main = runAff (trace <<< show) (const $ trace "All ok") $ do
2628

2729
exampleQueries
2830

31+
exampleTransaction
32+
2933
liftEff $ disconnect
3034

3135
data Artist = Artist
@@ -74,6 +78,17 @@ exampleQueries = withClient connectionInfo $ \c -> do
7478
artists <- query (Query "select * from artist where name = $1" :: Query Artist) [toSql "Toto"] c
7579
liftEff $ printRows artists
7680

81+
exampleTransaction :: forall eff. Aff (db :: DB | eff) Unit
82+
exampleTransaction = withConnection connectionInfo $ \c -> do
83+
execute_ (Query "delete from artist") c
84+
apathize $ tryInsert c
85+
one <- queryOne_ (Query "select * from artist" :: Query Artist) c
86+
liftEff $ trace $ show one
87+
where
88+
tryInsert = withTransaction $ \c -> do
89+
execute_ (Query "insert into artist values ('Not there', 1999)") c
90+
throwError $ error "fail"
91+
7792
printRows :: forall a eff. (Show a) => [a] -> Eff (trace :: Trace | eff) Unit
7893
printRows rows = trace $ "result:\n" <> foldMap stringify rows
7994
where stringify = show >>> flip (<>) "\n"

0 commit comments

Comments
 (0)