@@ -1,5 +1,5 @@
1−{-# LANGUAGE BangPatterns, DeriveDataTypeable, OverloadedStrings #-}
2−{-# LANGUAGE DoAndIfThenElse, RecordWildCards, NamedFieldPuns #-}
1+{-# LANGUAGE DeriveDataTypeable, RecordWildCards, NamedFieldPuns #-}
3------------------------------------------------------------------------------
3------------------------------------------------------------------------------
5-- Module: Database.PostgreSQL.Simple
5-- Module: Database.PostgreSQL.Simple
@@ -104,30 +104,39 @@ module Database.PostgreSQL.Simple
104 , formatQuery
104 , formatQuery
107+import Blaze.ByteString.Builder
107−import Blaze.ByteString.Builder (Builder, fromByteString, toByteString)
108+ ( Builder, fromByteString, toByteString )
108−import Blaze.ByteString.Builder.Char8 (fromChar)
109+import Blaze.ByteString.Builder.Char8 (fromChar)
109−import Control.Applicative ((<$>), pure)
110+import Control.Applicative ((<$>), pure)
110−import Control.Concurrent.MVar
111+import Control.Concurrent.MVar
112+import Control.Exception
111−import Control.Exception (Exception, bracket, onException, throw, throwIO, finally)
113+ ( Exception, bracket, onException, throw, throwIO, finally )
112−import Control.Monad (foldM)
114+import Control.Monad (foldM)
113−import Control.Monad.Fix (fix)
115+import Control.Monad.Fix (fix)
114−import Data.ByteString (ByteString)
116+import Data.ByteString (ByteString)
115−import Data.Char(ord)
117+import Data.Char(ord)
116−import Data.Int (Int64)
118+import Data.Int (Int64)
117import qualified Data.IntMap as IntMap
119import qualified Data.IntMap as IntMap
118−import Data.List (intersperse)
120+import Data.List (intersperse)
119−import Data.Monoid (mappend, mconcat)
121+import Data.Monoid (mappend, mconcat)
120−import Data.Typeable (Typeable)
122+import Data.Typeable (Typeable)
121−import Database.PostgreSQL.Simple.BuiltinTypes (oid2builtin, builtin2typname)
123+import Database.PostgreSQL.Simple.BuiltinTypes
124+ ( oid2builtin, builtin2typname )
125+import Database.PostgreSQL.Simple.FromField (ResultError(..))
126+import Database.PostgreSQL.Simple.FromRow (FromRow(..))
127+import Database.PostgreSQL.Simple.Ok
122−import Database.PostgreSQL.Simple.Param (Action(..), inQuotes)
128+import Database.PostgreSQL.Simple.Param (Action(..), inQuotes)
123−import Database.PostgreSQL.Simple.QueryParams (QueryParams(..))
129+import Database.PostgreSQL.Simple.QueryParams (QueryParams(..))
124−import Database.PostgreSQL.Simple.Result (ResultError(..))
125−import Database.PostgreSQL.Simple.QueryResults (QueryResults(..))
126−import Database.PostgreSQL.Simple.Types (Binary(..), In(..), Only(..), Query(..))
130+import Database.PostgreSQL.Simple.Types
131+ ( Binary(..), In(..), Only(..), Query(..) )
127−import Database.PostgreSQL.Simple.Internal as Base
132+import Database.PostgreSQL.Simple.Internal as Base
128import qualified Database.PostgreSQL.LibPQ as PQ
133import qualified Database.PostgreSQL.LibPQ as PQ
129−import Text.Regex.PCRE.Light (compile, caseless, match)
134+import Text.Regex.PCRE.Light (compile, caseless, match)
130import qualified Data.ByteString.Char8 as B
135import qualified Data.ByteString.Char8 as B
136+import qualified Data.Vector as V
137+import Control.Exception
138+import Control.Monad.Trans.Reader
139+import Control.Monad.Trans.State.Strict
132-- | Exception thrown if a 'Query' could not be formatted correctly.
141-- | Exception thrown if a 'Query' could not be formatted correctly.
133-- This may occur if the number of \'@?@\' characters in the query
142-- This may occur if the number of \'@?@\' characters in the query
@@ -293,14 +302,14 @@ finishExecute conn q result = do
293-- using 'execute' instead of 'query').
302-- using 'execute' instead of 'query').
295-- * 'ResultError': result conversion failed.
304-- * 'ResultError': result conversion failed.
296−query :: (QueryParams q, QueryResults r)
305+query :: (QueryParams q, FromRow r)
297 => Connection -> Query -> q -> IO [r]
306 => Connection -> Query -> q -> IO [r]
298query conn template qs = do
307query conn template qs = do
299 result <- exec conn =<< formatQuery conn template qs
308 result <- exec conn =<< formatQuery conn template qs
300 finishQuery conn template result
309 finishQuery conn template result
302-- | A version of 'query' that does not perform query substitution.
311-- | A version of 'query' that does not perform query substitution.
303−query_ :: (QueryResults r) => Connection -> Query -> IO [r]
312+query_ :: (FromRow r) => Connection -> Query -> IO [r]
304query_ conn q@(Query que) = do
313query_ conn q@(Query que) = do
305 result <- exec conn que
314 result <- exec conn que
306 finishQuery conn q result
315 finishQuery conn q result
@@ -324,7 +333,7 @@ query_ conn q@(Query que) = do
325-- * 'ResultError': result conversion failed.
334-- * 'ResultError': result conversion failed.
327−fold :: ( QueryResults row, QueryParams params )
336+fold :: ( FromRow row, QueryParams params )
328 => Connection
337 => Connection
330 -> params
339 -> params
@@ -348,7 +357,7 @@ defaultFoldOptions = FoldOptions {
348 transactionMode = TransactionMode ReadCommitted ReadOnly
357 transactionMode = TransactionMode ReadCommitted ReadOnly
351−foldWithOptions :: ( QueryResults row, QueryParams params )
360+foldWithOptions :: ( FromRow row, QueryParams params )
352 => FoldOptions
361 => FoldOptions
353 -> Connection
362 -> Connection
@@ -361,7 +370,7 @@ foldWithOptions opts conn template qs a f = do
361 doFold opts conn template (Query q) a f
370 doFold opts conn template (Query q) a f
363-- | A version of 'fold' that does not perform query substitution.
372-- | A version of 'fold' that does not perform query substitution.
364−fold_ :: (QueryResults r) =>
373+fold_ :: (FromRow r) =>
365 Connection
374 Connection
366 -> Query -- ^ Query.
375 -> Query -- ^ Query.
367 -> a -- ^ Initial state for result consumer.
376 -> a -- ^ Initial state for result consumer.
@@ -370,7 +379,7 @@ fold_ :: (QueryResults r) =>
370fold_ = foldWithOptions_ defaultFoldOptions
379fold_ = foldWithOptions_ defaultFoldOptions
373−foldWithOptions_ :: (QueryResults r) =>
382+foldWithOptions_ :: (FromRow r) =>
374 FoldOptions
383 FoldOptions
375 -> Connection
384 -> Connection
376 -> Query -- ^ Query.
385 -> Query -- ^ Query.
@@ -380,7 +389,7 @@ foldWithOptions_ :: (QueryResults r) =>
380foldWithOptions_ opts conn query a f = doFold opts conn query query a f
389foldWithOptions_ opts conn query a f = doFold opts conn query query a f
383−doFold :: ( QueryResults row )
392+doFold :: ( FromRow row )
384 => FoldOptions
393 => FoldOptions
385 -> Connection
394 -> Connection
@@ -426,7 +435,7 @@ doFold FoldOptions{..} conn _template q a f = do
426 if null rs then return a else foldM f a rs >>= loop
435 if null rs then return a else foldM f a rs >>= loop
428-- | A version of 'fold' that does not transform a state value.
437-- | A version of 'fold' that does not transform a state value.
429−forEach :: (QueryParams q, QueryResults r) =>
438+forEach :: (QueryParams q, FromRow r) =>
430 Connection
439 Connection
431 -> Query -- ^ Query template.
440 -> Query -- ^ Query template.
432 -> q -- ^ Query parameters.
441 -> q -- ^ Query parameters.
@@ -436,7 +445,7 @@ forEach conn template qs = fold conn template qs () . const
436{-# INLINE forEach #-}
445{-# INLINE forEach #-}
438-- | A version of 'forEach' that does not perform query substitution.
447-- | A version of 'forEach' that does not perform query substitution.
439−forEach_ :: (QueryResults r) =>
448+forEach_ :: (FromRow r) =>
440 Connection
449 Connection
441 -> Query -- ^ Query template.
450 -> Query -- ^ Query template.
442 -> (r -> IO ()) -- ^ Result consumer.
451 -> (r -> IO ()) -- ^ Result consumer.
@@ -453,7 +462,7 @@ forM' lo hi m = loop hi []
454 loop (n-1) (a:as)
463 loop (n-1) (a:as)
456−finishQuery :: (QueryResults r) => Connection -> Query -> PQ.Result -> IO [r]
465+finishQuery :: (FromRow r) => Connection -> Query -> PQ.Result -> IO [r]
457finishQuery conn q result = do
466finishQuery conn q result = do
458 status <- PQ.resultStatus result
467 status <- PQ.resultStatus result
459 case status of
468 case status of
@@ -461,26 +470,53 @@ finishQuery conn q result = do
461 throwIO $ QueryError "query resulted in a command response" q
470 throwIO $ QueryError "query resulted in a command response" q
462 PQ.TuplesOk -> do
471 PQ.TuplesOk -> do
463 ncols <- PQ.nfields result
472 ncols <- PQ.nfields result
464− fields <- forM' 0 (ncols-1) $ \column -> do
473+ let unCol (PQ.Col x) = fromIntegral x
465− type_oid <- PQ.ftype result column
474+ typenames <- V.generateM (unCol ncols)
475+ (\(PQ.Col . fromIntegral -> col) -> do
466− typename <- getTypename conn type_oid
476+ getTypename conn =<< PQ.ftype result col)
468 nrows <- PQ.ntuples result
477 nrows <- PQ.ntuples result
478+ ncols <- PQ.nfields result
469 forM' 0 (nrows-1) $ \row -> do
479 forM' 0 (nrows-1) $ \row -> do
480+ let rw = Row row typenames result
481+ case runStateT (runReaderT (unRP fromRow) rw) 0 of
482+ Ok (val,col) | col == ncols -> return val
484+ vals <- forM' 0 (ncols-1) $ \c -> do
470− values <- forM' 0 (ncols-1) (\col -> PQ.getvalue result row col)
485+ v <- PQ.getvalue result row c
471− case convertResults fields values of
486+ return ( typenames V.! unCol c
472− Left err -> throwIO err
487+ , fmap ellipsis v )
473− Right a -> return a
488+ throw (ConversionFailed
474− PQ.CopyIn -> fail "FIXME: postgresql-simple does not currently support COPY IN"
489+ (show (unCol ncols) ++ " values: " ++ show vals)
475− PQ.CopyOut -> fail "FIXME: postgresql-simple does not currently support COPY OUT"
490+ (show (unCol col) ++ " slots in target type")
476− _ -> do
491+ "mismatch between number of columns to \
477− errormsg <- maybe "" id <$> PQ.resultErrorMessage result
492+ \convert and number in target type")
478− statusmsg <- PQ.resStatus status
493+ Errors [] -> throwIO $ ConversionFailed "" "" "unknown error"
479− state <- maybe "" id <$> PQ.resultErrorField result PQ.DiagSqlstate
494+ Errors [x] -> throwIO x
480− throwIO $ SqlError { sqlState = state
495+ Errors xs -> throwIO $ ManyErrors xs
481− , sqlNativeError = fromEnum status
496+
482− , sqlErrorMsg = B.concat [ "query: ", statusmsg
497+ellipsis :: ByteString -> ByteString
483− , ": ", errormsg ]}
498+ellipsis bs
499+ | B.length bs > 15 = B.take 10 bs `B.append` "[...]"
502+-- | Throw a 'ConversionFailed' exception, indicating a mismatch
503+-- between the number of columns in the 'Field' and row, and the
504+-- number in the collection to be converted to.
505+convertError :: [Field]
506+ -- ^ Descriptors of fields to be converted.
507+ -> [Maybe ByteString]
508+ -- ^ Contents of the row to be converted.
510+ -- ^ Number of columns expected for conversion. For
511+ -- instance, if converting to a 3-tuple, the number to
512+ -- provide here would be 3.
513+ -> Either SomeException a
514+convertError fs vs n = Left . SomeException $ ConversionFailed
515+ (show (length fs) ++ " values: " ++ show (zip (map typename fs)
516+ (map (fmap ellipsis) vs)))
517+ (show n ++ " slots in target type")
518+ "mismatch between number of columns to convert and number in target type"
485-- | Of the four isolation levels defined by the SQL standard,
521-- | Of the four isolation levels defined by the SQL standard,
486-- these are the three levels distinguished by PostgreSQL as of version 9.0.
522-- these are the three levels distinguished by PostgreSQL as of version 9.0.
@@ -759,7 +795,7 @@ fmtError msg q xs = throw FormatError {
759-- $result
795-- $result
761-- The 'query' and 'query_' functions return a list of values in the
797-- The 'query' and 'query_' functions return a list of values in the
762−-- 'QueryResults' typeclass. This class performs automatic extraction
798+-- 'FromRow' typeclass. This class performs automatic extraction
763-- and type conversion of rows from a query result.
799-- and type conversion of rows from a query result.
765-- Here is a simple example of how to extract results:
801-- Here is a simple example of how to extract results: