packages feed

hpqtypes 1.3.0 → 1.3.1

raw patch · 18 files changed

+439/−153 lines, 18 filesdep ~QuickCheckPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: QuickCheck

API changes (from Hackage documentation)

- Database.PostgreSQL.PQTypes.Fold: foldLeftM :: (MonadBase IO m, MonadDB m, FromRow row) => (acc -> row -> m acc) -> acc -> m acc
- Database.PostgreSQL.PQTypes.Fold: foldRightM :: (MonadBase IO m, MonadDB m, FromRow row) => (row -> acc -> m acc) -> acc -> m acc
- Database.PostgreSQL.PQTypes.Internal.Query: runSQLQuery :: (IsSQL sql, MonadBase IO m) => (StateT DBState m Int -> r) -> sql -> r
+ Database.PostgreSQL.PQTypes.Class: getNotification :: MonadDB m => Int -> m (Maybe Notification)
+ Database.PostgreSQL.PQTypes.Internal.C.Interface: c_PQconsumeInput :: Ptr PGconn -> IO CInt
+ Database.PostgreSQL.PQTypes.Internal.C.Interface: c_PQfreemem :: Ptr a -> IO ()
+ Database.PostgreSQL.PQTypes.Internal.C.Interface: c_PQsocket :: Ptr PGconn -> IO Fd
+ Database.PostgreSQL.PQTypes.Internal.Connection: withConnectionData :: Connection -> String -> (ConnectionData -> IO (ConnectionData, r)) -> IO r
+ Database.PostgreSQL.PQTypes.Internal.Fold: foldLeftM :: (MonadBase IO m, MonadDB m, FromRow row) => (acc -> row -> m acc) -> acc -> m acc
+ Database.PostgreSQL.PQTypes.Internal.Fold: foldRightM :: (MonadBase IO m, MonadDB m, FromRow row) => (row -> acc -> m acc) -> acc -> m acc
+ Database.PostgreSQL.PQTypes.Internal.Notification: Channel :: (RawSQL ()) -> Channel
+ Database.PostgreSQL.PQTypes.Internal.Notification: Notification :: !CPid -> !Channel -> !ByteString -> Notification
+ Database.PostgreSQL.PQTypes.Internal.Notification: data Notification
+ Database.PostgreSQL.PQTypes.Internal.Notification: getNotificationIO :: DBState -> Int -> IO (Maybe Notification)
+ Database.PostgreSQL.PQTypes.Internal.Notification: instance Eq Channel
+ Database.PostgreSQL.PQTypes.Internal.Notification: instance Eq Notification
+ Database.PostgreSQL.PQTypes.Internal.Notification: instance IsString Channel
+ Database.PostgreSQL.PQTypes.Internal.Notification: instance Ord Channel
+ Database.PostgreSQL.PQTypes.Internal.Notification: instance Ord Notification
+ Database.PostgreSQL.PQTypes.Internal.Notification: instance Show Channel
+ Database.PostgreSQL.PQTypes.Internal.Notification: instance Show Notification
+ Database.PostgreSQL.PQTypes.Internal.Notification: instance Storable Notification
+ Database.PostgreSQL.PQTypes.Internal.Notification: instance Typeable Channel
+ Database.PostgreSQL.PQTypes.Internal.Notification: instance Typeable Notification
+ Database.PostgreSQL.PQTypes.Internal.Notification: newtype Channel
+ Database.PostgreSQL.PQTypes.Internal.Notification: ntChannel :: Notification -> !Channel
+ Database.PostgreSQL.PQTypes.Internal.Notification: ntPID :: Notification -> !CPid
+ Database.PostgreSQL.PQTypes.Internal.Notification: ntPayload :: Notification -> !ByteString
+ Database.PostgreSQL.PQTypes.Internal.Query: runQueryIO :: IsSQL sql => sql -> DBState -> IO (Int, DBState)
+ Database.PostgreSQL.PQTypes.Notification: Channel :: (RawSQL ()) -> Channel
+ Database.PostgreSQL.PQTypes.Notification: Notification :: !CPid -> !Channel -> !ByteString -> Notification
+ Database.PostgreSQL.PQTypes.Notification: data Notification
+ Database.PostgreSQL.PQTypes.Notification: listen :: MonadDB m => Channel -> m ()
+ Database.PostgreSQL.PQTypes.Notification: newtype Channel
+ Database.PostgreSQL.PQTypes.Notification: notify :: MonadDB m => Channel -> ByteString -> m ()
+ Database.PostgreSQL.PQTypes.Notification: ntChannel :: Notification -> !Channel
+ Database.PostgreSQL.PQTypes.Notification: ntPID :: Notification -> !CPid
+ Database.PostgreSQL.PQTypes.Notification: ntPayload :: Notification -> !ByteString
+ Database.PostgreSQL.PQTypes.Notification: unlisten :: MonadDB m => Channel -> m ()
+ Database.PostgreSQL.PQTypes.Notification: unlistenAll :: MonadDB m => m ()
+ Database.PostgreSQL.PQTypes.XML: XML :: Text -> XML
+ Database.PostgreSQL.PQTypes.XML: instance Eq XML
+ Database.PostgreSQL.PQTypes.XML: instance FromSQL XML
+ Database.PostgreSQL.PQTypes.XML: instance Ord XML
+ Database.PostgreSQL.PQTypes.XML: instance PQFormat XML
+ Database.PostgreSQL.PQTypes.XML: instance Read XML
+ Database.PostgreSQL.PQTypes.XML: instance Show XML
+ Database.PostgreSQL.PQTypes.XML: instance ToSQL XML
+ Database.PostgreSQL.PQTypes.XML: instance Typeable XML
+ Database.PostgreSQL.PQTypes.XML: newtype XML
+ Database.PostgreSQL.PQTypes.XML: unXML :: XML -> Text

Files

