Skip to content

Commit 39e9789

Browse files
committed
Generalize the base monad of fold-like operations
Previously the fold-like operations were restricted to fold operations in `IO`, greatly limiting their usefulness. Here we generalize them to any `MonadMask`, provided by the widely-used `exceptions` library. Resolves #9.
1 parent a8f6a90 commit 39e9789

File tree

4 files changed

+86
-96
lines changed

4 files changed

+86
-96
lines changed

postgresql-simple.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Library
6262
bytestring-builder,
6363
case-insensitive,
6464
containers,
65+
exceptions,
6566
hashable,
6667
postgresql-libpq >= 0.9 && < 0.10,
6768
template-haskell,

src/Database/PostgreSQL/Simple.hs

Lines changed: 72 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ module Database.PostgreSQL.Simple
121121
import Data.ByteString.Builder
122122
( Builder, byteString, char8, intDec )
123123
import Control.Applicative ((<$>))
124-
import Control.Exception as E
125-
import Control.Monad (unless)
124+
import Control.Monad (unless, void)
126125
import Data.ByteString (ByteString)
127126
import Data.Int (Int64)
128127
import Data.List (intersperse)
@@ -140,6 +139,8 @@ import Database.PostgreSQL.Simple.Transaction
140139
import Database.PostgreSQL.Simple.TypeInfo
141140
import qualified Database.PostgreSQL.LibPQ as PQ
142141
import qualified Data.ByteString.Char8 as B
142+
import Control.Monad.IO.Class
143+
import Control.Monad.Catch as E
143144
import Control.Monad.Trans.Reader
144145
import Control.Monad.Trans.State.Strict
145146

@@ -429,24 +430,24 @@ queryWith_ parser conn q@(Query que) = do
429430
--
430431
-- * 'SqlError': the postgresql backend returned an error, e.g.
431432
-- a syntax or type error, or an incorrect table or column name.
432-
fold :: ( FromRow row, ToRow params )
433+
fold :: ( MonadIO m, MonadMask m, FromRow row, ToRow params )
433434
=> Connection
434435
-> Query
435436
-> params
436437
-> a
437-
-> (a -> row -> IO a)
438-
-> IO a
438+
-> (a -> row -> m a)
439+
-> m a
439440
fold = foldWithOptions defaultFoldOptions
440441

441442
-- | A version of 'fold' taking a parser as an argument
442-
foldWith :: ( ToRow params )
443+
foldWith :: ( MonadIO m, MonadMask m, ToRow params )
443444
=> RowParser row
444445
-> Connection
445446
-> Query
446447
-> params
447448
-> a
448-
-> (a -> row -> IO a)
449-
-> IO a
449+
-> (a -> row -> m a)
450+
-> m a
450451
foldWith = foldWithOptionsAndParser defaultFoldOptions
451452