hpqtypes.cabal view
@@ -1,5 +1,5 @@ name:                hpqtypes-version:             1.3.0+version:             1.3.1 synopsis:            Haskell bindings to libpqtypes  description:         Efficient and easy-to-use bindings to (slightly modified)@@ -68,7 +68,6 @@                      , Database.PostgreSQL.PQTypes.Utils                      , Database.PostgreSQL.PQTypes.FromSQL                      , Database.PostgreSQL.PQTypes.Array-                     , Database.PostgreSQL.PQTypes.Fold                      , Database.PostgreSQL.PQTypes.FromRow                      , Database.PostgreSQL.PQTypes.ToSQL                      , Database.PostgreSQL.PQTypes.Transaction@@ -78,18 +77,22 @@                      , Database.PostgreSQL.PQTypes.Format                      , Database.PostgreSQL.PQTypes.Binary                      , Database.PostgreSQL.PQTypes.Interval+                     , Database.PostgreSQL.PQTypes.Notification                      , Database.PostgreSQL.PQTypes.SQL                      , Database.PostgreSQL.PQTypes.Single                      , Database.PostgreSQL.PQTypes.SQL.Raw                      , Database.PostgreSQL.PQTypes.SQL.Class                      , Database.PostgreSQL.PQTypes.Transaction.Settings+                     , Database.PostgreSQL.PQTypes.XML                      , Database.PostgreSQL.PQTypes.Internal.Error                      , Database.PostgreSQL.PQTypes.Internal.Error.Code+                     , Database.PostgreSQL.PQTypes.Internal.Fold                      , Database.PostgreSQL.PQTypes.Internal.Composite                      , Database.PostgreSQL.PQTypes.Internal.Utils                      , Database.PostgreSQL.PQTypes.Internal.Connection                      , Database.PostgreSQL.PQTypes.Internal.Exception                      , Database.PostgreSQL.PQTypes.Internal.Monad+                     , Database.PostgreSQL.PQTypes.Internal.Notification                      , Database.PostgreSQL.PQTypes.Internal.QueryResult                      , Database.PostgreSQL.PQTypes.Internal.Query                      , Database.PostgreSQL.PQTypes.Internal.State@@ -159,7 +162,7 @@                      , monad-control >= 0.3                      , lifted-base >= 0.2                      , mtl >= 2.1-                     , QuickCheck >= 2.5 && < 2.7+                     , QuickCheck >= 2.5                      , HUnit >= 1.2                      , test-framework >= 0.8                      , test-framework-hunit >= 0.3
libpqtypes/src/handler.c view
@@ -116,7 +116,10 @@ 	/* text passed as bytea to avoid double copy with ByteStringS 	 * and silent truncation of strings containting NULL characters */ 	{34, "pg_catalog", "btext", -1, TEXTOID, 1009, pqt_put_bytea,-		pqt_get_bytea, __HANDLER_DEFAULTS__}+                pqt_get_bytea, __HANDLER_DEFAULTS__},++	{35, "pg_catalog", "xml", -1, XMLOID, 143, pqt_put_bytea,+		pqt_get_bytea, __HANDLER_DEFAULTS__}, /* supports ptr */ };  static int
libpqtypes/src/libpqtypes-int.h view
@@ -257,6 +257,7 @@ #define ZPBITOID         1560 /* not supported yet */ #define VARBITOID        1562 /* not supported yet */ #define BYTEAOID           17+#define XMLOID            142 /* date and time types */ #define DATEOID          1082 #define TIMEOID          1083
src/Database/PostgreSQL/PQTypes.hs view
@@ -40,11 +40,11 @@   , module Database.PostgreSQL.PQTypes.Binary   , module Database.PostgreSQL.PQTypes.Class   , module Database.PostgreSQL.PQTypes.Composite-  , module Database.PostgreSQL.PQTypes.Fold   , module Database.PostgreSQL.PQTypes.Format   , module Database.PostgreSQL.PQTypes.FromRow   , module Database.PostgreSQL.PQTypes.FromSQL   , module Database.PostgreSQL.PQTypes.Interval+  , module Database.PostgreSQL.PQTypes.Notification   , module Database.PostgreSQL.PQTypes.Single   , module Database.PostgreSQL.PQTypes.SQL   , module Database.PostgreSQL.PQTypes.SQL.Class@@ -54,6 +54,7 @@   , module Database.PostgreSQL.PQTypes.Transaction   , module Database.PostgreSQL.PQTypes.Transaction.Settings   , module Database.PostgreSQL.PQTypes.Utils+  , module Database.PostgreSQL.PQTypes.XML   ) where  import Database.PostgreSQL.PQTypes.Internal.Connection@@ -68,11 +69,11 @@ import Database.PostgreSQL.PQTypes.Class import Database.PostgreSQL.PQTypes.Class.Instances () import Database.PostgreSQL.PQTypes.Composite-import Database.PostgreSQL.PQTypes.Fold import Database.PostgreSQL.PQTypes.Format import Database.PostgreSQL.PQTypes.FromRow import Database.PostgreSQL.PQTypes.FromSQL import Database.PostgreSQL.PQTypes.Interval+import Database.PostgreSQL.PQTypes.Notification import Database.PostgreSQL.PQTypes.Single import Database.PostgreSQL.PQTypes.SQL import Database.PostgreSQL.PQTypes.SQL.Class@@ -82,3 +83,4 @@ import Database.PostgreSQL.PQTypes.Transaction import Database.PostgreSQL.PQTypes.Transaction.Settings import Database.PostgreSQL.PQTypes.Utils+import Database.PostgreSQL.PQTypes.XML
src/Database/PostgreSQL/PQTypes/Class.hs view
@@ -6,6 +6,7 @@  import Database.PostgreSQL.PQTypes.FromRow import Database.PostgreSQL.PQTypes.Internal.Connection+import Database.PostgreSQL.PQTypes.Internal.Notification import Database.PostgreSQL.PQTypes.Internal.QueryResult import Database.PostgreSQL.PQTypes.Transaction.Settings import Database.PostgreSQL.PQTypes.SQL.Class@@ -38,6 +39,23 @@   foldlM :: FromRow row => (acc -> row -> m acc) -> acc -> m acc   -- | Fold the result set of rows from right to left.   foldrM :: FromRow row => (row -> acc -> m acc) -> acc -> m acc++  -- | Attempt to receive a notification from the server. This+  -- function waits until a notification arrives or specified+  -- number of microseconds has passed. If a negative number+  -- of microseconds is passed as an argument, it will wait+  -- indefinitely. In addition, there are a couple of things+  -- to be aware of:+  --+  -- * A lock on the underlying database connection is acquired+  -- for the duration of the function.+  --+  -- * Notifications can be received only between transactions+  -- (see <http://www.postgresql.org/docs/current/static/sql-notify.html>+  -- for further info), therefore calling this function within+  -- a transaction block will return 'Just' only if notifications+  -- were received before the transaction began.+  getNotification :: Int -> m (Maybe Notification)    -- | Execute supplied monadic action with new connection   -- using current connection source and transaction settings.
src/Database/PostgreSQL/PQTypes/Class/Instances.hs view
@@ -31,6 +31,7 @@     either (return . Left) (\k -> runErrorT $ f k row) acc) . Right   foldrM f = ErrorT . foldrM (\row acc ->     either (return . Left) (\k -> runErrorT $ f row k) acc) . Right+  getNotification = lift . getNotification   withNewConnection = mapErrorT withNewConnection  instance MonadDB m => MonadDB (IdentityT m) where@@ -43,6 +44,7 @@   setTransactionSettings = lift . setTransactionSettings   foldlM f = IdentityT . foldlM (\acc row -> runIdentityT $ f acc row)   foldrM f = IdentityT . foldrM (\row acc -> runIdentityT $ f row acc)+  getNotification = lift . getNotification   withNewConnection = mapIdentityT withNewConnection  instance MonadDB m => MonadDB (ListT m) where@@ -57,6 +59,7 @@     concat <$> mapM (\k -> runListT $ f k row) acc) . return   foldrM f = ListT . foldrM (\row acc ->     concat <$> mapM (\k -> runListT $ f row k) acc) . return+  getNotification = lift . getNotification   withNewConnection = mapListT withNewConnection  instance MonadDB m => MonadDB (MaybeT m) where@@ -71,6 +74,7 @@     maybe (return Nothing) (\k -> runMaybeT $ f k row) acc) . Just   foldrM f = MaybeT . foldrM (\row acc ->     maybe (return Nothing) (\k -> runMaybeT $ f row k) acc) . Just+  getNotification = lift . getNotification   withNewConnection = mapMaybeT withNewConnection  instance (Monoid w, MonadDB m) => MonadDB (L.RWST r w s m) where@@ -87,6 +91,7 @@   foldrM f acc = L.RWST $ \r s -> foldrM (\row ~(acc', s', w) -> do     ~(a, s'', w') <- L.runRWST (f row acc') r s'     return (a, s'', w `mappend` w')) (acc, s, mempty)+  getNotification = lift . getNotification   withNewConnection = L.mapRWST withNewConnection  instance (Monoid w, MonadDB m) => MonadDB (S.RWST r w s m) where@@ -103,6 +108,7 @@   foldrM f acc = S.RWST $ \r s -> foldrM (\row (acc', s', w) -> do     (a, s'', w') <- S.runRWST (f row acc') r s'     return (a, s'', w `mappend` w')) (acc, s, mempty)+  getNotification = lift . getNotification   withNewConnection = S.mapRWST withNewConnection  instance MonadDB m => MonadDB (ReaderT r m) where@@ -117,6 +123,7 @@     runReaderT (f acc' row) r) acc   foldrM f acc = ReaderT $ \r -> foldrM (\row acc' ->     runReaderT (f row acc') r) acc+  getNotification = lift . getNotification   withNewConnection = mapReaderT withNewConnection  instance MonadDB m => MonadDB (L.StateT s m) where@@ -131,6 +138,7 @@     L.runStateT (f acc' row) s') (acc, s)   foldrM f acc = L.StateT $ \s -> foldrM (\row ~(acc', s') ->     L.runStateT (f row acc') s') (acc, s)+  getNotification = lift . getNotification   withNewConnection = L.mapStateT withNewConnection  instance MonadDB m => MonadDB (S.StateT s m) where@@ -145,6 +153,7 @@     S.runStateT (f acc' row) s') (acc, s)   foldrM f acc = S.StateT $ \s -> foldrM (\row (acc', s') ->     S.runStateT (f row acc') s') (acc, s)+  getNotification = lift . getNotification   withNewConnection = S.mapStateT withNewConnection  instance (Monoid w, MonadDB m) => MonadDB (L.WriterT w m) where@@ -161,6 +170,7 @@   foldrM f acc = L.WriterT $ foldrM (\ row ~(acc', w) -> do     ~(r, w') <- L.runWriterT $ f row acc'     return (r, w `mappend` w')) (acc, mempty)+  getNotification = lift . getNotification   withNewConnection = L.mapWriterT withNewConnection  instance (Monoid w, MonadDB m) => MonadDB (S.WriterT w m) where@@ -177,4 +187,5 @@   foldrM f acc = S.WriterT $ foldrM (\ row (acc', w) -> do     (r, w') <- S.runWriterT $ f row acc'     return (r, w `mappend` w')) (acc, mempty)+  getNotification = lift . getNotification   withNewConnection = S.mapWriterT withNewConnection
src/Database/PostgreSQL/PQTypes/Class/Instances/Overlapping.hs view
@@ -32,6 +32,7 @@     foldrM f acc = controlT $ \run ->       run (return acc) >>= foldrM (\row acc' ->         run $ restoreT (return acc') >>= f row)+    getNotification = lift . getNotification     withNewConnection m = controlT $ \run ->       withNewConnection (run m) 
− src/Database/PostgreSQL/PQTypes/Fold.hs
@@ -1,85 +0,0 @@-{-# LANGUAGE BangPatterns, FlexibleContexts, Rank2Types, ScopedTypeVariables #-}-module Database.PostgreSQL.PQTypes.Fold (-    foldLeftM-  , foldRightM-  ) where--import Control.Monad-import Control.Monad.Base-import Foreign.ForeignPtr.Safe-import Foreign.C.Types-import qualified Control.Exception as E--import Database.PostgreSQL.PQTypes.Class-import Database.PostgreSQL.PQTypes.Format-import Database.PostgreSQL.PQTypes.FromRow-import Database.PostgreSQL.PQTypes.Internal.C.Interface-import Database.PostgreSQL.PQTypes.Internal.C.Types-import Database.PostgreSQL.PQTypes.Internal.Exception-import Database.PostgreSQL.PQTypes.Internal.Error-import Database.PostgreSQL.PQTypes.Internal.QueryResult-import Database.PostgreSQL.PQTypes.Internal.Utils-import Database.PostgreSQL.PQTypes.SQL.Class---- | Fold the result set of rows from left to right.-foldLeftM :: forall m row acc. (MonadBase IO m, MonadDB m, FromRow row)-          => (acc -> row -> m acc) -> acc -> m acc-foldLeftM f initAcc = withQueryResult $ \(_::row) ctx fres ferr ffmt ->-  liftBase (withForeignPtr fres c_PQntuples)-    >>= worker ctx fres ferr ffmt initAcc 0-  where-    worker ctx fres ferr ffmt acc !i !n-      | i == n    = return acc-      | otherwise = do-        obj <- liftBase $-          withForeignPtr fres $ \res ->-          withForeignPtr ferr $ \err ->-          withForeignPtr ffmt $ \fmt ->-            E.handle (rethrowWithContext ctx) (fromRow res err i fmt)-        acc' <- f acc obj-        worker ctx fres ferr ffmt acc' (i+1) n---- | Fold the result set of rows from right to left.-foldRightM :: forall m row acc. (MonadBase IO m, MonadDB m, FromRow row)-           => (row -> acc -> m acc) -> acc -> m acc-foldRightM f initAcc = withQueryResult $ \(_::row) ctx fres ferr ffmt ->-  liftBase (withForeignPtr fres c_PQntuples)-    >>= worker ctx fres ferr ffmt initAcc (-1) . pred-  where-    worker ctx fres ferr ffmt acc !n !i-      | i == n    = return acc-      | otherwise = do-        obj <- liftBase $-          withForeignPtr fres $ \res ->-          withForeignPtr ferr $ \err ->-          withForeignPtr ffmt $ \fmt ->-            E.handle (rethrowWithContext ctx) (fromRow res err i fmt)-        acc' <- f obj acc-        worker ctx fres ferr ffmt acc' n (i-1)---------------------------------------------- | Helper for abstracting away shared elements of both folds.-withQueryResult :: forall m row r. (MonadBase IO m, MonadDB m, FromRow row)-                => (forall sql. IsSQL sql => row -> sql -> ForeignPtr PGresult -> ForeignPtr PGerror -> ForeignPtr CChar -> m r)-                -> m r-withQueryResult f = do-  mres <- getQueryResult-  SomeSQL ctx <- getLastQuery-  case mres of-    Nothing -> liftBase . rethrowWithContext ctx . E.toException . HPQTypesError-      $ "withQueryResult: no query result"-    Just (QueryResult res) -> do-      liftBase $ do-        rowlen <- fromIntegral `liftM` withForeignPtr res c_PQnfields-        let expected = pqVariables (undefined::row)-        when (rowlen /= expected) $-          E.throwIO DBException {-            dbeQueryContext = ctx-          , dbeError = RowLengthMismatch expected rowlen-          }-      fmt <- liftBase . bsToCString $ pqFormat (undefined::row)-      err <- liftBase mallocForeignPtr-      acc <- f (undefined::row) ctx res err fmt-      clearQueryResult-      return acc
src/Database/PostgreSQL/PQTypes/Internal/C/Interface.hs view
@@ -2,9 +2,12 @@ -- | Exports a set of FFI-imported libpq/libpqtypes functions. module Database.PostgreSQL.PQTypes.Internal.C.Interface (   -- * libpq imports-    c_PQstatus+    c_PQfreemem+  , c_PQstatus   , c_PQerrorMessage   , c_PQsetClientEncoding+  , c_PQsocket+  , c_PQconsumeInput   , c_PQresultStatus   , c_PQresultErrorField   , c_PQresultErrorMessage@@ -31,12 +34,16 @@ import Foreign.ForeignPtr import Foreign.Ptr import Foreign.Storable+import System.Posix.Types import qualified Control.Exception as E  import Database.PostgreSQL.PQTypes.Internal.C.Types  -- libpq imports +foreign import ccall unsafe "PQfreemem"+  c_PQfreemem :: Ptr a -> IO ()+ foreign import ccall unsafe "PQstatus"   c_PQstatus :: Ptr PGconn -> IO ConnStatusType @@ -45,6 +52,12 @@  foreign import ccall unsafe "PQsetClientEncoding"   c_PQsetClientEncoding :: Ptr PGconn -> CString -> IO CInt++foreign import ccall unsafe "PQsocket"+  c_PQsocket :: Ptr PGconn -> IO Fd++foreign import ccall unsafe "PQconsumeInput"+  c_PQconsumeInput :: Ptr PGconn -> IO CInt  foreign import ccall unsafe "PQresultStatus"   c_PQresultStatus :: Ptr PGresult -> IO ExecStatusType
src/Database/PostgreSQL/PQTypes/Internal/Connection.hs view
@@ -1,7 +1,9 @@-{-# LANGUAGE FlexibleContexts, Rank2Types, RecordWildCards #-}+{-# LANGUAGE FlexibleContexts, RankNTypes, RecordWildCards+  , TupleSections #-} module Database.PostgreSQL.PQTypes.Internal.Connection (     Connection(..)   , ConnectionData(..)+  , withConnectionData   , ConnectionStats(..)   , ConnectionSettings(..)   , defaultSettings@@ -13,6 +15,7 @@   ) where  import Control.Applicative+import Control.Arrow import Control.Concurrent.MVar import Control.Monad import Control.Monad.Base@@ -34,6 +37,7 @@ import Database.PostgreSQL.PQTypes.Internal.Exception import Database.PostgreSQL.PQTypes.Internal.Utils import Database.PostgreSQL.PQTypes.SQL+import Database.PostgreSQL.PQTypes.SQL.Class  data ConnectionSettings = ConnectionSettings { -- | Connection info string.@@ -90,6 +94,15 @@ newtype Connection = Connection {   unConnection :: MVar (Maybe ConnectionData) }++withConnectionData :: Connection+                   -> String+                   -> (ConnectionData -> IO (ConnectionData, r))+                   -> IO r+withConnectionData (Connection mvc) fname f =+  modifyMVar mvc $ \mc -> case mc of+    Nothing -> E.throwIO . HPQTypesError $ fname ++ ": no connection"+    Just cd -> first Just <$> f cd  -- | Database connection supplier. newtype ConnectionSource = ConnectionSource {
+ src/Database/PostgreSQL/PQTypes/Internal/Fold.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE BangPatterns, FlexibleContexts, Rank2Types, ScopedTypeVariables #-}+module Database.PostgreSQL.PQTypes.Internal.Fold (+    foldLeftM+  , foldRightM+  ) where++import Control.Monad+import Control.Monad.Base+import Foreign.ForeignPtr.Safe+import Foreign.C.Types+import qualified Control.Exception as E++import Database.PostgreSQL.PQTypes.Class+import Database.PostgreSQL.PQTypes.Format+import Database.PostgreSQL.PQTypes.FromRow+import Database.PostgreSQL.PQTypes.Internal.C.Interface+import Database.PostgreSQL.PQTypes.Internal.C.Types+import Database.PostgreSQL.PQTypes.Internal.Exception+import Database.PostgreSQL.PQTypes.Internal.Error+import Database.PostgreSQL.PQTypes.Internal.QueryResult+import Database.PostgreSQL.PQTypes.Internal.Utils+import Database.PostgreSQL.PQTypes.SQL.Class++-- | Fold the result set of rows from left to right.+foldLeftM :: forall m row acc. (MonadBase IO m, MonadDB m, FromRow row)+          => (acc -> row -> m acc) -> acc -> m acc+foldLeftM f initAcc = withQueryResult $ \(_::row) ctx fres ferr ffmt ->+  liftBase (withForeignPtr fres c_PQntuples)+    >>= worker ctx fres ferr ffmt initAcc 0+  where+    worker ctx fres ferr ffmt acc !i !n+      | i == n    = return acc+      | otherwise = do+        obj <- liftBase $+          withForeignPtr fres $ \res ->+          withForeignPtr ferr $ \err ->+          withForeignPtr ffmt $ \fmt ->+            E.handle (rethrowWithContext ctx) (fromRow res err i fmt)+        acc' <- f acc obj+        worker ctx fres ferr ffmt acc' (i+1) n++-- | Fold the result set of rows from right to left.+foldRightM :: forall m row acc. (MonadBase IO m, MonadDB m, FromRow row)+           => (row -> acc -> m acc) -> acc -> m acc+foldRightM f initAcc = withQueryResult $ \(_::row) ctx fres ferr ffmt ->+  liftBase (withForeignPtr fres c_PQntuples)+    >>= worker ctx fres ferr ffmt initAcc (-1) . pred+  where+    worker ctx fres ferr ffmt acc !n !i+      | i == n    = return acc+      | otherwise = do+        obj <- liftBase $+          withForeignPtr fres $ \res ->+          withForeignPtr ferr $ \err ->+          withForeignPtr ffmt $ \fmt ->+            E.handle (rethrowWithContext ctx) (fromRow res err i fmt)+        acc' <- f obj acc+        worker ctx fres ferr ffmt acc' n (i-1)++----------------------------------------++-- | Helper for abstracting away shared elements of both folds.+withQueryResult :: forall m row r. (MonadBase IO m, MonadDB m, FromRow row)+                => (forall sql. IsSQL sql => row -> sql -> ForeignPtr PGresult -> ForeignPtr PGerror -> ForeignPtr CChar -> m r)+                -> m r+withQueryResult f = do+  mres <- getQueryResult+  SomeSQL ctx <- getLastQuery+  case mres of+    Nothing -> liftBase . rethrowWithContext ctx . E.toException . HPQTypesError+      $ "withQueryResult: no query result"+    Just (QueryResult res) -> do+      liftBase $ do+        rowlen <- fromIntegral `liftM` withForeignPtr res c_PQnfields+        let expected = pqVariables (undefined::row)+        when (rowlen /= expected) $+          E.throwIO DBException {+            dbeQueryContext = ctx+          , dbeError = RowLengthMismatch expected rowlen+          }+      fmt <- liftBase . bsToCString $ pqFormat (undefined::row)+      err <- liftBase mallocForeignPtr+      acc <- f (undefined::row) ctx res err fmt+      clearQueryResult+      return acc
src/Database/PostgreSQL/PQTypes/Internal/Monad.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving-  , MultiParamTypeClasses, ScopedTypeVariables, TypeFamilies-  , UndecidableInstances, CPP #-}+  , MultiParamTypeClasses, ScopedTypeVariables, TupleSections+  , TypeFamilies, UndecidableInstances, CPP #-} module Database.PostgreSQL.PQTypes.Internal.Monad (     DBT(..)   , runDBT@@ -21,10 +21,11 @@ import qualified Control.Monad.Trans.State as S  import Database.PostgreSQL.PQTypes.Class-import Database.PostgreSQL.PQTypes.Fold import Database.PostgreSQL.PQTypes.Internal.Connection import Database.PostgreSQL.PQTypes.Internal.Error import Database.PostgreSQL.PQTypes.Internal.Exception+import Database.PostgreSQL.PQTypes.Internal.Fold+import Database.PostgreSQL.PQTypes.Internal.Notification import Database.PostgreSQL.PQTypes.Internal.Query import Database.PostgreSQL.PQTypes.Internal.State import Database.PostgreSQL.PQTypes.SQL@@ -63,7 +64,7 @@ ----------------------------------------  instance (MonadBase IO m, MonadMask m) => MonadDB (DBT m) where-  runQuery = runSQLQuery DBT+  runQuery sql = DBT . StateT $ liftBase . runQueryIO sql   getLastQuery = DBT . gets $ dbLastQuery    getConnectionStats = do@@ -80,6 +81,9 @@    foldlM = foldLeftM   foldrM = foldRightM++  getNotification time = DBT . StateT $ \st -> (, st)+    <$> liftBase (getNotificationIO st time)    withNewConnection m = DBT . StateT $ \st -> do     let cs = dbConnectionSource st
+ src/Database/PostgreSQL/PQTypes/Internal/Notification.hsc view
@@ -0,0 +1,111 @@+{-# LANGUAGE DeriveDataTypeable, ForeignFunctionInterface, RecordWildCards+  , TupleSections #-}+module Database.PostgreSQL.PQTypes.Internal.Notification (+    Channel(..)+  , Notification(..)+  , getNotificationIO+  ) where++import Control.Concurrent+import Control.Monad+import Control.Monad.Fix+import Data.String+import Data.Typeable+import Foreign.Ptr+import Foreign.Storable+import System.Posix.Types+import System.Timeout+import qualified Control.Exception as E+import qualified Data.ByteString.Char8 as BS++import Database.PostgreSQL.PQTypes.Internal.C.Interface+import Database.PostgreSQL.PQTypes.Internal.C.Types+import Database.PostgreSQL.PQTypes.Internal.Connection+import Database.PostgreSQL.PQTypes.Internal.Exception+import Database.PostgreSQL.PQTypes.Internal.State+import Database.PostgreSQL.PQTypes.Internal.Utils+import Database.PostgreSQL.PQTypes.SQL.Class+import Database.PostgreSQL.PQTypes.SQL.Raw++#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__);}, y__)++#include <libpq-fe.h>++foreign import ccall unsafe "PQnotifies"+  c_PQnotifies :: Ptr PGconn -> IO (Ptr Notification)++----------------------------------------++-- | Representation of notification channel.+newtype Channel = Channel (RawSQL ())+  deriving (Eq, Ord, Typeable)++instance IsString Channel where+  fromString = Channel . fromString++instance Show Channel where+  showsPrec n (Channel chan) = ("Channel " ++) . showsPrec n (unRawSQL chan)++----------------------------------------++-- | Representation of a notification sent by PostgreSQL.+data Notification = Notification {+-- | Process ID of notifying server.+  ntPID     :: !CPid+  -- | Notification channel name.+, ntChannel :: !Channel+  -- | Notification payload string.+, ntPayload :: !BS.ByteString+} deriving (Eq, Ord, Show, Typeable)++instance Storable Notification where+  sizeOf _ = #{size PGnotify}+  alignment _ = #{alignment PGnotify}+  peek ptr = do+    ntPID <- return . CPid+      =<< #{peek PGnotify, be_pid} ptr+    ntChannel <- fmap (Channel . flip rawSQL ()) . BS.packCString+      =<< #{peek PGnotify, relname} ptr+    ntPayload <- BS.packCString+      =<< #{peek PGnotify, extra} ptr+    return Notification{..}+  poke _ _ = error "Storable Notification: poke is not supposed to be used"++----------------------------------------++-- | Low-level function that waits for a notification for a given+-- number of microseconds (it uses 'timeout' function internally).+getNotificationIO :: DBState -> Int -> IO (Maybe Notification)+getNotificationIO st n = do+  let mvconn = dbConnection st+  SomeSQL ctx <- return $ dbLastQuery st+  E.handle (rethrowWithContext ctx)+    . timeout n+    . withConnectionData mvconn fname $ \cd -> fix $ \loop -> do+      let conn = cdPtr cd+      mmsg <- tryGet conn+      case mmsg of+        Just msg -> return (cd, msg)+        Nothing -> do+          fd <- c_PQsocket conn+          if fd == -1+            then hpqTypesError $ fname ++ ": invalid file descriptor"+            else do+              threadWaitRead fd+              res <- c_PQconsumeInput conn+              when (res /= 1) $ do+                throwLibPQError conn fname+              loop+  where+    fname :: String+    fname = "getNotificationIO"++    tryGet :: Ptr PGconn -> IO (Maybe Notification)+    tryGet connPtr = E.mask_ $ do+      ptr <- c_PQnotifies connPtr+      if ptr /= nullPtr+        then do+          msg <- peek ptr+          c_PQfreemem ptr+          return $ Just msg+        else return Nothing
src/Database/PostgreSQL/PQTypes/Internal/Query.hs view
@@ -1,12 +1,9 @@ {-# LANGUAGE FlexibleContexts, RecordWildCards #-} module Database.PostgreSQL.PQTypes.Internal.Query (-    runSQLQuery+    runQueryIO   ) where  import Control.Applicative-import Control.Concurrent.MVar-import Control.Monad.Base-import Control.Monad.Trans.State import Foreign.ForeignPtr import Foreign.Ptr import qualified Control.Exception as E@@ -22,48 +19,41 @@ import Database.PostgreSQL.PQTypes.SQL.Class import Database.PostgreSQL.PQTypes.Internal.Utils --- | Run SQL query.-runSQLQuery :: (IsSQL sql, MonadBase IO m)-            => (StateT DBState m Int -> r) -> sql -> r-runSQLQuery dbT sql = dbT $ do-  mvconn <- gets (unConnection . dbConnection)-  (affected, res) <- liftBase . modifyMVar mvconn $ \mconn -> case mconn of-    Nothing -> E.throwIO DBException {-      dbeQueryContext = sql-    , dbeError = HPQTypesError "runQuery: no connection"-    }-    Just cd@ConnectionData{..} -> E.handle (rethrowWithContext sql) $ do-      (paramCount, res) <- withSQL sql (withPGparam cdPtr) $ \param query -> (,)-        <$> (fromIntegral <$> c_PQparamCount param)-        <*> c_PQparamExec cdPtr nullPtr param query c_RESULT_BINARY-      affected <- withForeignPtr res $ verifyResult cdPtr-      stats' <- case affected of-        Left _ -> return cdStats {+-- | Low-level function for running SQL query.+runQueryIO :: IsSQL sql => sql -> DBState -> IO (Int, DBState)+runQueryIO sql st = E.handle (rethrowWithContext sql) $ do+  (affected, res) <- withConnectionData (dbConnection st) "runQueryIO" $ \cd -> do+    let ConnectionData{..} = cd+    (paramCount, res) <- withSQL sql (withPGparam cdPtr) $ \param query -> (,)+      <$> (fromIntegral <$> c_PQparamCount param)+      <*> c_PQparamExec cdPtr nullPtr param query c_RESULT_BINARY+    affected <- withForeignPtr res $ verifyResult cdPtr+    stats' <- case affected of+      Left _ -> return cdStats {+        statsQueries = statsQueries cdStats + 1+      , statsParams  = statsParams cdStats + paramCount+      }+      Right rows -> do+        columns <- fromIntegral <$> withForeignPtr res c_PQnfields+        return ConnectionStats {           statsQueries = statsQueries cdStats + 1+        , statsRows    = statsRows cdStats + rows+        , statsValues  = statsValues cdStats + (rows * columns)         , statsParams  = statsParams cdStats + paramCount         }-        Right rows -> do-          columns <- fromIntegral <$> withForeignPtr res c_PQnfields-          return ConnectionStats {-            statsQueries = statsQueries cdStats + 1-          , statsRows    = statsRows cdStats + rows-          , statsValues  = statsValues cdStats + (rows * columns)-          , statsParams  = statsParams cdStats + paramCount-          }-      -- Force evaluation of modified stats to squash space leak.-      stats' `seq` return (Just cd { cdStats = stats' }, (either id id affected, res))-  modify $ \st -> st {+    -- Force evaluation of modified stats to squash space leak.+    stats' `seq` return (cd { cdStats = stats' }, (either id id affected, res))+  return (affected, st {     dbLastQuery = someSQL sql   , dbQueryResult = Just $ QueryResult res-  }-  return affected+  })   where     verifyResult :: Ptr PGconn -> Ptr PGresult -> IO (Either Int Int)     verifyResult conn res = do       -- works even if res is NULL-      st <- c_PQresultStatus res-      case st of-        _ | st == c_PGRES_COMMAND_OK -> do+      rst <- c_PQresultStatus res+      case rst of+        _ | rst == c_PGRES_COMMAND_OK -> do           sn <- c_PQcmdTuples res >>= BS.packCString           case BS.readInt sn of             Nothing@@ -72,9 +62,9 @@             Just (n, rest)               | rest /= BS.empty -> throwParseError sn               | otherwise        -> return . Left $ n-        _ | st == c_PGRES_TUPLES_OK    -> Right . fromIntegral <$> c_PQntuples res-        _ | st == c_PGRES_FATAL_ERROR  -> throwSQLError-        _ | st == c_PGRES_BAD_RESPONSE -> throwSQLError+        _ | rst == c_PGRES_TUPLES_OK    -> Right . fromIntegral <$> c_PQntuples res+        _ | rst == c_PGRES_FATAL_ERROR  -> throwSQLError+        _ | rst == c_PGRES_BAD_RESPONSE -> throwSQLError         _ | otherwise                  -> return . Left $ 0         where           throwSQLError = throwQueryError conn res
+ src/Database/PostgreSQL/PQTypes/Notification.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE OverloadedStrings #-}+module Database.PostgreSQL.PQTypes.Notification (+    Channel(..)+  , Notification(..)+  , listen+  , unlisten+  , unlistenAll+  , notify+  ) where++import Data.ByteString (ByteString)++import Data.Monoid.Space+import Database.PostgreSQL.PQTypes.Class+import Database.PostgreSQL.PQTypes.Internal.Notification+import Database.PostgreSQL.PQTypes.SQL.Raw+import Database.PostgreSQL.PQTypes.Utils++-- | Start listening for notifications on a given channel.+listen :: MonadDB m => Channel -> m ()+listen (Channel chan) = runQuery_ $ "LISTEN" <+> chan++-- | Stop listening for notifications on a given channel.+unlisten :: MonadDB m => Channel -> m ()+unlisten (Channel chan) = runQuery_ $ "UNLISTEN" <+> chan++-- | Cancel all listener registrations for the current session.+unlistenAll :: MonadDB m => m ()+unlistenAll = runSQL_ "UNLISTEN *"++-- | Generate a notification on a given channel.+notify :: MonadDB m => Channel -> ByteString -> m ()+notify (Channel chan) payload = runQuery_+  $ rawSQL "SELECT pg_notify($1, $2)" (unRawSQL chan, payload)
src/Database/PostgreSQL/PQTypes/Transaction.hs view
@@ -17,6 +17,8 @@ import Control.Monad.Catch import Data.String import Data.Typeable+import qualified Data.ByteString as BS+import System.IO.Unsafe  import Data.Monoid.Space import Data.Monoid.Utils@@ -82,11 +84,16 @@ withTransaction' ts m = mask $ exec 1   where     exec :: Integer -> (forall r. m r -> m r) -> m a-    exec !n restore = handleJust expred (const $ exec (succ n) restore) $ do-      begin' ts-      res <- restore m `onException` rollback' ts-      commit' ts-      return res+    exec !n restore = handleJust expred (\_ -> do+      unsafePerformIO $ do+        BS.appendFile "/home/unknown/transaction.txt" "."+        return $ return ()+      exec (succ n) restore+      ) $ do+        begin' ts+        res <- restore m `onException` rollback' ts+        commit' ts+        return res       where         expred :: DBException -> Maybe ()         expred DBException{..} = do
+ src/Database/PostgreSQL/PQTypes/XML.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE DeriveDataTypeable, TypeFamilies #-}+module Database.PostgreSQL.PQTypes.XML (+    XML(..)+  ) where++import Data.Text+import Data.Typeable+import qualified Data.ByteString.Char8 as BSC++import Database.PostgreSQL.PQTypes.Format+import Database.PostgreSQL.PQTypes.FromSQL+import Database.PostgreSQL.PQTypes.Internal.C.Types+import Database.PostgreSQL.PQTypes.ToSQL++-- | Representation of SQL XML types as 'Text'.  Users of hpqtypes may+-- want to add conversion instances for their favorite XML type around 'XML'.+newtype XML = XML { unXML :: Text }+  deriving (Eq, Ord, Read, Show, Typeable)++instance PQFormat XML where+  pqFormat _ = BSC.pack "%xml"++instance FromSQL XML where+  type PQBase XML = PGbytea+  fromSQL = fmap XML . fromSQL++instance ToSQL XML where+  type PQDest XML = PGbytea+  toSQL = toSQL . unXML
test/Main.hs view
@@ -1,10 +1,12 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE BangPatterns, DeriveDataTypeable, FlexibleContexts   , GeneralizedNewtypeDeriving, MultiParamTypeClasses, OverloadedStrings-  , ScopedTypeVariables, TypeFamilies, UndecidableInstances, CPP #-}+  , RecordWildCards, ScopedTypeVariables, TypeFamilies+  , UndecidableInstances, CPP #-} module Main where  import Control.Applicative+import Control.Concurrent.Lifted import Control.Monad import Control.Monad.Base import Control.Monad.Catch@@ -12,6 +14,7 @@ import Control.Monad.Trans.Control import Data.Char import Data.Int+import Data.Maybe import Data.Time import Data.Typeable import Data.Word@@ -22,6 +25,7 @@ import Test.Framework.Providers.HUnit import Test.HUnit hiding (Test, assertEqual) import Test.QuickCheck+import Test.QuickCheck.Compat import Test.QuickCheck.Gen import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BSC@@ -33,7 +37,7 @@ import Prelude.Instances () import Test.QuickCheck.Arbitrary.Instances () -type InnerTestEnv = StateT StdGen (DBT IO)+type InnerTestEnv = StateT QCGen (DBT IO)  newtype TestEnv a = TestEnv { unTestEnv :: InnerTestEnv a }   deriving (Applicative, Functor, Monad, MonadBase IO, MonadCatch, MonadDB, MonadMask, MonadThrow)@@ -41,9 +45,9 @@ instance MonadBaseControl IO TestEnv where #if MIN_VERSION_monad_control(1,0,0)   type StM TestEnv a = StM InnerTestEnv a-  liftBaseWith f = liftBaseWith $ \run ->-                         f $ run-  restoreM = restoreM+  liftBaseWith f = TestEnv $ liftBaseWith $ \run ->+                         f $ run . unTestEnv+  restoreM = TestEnv . restoreM #else   newtype StM TestEnv a = StTestEnv { unStTestEnv :: StM InnerTestEnv a }   liftBaseWith f = TestEnv $ liftBaseWith $ \run ->@@ -51,15 +55,15 @@   restoreM = TestEnv . restoreM . unStTestEnv #endif -withStdGen :: (StdGen -> r) -> TestEnv r-withStdGen f = do+withQCGen :: (QCGen -> r) -> TestEnv r+withQCGen f = do   gen <- TestEnv get   TestEnv . modify $ snd . next   return (f gen)  ---------------------------------------- -type TestData = (StdGen, ConnectionSource)+type TestData = (QCGen, ConnectionSource)  runTestEnv :: TestData -> TransactionSettings -> TestEnv a -> IO a runTestEnv (env, cs) ts m = runDBT cs ts $ evalStateT (unTestEnv m) env@@ -244,8 +248,11 @@ dts :: TransactionSettings dts = defaultTransactionSettings +tsNoTrans :: TransactionSettings+tsNoTrans = dts { tsAutoTransaction = False }+ randomValue :: Arbitrary t => Int -> TestEnv t-randomValue n = withStdGen $ \gen -> unGen arbitrary gen n+randomValue n = withQCGen $ \gen -> unGen arbitrary gen n  assertEqual :: (Show a, MonadBase IO m)             => String -> a -> a -> (a -> a -> Bool) -> m ()@@ -260,7 +267,7 @@ ----------------------------------------  autocommitTest :: TestData -> Test-autocommitTest td = testCase "Autocommit mode works" . runTestEnv td dts{tsAutoTransaction = False} $ do+autocommitTest td = testCase "Autocommit mode works" . runTestEnv td tsNoTrans $ do   let sint = Single (1::Int32)   runQuery_ $ rawSQL "INSERT INTO test1_ (a) VALUES ($1)" sint   withNewConnection $ do@@ -303,6 +310,31 @@   res2 <- fetchMany unSingle   assertEqual "Result of all queries is visible" res2 [int1, int2] (==) +notifyTest :: TestData -> Test+notifyTest td = testCase "Notifications work" . runTestEnv td tsNoTrans $ do+  listen chan+  forkNewConn $ notify chan payload+  mnt1 <- getNotification 100000+  liftBase $ assertBool "Notification received" (isJust mnt1)+  let Just nt1 = mnt1+  assertEqual "Channels are equal" chan (ntChannel nt1) (==)+  assertEqual "Payloads are equal" payload (ntPayload nt1) (==)++  unlisten chan+  forkNewConn $ notify chan payload+  mnt2 <- getNotification 100000+  assertEqual "No notification received after unlisten" Nothing mnt2 (==)++  listen chan+  unlistenAll+  forkNewConn $ notify chan payload+  mnt3 <- getNotification 100000+  assertEqual "No notification received after unlistenAll" Nothing mnt3 (==)+  where+    chan = "test_channel"+    payload = "test_payload"+    forkNewConn = void . fork . withNewConnection+ transactionTest :: TestData -> IsolationLevel -> Test transactionTest td lvl = testCase ("Auto transaction works by default with isolation level" <+> show lvl) . runTestEnv td dts{tsIsolationLevel = lvl} $ do   let sint = Single (5::Int32)@@ -330,6 +362,18 @@   v' <- fetchOne unSingle   assertEqual "Value doesn't change after getting through database" v v' eq +xmlTest :: TestData -> Test+xmlTest td  = testCase "Put and get XML value works" . runTestEnv td dts $ do+  runSQL_ $ "SET CLIENT_ENCODING TO 'UTF8'"+  let v = XML "some<tag>stringå</tag>"+  runSQL_ $ "SELECT XML 'some<tag>stringå</tag>'"+  v' <- fetchOne unSingle+  assertEqual "XML value correct" v v' (==)+  runSQL_ $ "SELECT" <?> v+  v'' <- fetchOne unSingle+  assertEqual "XML value correct" v v'' (==)+  runSQL_ $ "SET CLIENT_ENCODING TO 'latin-1'"+ rowTest :: forall row. (Arbitrary row, Eq row, Show row, ToRow row, FromRow row)         => TestData -> row -> Test rowTest td r = testCase ("Putting row of length" <+> show (pqVariables r) <+> "through database works") . runTestEnv td dts . runTimes 100 $ do@@ -350,8 +394,10 @@ tests :: TestData -> [Test] tests td = [     autocommitTest td+  , xmlTest td   , readOnlyTest td   , savepointTest td+  , notifyTest td   ----------------------------------------   , transactionTest td ReadCommitted   , transactionTest td RepeatableRead@@ -495,7 +541,7 @@    createStructures connSource   connPool <- poolSource (connSettings { csComposites = ["simple_", "nested_"] }) 1 30 16-  gen <- getStdGen+  gen <- newQCGen   putStrLn $ "PRNG:" <+> show gen    finally (defaultMainWithArgs (tests (gen, connPool)) $ tail args) $ do