452453
-- | Number of rows to fetch at a time. 'Automatic' currently defaults
@@ -475,77 +476,80 @@ defaultFoldOptions = FoldOptions {
475476
-- accordingly. If the connection is already in a transaction,
476477
-- then the existing transaction is used and thus the 'transactionMode'
477478
-- option is ignored.
478-
foldWithOptions :: ( FromRow row, ToRow params )
479+
foldWithOptions :: ( MonadIO m, MonadMask m, FromRow row, ToRow params )
479480
=> FoldOptions
480481
-> Connection
481482
-> Query
482483
-> params
483484
-> a
484-
-> (a -> row -> IO a)
485-
-> IO a
485+
-> (a -> row -> m a)
486+
-> m a
486487
foldWithOptions opts = foldWithOptionsAndParser opts fromRow
487488

488489
-- | A version of 'foldWithOptions' taking a parser as an argument
489-
foldWithOptionsAndParser :: (ToRow params)
490+
foldWithOptionsAndParser :: ( MonadIO m, MonadMask m, ToRow params )
490491
=> FoldOptions
491492
-> RowParser row
492493
-> Connection
493494
-> Query
494495
-> params
495496
-> a
496-
-> (a -> row -> IO a)
497-
-> IO a
497+
-> (a -> row -> m a)
498+
-> m a
498499
foldWithOptionsAndParser opts parser conn template qs a f = do
499-
q <- formatQuery conn template qs
500+
q <- liftIO $ formatQuery conn template qs
500501
doFold opts parser conn template (Query q) a f
501502

502503
-- | A version of 'fold' that does not perform query substitution.
503-
fold_ :: (FromRow r) =>
504-
Connection
504+
fold_ :: ( MonadIO m, MonadMask m, FromRow r )
505+
=> Connection
505506
-> Query -- ^ Query.
506507
-> a -- ^ Initial state for result consumer.
507-
-> (a -> r -> IO a) -- ^ Result consumer.
508-
-> IO a
508+
-> (a -> r -> m a) -- ^ Result consumer.
509+
-> m a
509510
fold_ = foldWithOptions_ defaultFoldOptions
510511

511512
-- | A version of 'fold_' taking a parser as an argument
512-
foldWith_ :: RowParser r
513+
foldWith_ :: ( MonadIO m, MonadMask m)
514+
=> RowParser r
513515
-> Connection
514516
-> Query
515517
-> a
516-
-> (a -> r -> IO a)
517-
-> IO a
518+
-> (a -> r -> m a)
519+
-> m a
518520
foldWith_ = foldWithOptionsAndParser_ defaultFoldOptions
519521

520-
foldWithOptions_ :: (FromRow r) =>
521-
FoldOptions
522+
foldWithOptions_ :: ( MonadIO m, MonadMask m, FromRow r)
523+
=> FoldOptions
522524
-> Connection
523525
-> Query -- ^ Query.
524526
-> a -- ^ Initial state for result consumer.
525-
-> (a -> r -> IO a) -- ^ Result consumer.
526-
-> IO a
527+
-> (a -> r -> m a) -- ^ Result consumer.
528+
-> m a
527529
foldWithOptions_ opts conn query a f = doFold opts fromRow conn query query a f
528530

529531
-- | A version of 'foldWithOptions_' taking a parser as an argument
530-
foldWithOptionsAndParser_ :: FoldOptions
532+
foldWithOptionsAndParser_ :: ( MonadIO m, MonadMask m )
533+
=> FoldOptions
531534
-> RowParser r
532535
-> Connection
533536
-> Query -- ^ Query.
534537
-> a -- ^ Initial state for result consumer.
535-
-> (a -> r -> IO a) -- ^ Result consumer.
536-
-> IO a
538+
-> (a -> r -> m a) -- ^ Result consumer.
539+
-> m a
537540
foldWithOptionsAndParser_ opts parser conn query a f = doFold opts parser conn query query a f
538541

539-
doFold :: FoldOptions
542+
doFold :: ( MonadIO m, MonadMask m )
543+
=> FoldOptions
540544
-> RowParser row
541545
-> Connection
542546
-> Query
543547
-> Query
544548
-> a
545-
-> (a -> row -> IO a)
546-
-> IO a
549+
-> (a -> row -> m a)
550+
-> m a
547551
doFold FoldOptions{..} parser conn _template q a0 f = do
548-
stat <- withConnection conn PQ.transactionStatus
552+
stat <- liftIO $ withConnection conn PQ.transactionStatus
549553
case stat of
550554
PQ.TransIdle -> withTransactionMode transactionMode conn go
551555
PQ.TransInTrans -> go
@@ -563,15 +567,15 @@ doFold FoldOptions{..} parser conn _template q a0 f = do
563567
-- Not sure what this means.
564568
where
565569
declare = do
566-
name <- newTempName conn
567-
_ <- execute_ conn $ mconcat
570+
name <- liftIO $ newTempName conn
571+
_ <- liftIO $ execute_ conn $ mconcat
568572
[ "DECLARE ", name, " NO SCROLL CURSOR FOR ", q ]
569573
return name
570574
close name =
571-
(execute_ conn ("CLOSE " <> name) >> return ()) `E.catch` \ex ->
575+
(void $ liftIO $ execute_ conn ("CLOSE " <> name)) `E.catch` \ex ->
572576
-- Don't throw exception if CLOSE failed because the transaction is
573577
-- aborted. Otherwise, it will throw away the original error.
574-
unless (isFailedTransactionError ex) $ throwIO ex
578+
unless (isFailedTransactionError ex) $ throwM ex
575579

576580
go = bracket declare close $ \(Query name) ->
577581
let q = toByteString (byteString "FETCH FORWARD "
@@ -580,20 +584,20 @@ doFold FoldOptions{..} parser conn _template q a0 f = do
580584
<> byteString name
581585
)
582586
loop a = do
583-
result <- exec conn q
584-
status <- PQ.resultStatus result
587+
result <- liftIO $ exec conn q
588+
status <- liftIO $ PQ.resultStatus result
585589
case status of
586590
PQ.TuplesOk -> do
587-
nrows <- PQ.ntuples result
588-
ncols <- PQ.nfields result
591+
nrows <- liftIO $ PQ.ntuples result
592+
ncols <- liftIO $ PQ.nfields result
589593
if nrows > 0
590594
then do
591595
let inner a row = do
592-
x <- getRowWith parser row ncols conn result
596+
x <- liftIO $ getRowWith parser row ncols conn result
593597
f a x
594598
foldM' inner a 0 (nrows - 1) >>= loop
595599
else return a
596-
_ -> throwResultError "fold" result status
600+
_ -> liftIO $ throwResultError "fold" result status
597601
in loop a0
598602

599603
-- FIXME: choose the Automatic chunkSize more intelligently
@@ -607,44 +611,45 @@ doFold FoldOptions{..} parser conn _template q a0 f = do
607611
Fixed n -> n
608612

609613
-- | A version of 'fold' that does not transform a state value.
610-
forEach :: (ToRow q, FromRow r) =>
611-
Connection
614+
forEach :: ( MonadIO m, MonadMask m, ToRow q, FromRow r )
615+
=> Connection
612616
-> Query -- ^ Query template.
613617
-> q -- ^ Query parameters.
614-
-> (r -> IO ()) -- ^ Result consumer.
615-
-> IO ()
618+
-> (r -> m ()) -- ^ Result consumer.
619+
-> m ()
616620
forEach = forEachWith fromRow
617621
{-# INLINE forEach #-}
618622

619623
-- | A version of 'forEach' taking a parser as an argument
620-
forEachWith :: ( ToRow q )
624+
forEachWith :: ( MonadIO m, MonadMask m, ToRow q )
621625
=> RowParser r
622626
-> Connection
623627
-> Query
624628
-> q
625-
-> (r -> IO ())
626-
-> IO ()
629+
-> (r -> m ())
630+
-> m ()
627631
forEachWith parser conn template qs = foldWith parser conn template qs () . const
628632
{-# INLINE forEachWith #-}
629633

630634
-- | A version of 'forEach' that does not perform query substitution.
631-
forEach_ :: (FromRow r) =>
632-
Connection
635+
forEach_ :: ( MonadIO m, MonadMask m, FromRow r )
636+
=> Connection
633637
-> Query -- ^ Query template.
634-
-> (r -> IO ()) -- ^ Result consumer.
635-
-> IO ()
638+
-> (r -> m ()) -- ^ Result consumer.
639+
-> m ()
636640
forEach_ = forEachWith_ fromRow
637641
{-# INLINE forEach_ #-}
638642

639-
forEachWith_ :: RowParser r
643+
forEachWith_ :: ( MonadIO m , MonadMask m )
644+
=> RowParser r
640645
-> Connection
641646
-> Query
642-
-> (r -> IO ())
643-
-> IO ()
647+
-> (r -> m ())
648+
-> m ()
644649
forEachWith_ parser conn template = foldWith_ parser conn template () . const
645650
{-# INLINE forEachWith_ #-}
646651

647-
forM' :: (Ord n, Num n) => n -> n -> (n -> IO a) -> IO [a]
652+
forM' :: (Monad m, Ord n, Num n) => n -> n -> (n -> m a) -> m [a]
648653
forM' lo hi m = loop hi []
649654
where
650655
loop !n !as
@@ -654,7 +659,7 @@ forM' lo hi m = loop hi []
654659
loop (n-1) (a:as)
655660
{-# INLINE forM' #-}
656661

657-
foldM' :: (Ord n, Num n) => (a -> n -> IO a) -> a -> n -> n -> IO a
662+
foldM' :: (Monad m, Ord n, Num n) => (a -> n -> m a) -> a -> n -> n -> m a
658663
foldM' f a lo hi = loop a lo
659664
where
660665
loop a !n
@@ -669,18 +674,18 @@ finishQueryWith parser conn q result = do
669674
status <- PQ.resultStatus result
670675
case status of
671676
PQ.EmptyQuery ->
672-
throwIO $ QueryError "query: Empty query" q
677+
throwM $ QueryError "query: Empty query" q
673678
PQ.CommandOk ->
674-
throwIO $ QueryError "query resulted in a command response" q
679+
throwM $ QueryError "query resulted in a command response" q
675680
PQ.TuplesOk -> do
676681
nrows <- PQ.ntuples result
677682
ncols <- PQ.nfields result
678683
forM' 0 (nrows-1) $ \row ->
679684
getRowWith parser row ncols conn result
680685
PQ.CopyOut ->
681-
throwIO $ QueryError "query: COPY TO is not supported" q
686+
throwM $ QueryError "query: COPY TO is not supported" q
682687
PQ.CopyIn ->
683-
throwIO $ QueryError "query: COPY FROM is not supported" q
688+
throwM $ QueryError "query: COPY FROM is not supported" q
684689
PQ.BadResponse -> throwResultError "query" result status
685690
PQ.NonfatalError -> throwResultError "query" result status
686691
PQ.FatalError -> throwResultError "query" result status
@@ -698,16 +703,16 @@ getRowWith parser row ncols conn result = do
698703
v <- PQ.getvalue result row c
699704
return ( tinfo
700705
, fmap ellipsis v )
701-
throw (ConversionFailed
706+
throwM (ConversionFailed
702707
(show (unCol ncols) ++ " values: " ++ show vals)
703708
Nothing
704709
""
705710
(show (unCol col) ++ " slots in target type")
706711
"mismatch between number of columns to \
707712
\convert and number in target type")
708-
Errors [] -> throwIO $ ConversionFailed "" Nothing "" "" "unknown error"
709-
Errors [x] -> throwIO x
710-
Errors xs -> throwIO $ ManyErrors xs
713+
Errors [] -> throwM $ ConversionFailed "" Nothing "" "" "unknown error"
714+
Errors [x] -> throwM x
715+
Errors xs -> throwM $ ManyErrors xs
711716

712717
ellipsis :: ByteString -> ByteString
713718
ellipsis bs

src/Database/PostgreSQL/Simple/Compat.hs

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@
22
-- | This is a module of its own, partly because it uses the CPP extension,
33
-- which doesn't play well with backslash-broken string literals.
44
module Database.PostgreSQL.Simple.Compat
5-
( mask
6-
, (<>)
5+
( (<>)
76
, unsafeDupablePerformIO
87
, toByteString
98
, scientificBuilder
109
, toPico
1110
, fromPico
1211
) where
1312

14-
import qualified Control.Exception as E
1513
import Data.Monoid
1614
import Data.ByteString (ByteString)
1715
#if MIN_VERSION_bytestring(0,10,0)
@@ -43,23 +41,6 @@ import Data.Fixed (Fixed(MkFixed))
4341
import Unsafe.Coerce (unsafeCoerce)
4442
#endif
4543

46-
-- | Like 'E.mask', but backported to base before version 4.3.0.
47-
--
48-
-- Note that the restore callback is monomorphic, unlike in 'E.mask'. This
49-
-- could be fixed by changing the type signature, but it would require us to
50-
-- enable the RankNTypes extension (since 'E.mask' has a rank-3 type). The
51-
-- 'withTransactionMode' function calls the restore callback only once, so we
52-
-- don't need that polymorphism.
53-
mask :: ((IO a -> IO a) -> IO b) -> IO b
54-
#if MIN_VERSION_base(4,3,0)
55-
mask io = E.mask $ \restore -> io restore
56-
#else
57-
mask io = do
58-
b <- E.blocked
59-
E.block $ io $ \m -> if b then m else E.unblock m
60-
#endif
61-
{-# INLINE mask #-}
62-
6344
#if !MIN_VERSION_base(4,5,0)
6445
infixr 6 <>
6546

0 commit comments

Comments
 (0)