postgresql-typed 0.5.2 → 0.5.3.0
raw patch · 8 files changed
+331/−195 lines, 8 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Database.PostgreSQL.Typed: data PGError
- Database.PostgreSQL.Typed.Dynamic: instance Database.PostgreSQL.Typed.Dynamic.PGRep (Data.Time.LocalTime.TimeOfDay.TimeOfDay, Data.Time.LocalTime.TimeZone.TimeZone)
- Database.PostgreSQL.Typed.Dynamic: instance Database.PostgreSQL.Typed.Dynamic.PGRep Data.Time.Clock.Scale.DiffTime
- Database.PostgreSQL.Typed.Dynamic: instance Database.PostgreSQL.Typed.Dynamic.PGRep Data.Time.Clock.UTC.UTCTime
- Database.PostgreSQL.Typed.Dynamic: instance Database.PostgreSQL.Typed.Dynamic.PGRep Data.Time.LocalTime.LocalTime.LocalTime
- Database.PostgreSQL.Typed.Dynamic: instance Database.PostgreSQL.Typed.Dynamic.PGRep Data.Time.LocalTime.TimeOfDay.TimeOfDay
- Database.PostgreSQL.Typed.Protocol: [colBinary] :: PGColDescription -> !Bool
- Database.PostgreSQL.Typed.Protocol: [colModifier] :: PGColDescription -> !Int32
- Database.PostgreSQL.Typed.Protocol: [colName] :: PGColDescription -> ByteString
- Database.PostgreSQL.Typed.Protocol: [colNumber] :: PGColDescription -> !Int16
- Database.PostgreSQL.Typed.Protocol: [colSize] :: PGColDescription -> !Int16
- Database.PostgreSQL.Typed.Protocol: [colTable] :: PGColDescription -> !OID
- Database.PostgreSQL.Typed.Protocol: [colType] :: PGColDescription -> !OID
- Database.PostgreSQL.Typed.Protocol: data PGError
- Database.PostgreSQL.Typed.Protocol: pgServerVersion :: PGConnection -> Maybe ByteString
- Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGColumn "interval" Data.Time.Clock.Scale.DiffTime
- Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGColumn "time with time zone" (Data.Time.LocalTime.TimeOfDay.TimeOfDay, Data.Time.LocalTime.TimeZone.TimeZone)
- Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGColumn "time without time zone" Data.Time.LocalTime.TimeOfDay.TimeOfDay
- Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGColumn "timestamp with time zone" Data.Time.Clock.UTC.UTCTime
- Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGColumn "timestamp without time zone" Data.Time.LocalTime.LocalTime.LocalTime
- Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGParameter "interval" Data.Time.Clock.Scale.DiffTime
- Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGParameter "time with time zone" (Data.Time.LocalTime.TimeOfDay.TimeOfDay, Data.Time.LocalTime.TimeZone.TimeZone)
- Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGParameter "time without time zone" Data.Time.LocalTime.TimeOfDay.TimeOfDay
- Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGParameter "timestamp with time zone" Data.Time.Clock.UTC.UTCTime
- Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGParameter "timestamp without time zone" Data.Time.LocalTime.LocalTime.LocalTime
+ Database.PostgreSQL.Typed: newtype PGError
+ Database.PostgreSQL.Typed.Dynamic: instance Database.PostgreSQL.Typed.Dynamic.PGRep (Data.Time.LocalTime.Internal.TimeOfDay.TimeOfDay, Data.Time.LocalTime.Internal.TimeZone.TimeZone)
+ Database.PostgreSQL.Typed.Dynamic: instance Database.PostgreSQL.Typed.Dynamic.PGRep Data.Time.Clock.Internal.DiffTime.DiffTime
+ Database.PostgreSQL.Typed.Dynamic: instance Database.PostgreSQL.Typed.Dynamic.PGRep Data.Time.Clock.Internal.UTCTime.UTCTime
+ Database.PostgreSQL.Typed.Dynamic: instance Database.PostgreSQL.Typed.Dynamic.PGRep Data.Time.LocalTime.Internal.LocalTime.LocalTime
+ Database.PostgreSQL.Typed.Dynamic: instance Database.PostgreSQL.Typed.Dynamic.PGRep Data.Time.LocalTime.Internal.TimeOfDay.TimeOfDay
+ Database.PostgreSQL.Typed.Protocol: PGNotification :: !Word32 -> !ByteString -> ByteString -> PGNotification
+ Database.PostgreSQL.Typed.Protocol: [pgColBinary] :: PGColDescription -> !Bool
+ Database.PostgreSQL.Typed.Protocol: [pgColModifier] :: PGColDescription -> !Int32
+ Database.PostgreSQL.Typed.Protocol: [pgColName] :: PGColDescription -> ByteString
+ Database.PostgreSQL.Typed.Protocol: [pgColNumber] :: PGColDescription -> !Int16
+ Database.PostgreSQL.Typed.Protocol: [pgColSize] :: PGColDescription -> !Int16
+ Database.PostgreSQL.Typed.Protocol: [pgColTable] :: PGColDescription -> !OID
+ Database.PostgreSQL.Typed.Protocol: [pgColType] :: PGColDescription -> !OID
+ Database.PostgreSQL.Typed.Protocol: [pgNotificationChannel] :: PGNotification -> !ByteString
+ Database.PostgreSQL.Typed.Protocol: [pgNotificationPayload] :: PGNotification -> ByteString
+ Database.PostgreSQL.Typed.Protocol: [pgNotificationPid] :: PGNotification -> !Word32
+ Database.PostgreSQL.Typed.Protocol: data PGNotification
+ Database.PostgreSQL.Typed.Protocol: instance Database.PostgreSQL.Typed.Protocol.RecvMsg (Data.Either.Either Database.PostgreSQL.Typed.Protocol.PGBackendMessage Database.PostgreSQL.Typed.Protocol.RecvSync)
+ Database.PostgreSQL.Typed.Protocol: instance Database.PostgreSQL.Typed.Protocol.RecvMsg Database.PostgreSQL.Typed.Protocol.PGBackendMessage
+ Database.PostgreSQL.Typed.Protocol: instance Database.PostgreSQL.Typed.Protocol.RecvMsg Database.PostgreSQL.Typed.Protocol.PGNotification
+ Database.PostgreSQL.Typed.Protocol: instance Database.PostgreSQL.Typed.Protocol.RecvMsg Database.PostgreSQL.Typed.Protocol.RecvNonBlock
+ Database.PostgreSQL.Typed.Protocol: instance Database.PostgreSQL.Typed.Protocol.RecvMsg Database.PostgreSQL.Typed.Protocol.RecvSync
+ Database.PostgreSQL.Typed.Protocol: instance GHC.Show.Show Database.PostgreSQL.Typed.Protocol.PGNotification
+ Database.PostgreSQL.Typed.Protocol: instance GHC.Show.Show Database.PostgreSQL.Typed.Protocol.RecvNonBlock
+ Database.PostgreSQL.Typed.Protocol: instance GHC.Show.Show Database.PostgreSQL.Typed.Protocol.RecvSync
+ Database.PostgreSQL.Typed.Protocol: newtype PGError
+ Database.PostgreSQL.Typed.Protocol: pgGetNotification :: PGConnection -> IO PGNotification
+ Database.PostgreSQL.Typed.Protocol: pgGetNotifications :: PGConnection -> IO [PGNotification]
+ Database.PostgreSQL.Typed.Range: instance GHC.Classes.Ord a => Data.Semigroup.Semigroup (Database.PostgreSQL.Typed.Range.Range a)
+ Database.PostgreSQL.Typed.Range: union :: Ord a => Range a -> Range a -> Range a
+ Database.PostgreSQL.Typed.Types: [pgServerVersion] :: PGTypeEnv -> Maybe ByteString
+ Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGColumn "interval" Data.Time.Clock.Internal.DiffTime.DiffTime
+ Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGColumn "time with time zone" (Data.Time.LocalTime.Internal.TimeOfDay.TimeOfDay, Data.Time.LocalTime.Internal.TimeZone.TimeZone)
+ Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGColumn "time without time zone" Data.Time.LocalTime.Internal.TimeOfDay.TimeOfDay
+ Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGColumn "timestamp with time zone" Data.Time.Clock.Internal.UTCTime.UTCTime
+ Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGColumn "timestamp without time zone" Data.Time.LocalTime.Internal.LocalTime.LocalTime
+ Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGParameter "interval" Data.Time.Clock.Internal.DiffTime.DiffTime
+ Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGParameter "time with time zone" (Data.Time.LocalTime.Internal.TimeOfDay.TimeOfDay, Data.Time.LocalTime.Internal.TimeZone.TimeZone)
+ Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGParameter "time without time zone" Data.Time.LocalTime.Internal.TimeOfDay.TimeOfDay
+ Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGParameter "timestamp with time zone" Data.Time.Clock.Internal.UTCTime.UTCTime
+ Database.PostgreSQL.Typed.Types: instance Database.PostgreSQL.Typed.Types.PGParameter "timestamp without time zone" Data.Time.LocalTime.Internal.LocalTime.LocalTime
+ Database.PostgreSQL.Typed.Types: pgDQuoteFrom :: [Char] -> ByteString -> Builder
- Database.PostgreSQL.Typed.Array: class (PGType t, PGType (PGElemType t)) => PGArrayType t where type PGElemType t :: Symbol pgArrayElementType PGTypeProxy = PGTypeProxy pgArrayDelim _ = ',' where {
+ Database.PostgreSQL.Typed.Array: class (PGType t, PGType (PGElemType t)) => PGArrayType t where {
- Database.PostgreSQL.Typed.Dynamic: class (PGParameter (PGRepType a) a, PGColumn (PGRepType a) a) => PGRep a where type PGRepType a :: Symbol where {
+ Database.PostgreSQL.Typed.Dynamic: class (PGParameter (PGRepType a) a, PGColumn (PGRepType a) a) => PGRep a where {
- Database.PostgreSQL.Typed.Enum: class (Eq a, Ord a, Enum a, Bounded a, PGRep a) => PGEnum a where pgEnumName a = fromJust $ lookup a pgEnumValues pgEnumValue n = lookup n $ map swap pgEnumValues pgEnumValues = map (id &&& pgEnumName) $ enumFromTo minBound maxBound
+ Database.PostgreSQL.Typed.Enum: class (Eq a, Ord a, Enum a, Bounded a, PGRep a) => PGEnum a
- Database.PostgreSQL.Typed.Range: class (PGType t, PGType (PGSubType t)) => PGRangeType t where type PGSubType t :: Symbol pgRangeElementType PGTypeProxy = PGTypeProxy where {
+ Database.PostgreSQL.Typed.Range: class (PGType t, PGType (PGSubType t)) => PGRangeType t where {
- Database.PostgreSQL.Typed.Types: PGTypeEnv :: Maybe Bool -> PGTypeEnv
+ Database.PostgreSQL.Typed.Types: PGTypeEnv :: Maybe Bool -> Maybe ByteString -> PGTypeEnv
- Database.PostgreSQL.Typed.Types: class PGType t => PGColumn t a where pgDecodeBinary _ t _ = error $ "pgDecodeBinary " ++ show (pgTypeName t) ++ ": not supported" pgDecodeValue _ t (PGTextValue v) = pgDecode t v pgDecodeValue e t (PGBinaryValue v) = pgDecodeBinary e t v pgDecodeValue _ t PGNullValue = error $ "NULL in " ++ show (pgTypeName t) ++ " column (use Maybe or COALESCE)"
+ Database.PostgreSQL.Typed.Types: class PGType t => PGColumn t a
- Database.PostgreSQL.Typed.Types: class PGType t => PGParameter t a where pgLiteral t = pgQuote . pgEncode t pgEncodeValue _ t = PGTextValue . pgEncode t
+ Database.PostgreSQL.Typed.Types: class PGType t => PGParameter t a
- Database.PostgreSQL.Typed.Types: class (KnownSymbol t, PGParameter t (PGVal t), PGColumn t (PGVal t)) => PGType t where type PGVal t :: * pgTypeName = fromString . symbolVal pgBinaryColumn _ _ = False where {
+ Database.PostgreSQL.Typed.Types: class (KnownSymbol t, PGParameter t (PGVal t), PGColumn t (PGVal t)) => PGType t where {
- Database.PostgreSQL.Typed.Types: pgDQuote :: [Char] -> ByteString -> Builder
+ Database.PostgreSQL.Typed.Types: pgDQuote :: ByteString -> Builder
Files
- Database/PostgreSQL/Typed/Array.hs +1/−1
- Database/PostgreSQL/Typed/HDBC.hs +12/−12
- Database/PostgreSQL/Typed/Protocol.hs +256/−155
- Database/PostgreSQL/Typed/Range.hs +27/−16
- Database/PostgreSQL/Typed/Types.hs +14/−8
- postgresql-typed.cabal +1/−1
- test/Connect.hs +1/−1
- test/Main.hs +19/−1
Database/PostgreSQL/Typed/Array.hs view
@@ -54,7 +54,7 @@ (PGArrayType t, PGParameter (PGElemType t) a) => PGParameter t (PGArray a) where pgEncode ta l = buildPGValue $ BSB.char7 '{' <> mconcat (intersperse (BSB.char7 $ pgArrayDelim ta) $ map el l) <> BSB.char7 '}' where el Nothing = BSB.string7 "null"- el (Just e) = pgDQuote (pgArrayDelim ta : "{}") $ pgEncode (pgArrayElementType ta) e+ el (Just e) = pgDQuoteFrom (pgArrayDelim ta : "{}") $ pgEncode (pgArrayElementType ta) e #if __GLASGOW_HASKELL__ >= 710 -- |Allow entirely non-null arrays as parameter inputs only. -- (Only supported on ghc >= 7.10 due to instance overlap.)
Database/PostgreSQL/Typed/HDBC.hs view
@@ -89,7 +89,7 @@ pgv <- takePGConnection pg reloadTypes Connection { connectionPG = pgv- , connectionServerVer = maybe "" BSC.unpack $ pgServerVersion pg+ , connectionServerVer = maybe "" BSC.unpack $ pgServerVersion $ pgTypeEnv pg , connectionTypes = mempty , connectionFetchSize = 1 }@@ -139,16 +139,16 @@ getType :: Connection -> PGConnection -> Maybe Bool -> PGColDescription -> ColDesc getType c pg nul PGColDescription{..} = ColDesc- { colDescName = BSC.unpack colName+ { colDescName = BSC.unpack pgColName , colDesc = HDBC.SqlColDesc { HDBC.colType = sqlTypeId t- , HDBC.colSize = fromIntegral colModifier <$ guard (colModifier >= 0)- , HDBC.colOctetLength = fromIntegral colSize <$ guard (colSize >= 0)+ , HDBC.colSize = fromIntegral pgColModifier <$ guard (pgColModifier >= 0)+ , HDBC.colOctetLength = fromIntegral pgColSize <$ guard (pgColSize >= 0) , HDBC.colDecDigits = Nothing , HDBC.colNullable = nul } , colDescDecode = sqlTypeDecode t- } where t = IntMap.findWithDefault (sqlType (pgTypeEnv pg) $ show colType) (fromIntegral colType) (connectionTypes c)+ } where t = IntMap.findWithDefault (sqlType (pgTypeEnv pg) $ show pgColType) (fromIntegral pgColType) (connectionTypes c) instance HDBC.IConnection Connection where disconnect c = withPGConnection c@@ -232,13 +232,13 @@ describeTable c t = withPGConnection c $ \pg -> map (\[attname, attrelid, attnum, atttypid, attlen, atttypmod, attnotnull] -> colDescName &&& colDesc $ getType c pg (Just $ not $ pgDecodeRep attnotnull) PGColDescription- { colName = pgDecodeRep attname- , colTable = pgDecodeRep attrelid- , colNumber = pgDecodeRep attnum- , colType = pgDecodeRep atttypid- , colSize = pgDecodeRep attlen- , colModifier = pgDecodeRep atttypmod- , colBinary = False+ { pgColName = pgDecodeRep attname+ , pgColTable = pgDecodeRep attrelid+ , pgColNumber = pgDecodeRep attnum+ , pgColType = pgDecodeRep atttypid+ , pgColSize = pgDecodeRep attlen+ , pgColModifier = pgDecodeRep atttypmod+ , pgColBinary = False }) . snd <$> pgSimpleQuery pg (BSLC.fromChunks [ "SELECT attname, attrelid, attnum, atttypid, attlen, atttypmod, attnotnull"
Database/PostgreSQL/Typed/Protocol.hs view
@@ -1,6 +1,13 @@-{-# LANGUAGE CPP, DeriveDataTypeable, PatternGuards, DataKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-} -- Copyright 2010, 2011, 2012, 2013 Chris Forno--- Copyright 2014-2015 Dylan Simon+-- Copyright 2014-2018 Dylan Simon -- |The Protocol module allows for direct, low-level communication with a -- PostgreSQL server over TCP/IP. You probably don't want to use this module@@ -14,7 +21,6 @@ , pgErrorCode , pgConnectionDatabase , pgTypeEnv- , pgServerVersion , pgConnect , pgDisconnect , pgReconnect@@ -42,14 +48,18 @@ , PGRowDescription , pgBind , pgFetch+ -- * Notifications+ , PGNotification(..)+ , pgGetNotifications+ , pgGetNotification ) where #if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), (<$))+import Control.Applicative ((<$>), (<$)) #endif-import Control.Arrow ((&&&), first, second)-import Control.Exception (Exception, throwIO, onException)-import Control.Monad (void, liftM2, replicateM, when, unless)+import Control.Arrow ((&&&), first, second)+import Control.Exception (Exception, throwIO, onException, finally)+import Control.Monad (void, liftM2, replicateM, when, unless) #ifdef VERSION_cryptonite import qualified Crypto.Hash as Hash import qualified Data.ByteArray.Encoding as BA@@ -58,36 +68,37 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Builder as B import qualified Data.ByteString.Char8 as BSC-import Data.ByteString.Internal (w2c)+import Data.ByteString.Internal (w2c) import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Lazy.Char8 as BSLC-import Data.ByteString.Lazy.Internal (smallChunkSize)+import Data.ByteString.Lazy.Internal (smallChunkSize) import qualified Data.Foldable as Fold-import Data.IORef (IORef, newIORef, writeIORef, readIORef, atomicModifyIORef, atomicModifyIORef', modifyIORef, modifyIORef')-import Data.Int (Int32, Int16)+import Data.IORef (IORef, newIORef, writeIORef, readIORef, atomicModifyIORef, atomicModifyIORef', modifyIORef')+import Data.Int (Int32, Int16) import qualified Data.Map.Lazy as Map-import Data.Maybe (fromMaybe)-import Data.Monoid ((<>))+import Data.Maybe (fromMaybe)+import Data.Monoid ((<>)) #if !MIN_VERSION_base(4,8,0)-import Data.Monoid (mempty)+import Data.Monoid (mempty) #endif-import Data.Tuple (swap)-import Data.Typeable (Typeable)+import Data.Tuple (swap)+import Data.Typeable (Typeable) #if !MIN_VERSION_base(4,8,0)-import Data.Word (Word)+import Data.Word (Word) #endif-import Data.Word (Word32)-import Network (HostName, PortID(..), connectTo)-import System.IO (Handle, hFlush, hClose, stderr, hPutStrLn, hSetBuffering, BufferMode(BlockBuffering))-import System.IO.Unsafe (unsafeInterleaveIO)-import Text.Read (readMaybe)+import Data.Word (Word32)+import Network (HostName, PortID(..), connectTo)+import System.IO (Handle, hFlush, hClose, stderr, hPutStrLn, hSetBuffering, BufferMode(BlockBuffering))+import System.IO.Error (mkIOError, eofErrorType, ioError)+import System.IO.Unsafe (unsafeInterleaveIO)+import Text.Read (readMaybe) import Database.PostgreSQL.Typed.Types import Database.PostgreSQL.Typed.Dynamic data PGState = StateUnsync -- no Sync- | StatePending -- Sync sent+ | StatePending -- expecting ReadyForQuery -- ReadyForQuery received: | StateIdle | StateTransaction@@ -124,28 +135,52 @@ , connDatabase :: !PGDatabase , connPid :: !Word32 -- unused , connKey :: !Word32 -- unused- , connParameters :: Map.Map BS.ByteString BS.ByteString , connTypeEnv :: PGTypeEnv+ , connParameters :: IORef (Map.Map BS.ByteString BS.ByteString) , connPreparedStatementCount :: IORef Integer , connPreparedStatementMap :: IORef (Map.Map (BS.ByteString, [OID]) PGPreparedStatement) , connState :: IORef PGState , connInput :: IORef (G.Decoder PGBackendMessage) , connTransaction :: IORef Word+ , connNotifications :: IORef (Queue PGNotification) } data PGColDescription = PGColDescription- { colName :: BS.ByteString- , colTable :: !OID- , colNumber :: !Int16- , colType :: !OID- , colSize :: !Int16- , colModifier :: !Int32- , colBinary :: !Bool+ { pgColName :: BS.ByteString+ , pgColTable :: !OID+ , pgColNumber :: !Int16+ , pgColType :: !OID+ , pgColSize :: !Int16+ , pgColModifier :: !Int32+ , pgColBinary :: !Bool } deriving (Show) type PGRowDescription = [PGColDescription] type MessageFields = Map.Map Char BS.ByteString +data PGNotification = PGNotification+ { pgNotificationPid :: !Word32+ , pgNotificationChannel :: !BS.ByteString+ , pgNotificationPayload :: BSL.ByteString+ } deriving (Show)++-- |Simple amortized fifo+data Queue a = Queue [a] [a]++emptyQueue :: Queue a+emptyQueue = Queue [] []++enQueue :: a -> Queue a -> Queue a+enQueue a (Queue e d) = Queue (a:e) d++deQueue :: Queue a -> (Queue a, Maybe a)+deQueue (Queue e (x:d)) = (Queue e d, Just x)+deQueue (Queue (reverse -> x:d) []) = (Queue [] d, Just x)+deQueue q = (q, Nothing)++queueToList :: Queue a -> [a]+queueToList (Queue e d) = d ++ reverse e+ -- |PGFrontendMessage represents a PostgreSQL protocol message that we'll send. -- See <http://www.postgresql.org/docs/current/interactive/protocol-message-formats.html>. data PGFrontendMessage@@ -190,6 +225,7 @@ | ErrorResponse { messageFields :: MessageFields } | NoData | NoticeResponse { messageFields :: MessageFields }+ | NotificationResponse PGNotification -- |A ParameterDescription describes the type of a given SQL -- query/statement parameter ($1, $2, etc.). Unfortunately, -- PostgreSQL does not give us nullability information for the@@ -207,7 +243,7 @@ -- |PGException is thrown upon encountering an 'ErrorResponse' with severity of -- ERROR, FATAL, or PANIC. It holds the message of the error.-data PGError = PGError { pgErrorFields :: MessageFields }+newtype PGError = PGError { pgErrorFields :: MessageFields } deriving (Typeable) instance Show PGError where@@ -237,7 +273,16 @@ -- |A database connection with sane defaults: -- localhost:5432:postgres defaultPGDatabase :: PGDatabase-defaultPGDatabase = PGDatabase "localhost" (PortNumber 5432) (BSC.pack "postgres") (BSC.pack "postgres") BS.empty [] False defaultLogMessage+defaultPGDatabase = PGDatabase+ { pgDBHost = "localhost"+ , pgDBPort = PortNumber 5432+ , pgDBName = "postgres"+ , pgDBUser = "postgres"+ , pgDBPass = BS.empty+ , pgDBParams = []+ , pgDBDebug = False+ , pgDBLogMessage = defaultLogMessage+ } connDebug :: PGConnection -> Bool connDebug = pgDBDebug . connDatabase@@ -253,10 +298,6 @@ pgTypeEnv :: PGConnection -> PGTypeEnv pgTypeEnv = connTypeEnv --- |Retrieve the \"server_version\" parameter from the connection, if any.-pgServerVersion :: PGConnection -> Maybe BS.ByteString-pgServerVersion PGConnection{ connParameters = p } = Map.lookup (BSC.pack "server_version") p- #ifdef VERSION_cryptonite md5 :: BS.ByteString -> BS.ByteString md5 = BA.convertToBase BA.Base16 . (Hash.hash :: BS.ByteString -> Hash.Digest Hash.MD5)@@ -272,7 +313,7 @@ lazyByteStringNul :: BSL.ByteString -> B.Builder lazyByteStringNul s = B.lazyByteString s <> nul --- |Given a message, determin the (optional) type ID and the body+-- |Given a message, determine the (optional) type ID and the body messageBody :: PGFrontendMessage -> (Maybe Char, B.Builder) messageBody (StartupMessage kv) = (Nothing, B.word32BE 0x30000 <> Fold.foldMap (\(k, v) -> byteStringNul k <> byteStringNul v) kv <> nul)@@ -326,6 +367,7 @@ (t, b) = second (BSL.toStrict . B.toLazyByteString) $ messageBody msg state _ StateClosed = StateClosed state Sync _ = StatePending+ state SimpleQuery{} _ = StatePending state Terminate _ = StateClosed state _ _ = StateUnsync @@ -363,13 +405,13 @@ tmod <- G.getWord32be -- type modifier fmt <- G.getWord16be -- format code return $ PGColDescription- { colName = name- , colTable = oid- , colNumber = fromIntegral col- , colType = typ'- , colSize = fromIntegral siz- , colModifier = fromIntegral tmod- , colBinary = toEnum (fromIntegral fmt)+ { pgColName = name+ , pgColTable = oid+ , pgColNumber = fromIntegral col+ , pgColType = typ'+ , pgColSize = fromIntegral siz+ , pgColModifier = fromIntegral tmod+ , pgColBinary = toEnum (fromIntegral fmt) } getMessageBody 'Z' = ReadyForQuery <$> (rs . w2c =<< G.getWord8) where rs 'I' = return StateIdle@@ -393,65 +435,123 @@ getMessageBody 'n' = return NoData getMessageBody 's' = return PortalSuspended getMessageBody 'N' = NoticeResponse <$> getMessageFields+getMessageBody 'A' = NotificationResponse <$> do+ PGNotification+ <$> G.getWord32be+ <*> getByteStringNul+ <*> G.getLazyByteStringNul getMessageBody t = fail $ "pgGetMessage: unknown message type: " ++ show t getMessage :: G.Decoder PGBackendMessage getMessage = G.runGetIncremental $ do typ <- G.getWord8- s <- G.bytesRead len <- G.getWord32be- msg <- getMessageBody (w2c typ)- e <- G.bytesRead- let r = fromIntegral len - fromIntegral (e - s)- when (r > 0) $ G.skip r- when (r < 0) $ fail "pgReceive: decoder overran message"- return msg+ G.isolate (fromIntegral len - 4) $ getMessageBody (w2c typ) -pgRecv :: Bool -> PGConnection -> IO (Maybe PGBackendMessage)-pgRecv block c@PGConnection{ connHandle = h, connInput = dr, connState = sr } =- go =<< readIORef dr where+class Show m => RecvMsg m where+ -- |Read from connection, returning immediate value or non-empty data+ recvMsgData :: PGConnection -> IO (Either m BS.ByteString)+ recvMsgData c = do+ r <- BS.hGetSome (connHandle c) smallChunkSize+ if BS.null r+ then do+ writeIORef (connState c) StateClosed+ hClose (connHandle c)+ -- Should this instead be a special PGError?+ ioError $ mkIOError eofErrorType "PGConnection" (Just (connHandle c)) Nothing+ else+ return (Right r)+ -- |Expected ReadyForQuery message+ recvMsgSync :: Maybe m+ recvMsgSync = Nothing+ -- |NotificationResponse message+ recvMsgNotif :: PGConnection -> PGNotification -> IO (Maybe m)+ recvMsgNotif c n = Nothing <$+ modifyIORef' (connNotifications c) (enQueue n)+ -- |ErrorResponse message+ recvMsgErr :: PGConnection -> MessageFields -> IO (Maybe m)+ recvMsgErr c m = Nothing <$+ connLogMessage c m+ -- |Any other unhandled message+ recvMsg :: PGConnection -> PGBackendMessage -> IO (Maybe m)+ recvMsg c m = Nothing <$ + connLogMessage c (makeMessage (BSC.pack $ "Unexpected server message: " ++ show m) "Each statement should only contain a single query")++-- |Process all pending messages+data RecvNonBlock = RecvNonBlock deriving (Show)+instance RecvMsg RecvNonBlock where+ recvMsgData c = do+ r <- BS.hGetNonBlocking (connHandle c) smallChunkSize+ if BS.null r+ then return (Left RecvNonBlock)+ else return (Right r)++-- |Wait for ReadyForQuery+data RecvSync = RecvSync deriving (Show)+instance RecvMsg RecvSync where+ recvMsgSync = Just RecvSync++-- |Wait for NotificationResponse+instance RecvMsg PGNotification where+ recvMsgNotif _ = return . Just++-- |Return any message (throwing errors)+instance RecvMsg PGBackendMessage where+ recvMsgErr _ = throwIO . PGError+ recvMsg _ = return . Just++-- |Return any message or ReadyForQuery+instance RecvMsg (Either PGBackendMessage RecvSync) where+ recvMsgSync = Just $ Right RecvSync+ recvMsgErr _ = throwIO . PGError+ recvMsg _ = return . Just . Left++-- |Receive the next message from PostgreSQL (low-level).+pgRecv :: RecvMsg m => PGConnection -> IO m+pgRecv c@PGConnection{ connInput = dr, connState = sr } =+ rcv =<< readIORef dr where next = writeIORef dr new = G.pushChunk getMessage- go (G.Done b _ m) = do++ -- read and parse+ rcv (G.Done b _ m) = do when (connDebug c) $ putStrLn $ "< " ++ show m got (new b) m- go (G.Fail _ _ r) = next (new BS.empty) >> fail r -- not clear how can recover- go d@(G.Partial r) = do- b <- (if block then BS.hGetSome else BS.hGetNonBlocking) h smallChunkSize- if BS.null b- then Nothing <$ next d- else go $ r (Just b)- got :: G.Decoder PGBackendMessage -> PGBackendMessage -> IO (Maybe PGBackendMessage)- got d (NoticeResponse m) = connLogMessage c m >> go d- got d m@(ReadyForQuery s) = do- s' <- atomicModifyIORef' sr ((,) s)- if s == s'- then go d- else done d m- got d m@(ErrorResponse _) = writeIORef sr StateUnsync >> done d m- got d m = done d m- done d m = Just m <$ next d+ rcv (G.Fail _ _ r) = next (new BS.empty) >> fail r -- not clear how can recover+ rcv d@(G.Partial r) = recvMsgData c `onException` next d >>=+ either (<$ next d) (rcv . r . Just) --- |Receive the next message from PostgreSQL (low-level). Note that this will--- block until it gets a message.-pgReceive :: PGConnection -> IO PGBackendMessage-pgReceive c = do- r <- pgRecv True c- case r of- Nothing -> do- writeIORef (connState c) StateClosed- fail $ "pgReceive: connection closed"- Just ErrorResponse{ messageFields = m } -> throwIO (PGError m)- Just m -> return m+ -- process message+ msg (ParameterStatus k v) = Nothing <$+ modifyIORef' (connParameters c) (Map.insert k v)+ msg (NoticeResponse m) = Nothing <$+ connLogMessage c m+ msg (ErrorResponse m) =+ recvMsgErr c m+ msg m@(ReadyForQuery s) = do+ s' <- atomicModifyIORef' sr (s, )+ if s' == StatePending+ then return recvMsgSync -- expected+ else recvMsg c m -- unexpected+ msg (NotificationResponse n) =+ recvMsgNotif c n+ msg m@AuthenticationOk = do+ writeIORef sr StatePending+ recvMsg c m+ msg m = recvMsg c m+ got d m = msg m `onException` next d >>=+ maybe (rcv d) (<$ next d) -- |Connect to a PostgreSQL server. pgConnect :: PGDatabase -> IO PGConnection pgConnect db = do+ param <- newIORef Map.empty state <- newIORef StateUnsync prepc <- newIORef 0 prepm <- newIORef Map.empty input <- newIORef getMessage tr <- newIORef 0+ notif <- newIORef emptyQueue h <- connectTo (pgDBHost db) (pgDBPort db) hSetBuffering h (BlockBuffering Nothing) let c = PGConnection@@ -459,53 +559,55 @@ , connDatabase = db , connPid = 0 , connKey = 0- , connParameters = Map.empty+ , connParameters = param , connPreparedStatementCount = prepc , connPreparedStatementMap = prepm , connState = state , connTypeEnv = unknownPGTypeEnv , connInput = input , connTransaction = tr+ , connNotifications = notif } pgSend c $ StartupMessage $- [ (BSC.pack "user", pgDBUser db)- , (BSC.pack "database", pgDBName db)- , (BSC.pack "client_encoding", BSC.pack "UTF8")- , (BSC.pack "standard_conforming_strings", BSC.pack "on")- , (BSC.pack "bytea_output", BSC.pack "hex")- , (BSC.pack "DateStyle", BSC.pack "ISO, YMD")- , (BSC.pack "IntervalStyle", BSC.pack "iso_8601")+ [ ("user", pgDBUser db)+ , ("database", pgDBName db)+ , ("client_encoding", "UTF8")+ , ("standard_conforming_strings", "on")+ , ("bytea_output", "hex")+ , ("DateStyle", "ISO, YMD")+ , ("IntervalStyle", "iso_8601") ] ++ pgDBParams db pgFlush c conn c where- conn c = pgReceive c >>= msg c- msg c (ReadyForQuery _) = return c- { connTypeEnv = PGTypeEnv- { pgIntegerDatetimes = fmap (BSC.pack "on" ==) $ Map.lookup (BSC.pack "integer_datetimes") (connParameters c)+ conn c = pgRecv c >>= msg c+ msg c (Right RecvSync) = do+ cp <- readIORef (connParameters c)+ return c+ { connTypeEnv = PGTypeEnv+ { pgIntegerDatetimes = fmap ("on" ==) $ Map.lookup "integer_datetimes" cp+ , pgServerVersion = Map.lookup "server_version" cp+ } }- }- msg c (BackendKeyData p k) = conn c{ connPid = p, connKey = k }- msg c (ParameterStatus k v) = conn c{ connParameters = Map.insert k v $ connParameters c }- msg c AuthenticationOk = conn c- msg c AuthenticationCleartextPassword = do+ msg c (Left (BackendKeyData p k)) = conn c{ connPid = p, connKey = k }+ msg c (Left AuthenticationOk) = conn c+ msg c (Left AuthenticationCleartextPassword) = do pgSend c $ PasswordMessage $ pgDBPass db pgFlush c conn c #ifdef VERSION_cryptonite- msg c (AuthenticationMD5Password salt) = do- pgSend c $ PasswordMessage $ BSC.pack "md5" `BS.append` md5 (md5 (pgDBPass db <> pgDBUser db) `BS.append` salt)+ msg c (Left (AuthenticationMD5Password salt)) = do+ pgSend c $ PasswordMessage $ "md5" `BS.append` md5 (md5 (pgDBPass db <> pgDBUser db) `BS.append` salt) pgFlush c conn c #endif- msg _ m = fail $ "pgConnect: unexpected response: " ++ show m+ msg _ (Left m) = fail $ "pgConnect: unexpected response: " ++ show m -- |Disconnect cleanly from the PostgreSQL server. pgDisconnect :: PGConnection -- ^ a handle from 'pgConnect' -> IO ()-pgDisconnect c@PGConnection{ connHandle = h } = do- pgSend c Terminate- hClose h+pgDisconnect c@PGConnection{ connHandle = h } =+ pgSend c Terminate `finally` hClose h -- |Disconnect cleanly from the PostgreSQL server, but only if it's still connected. pgDisconnectOnce :: PGConnection -- ^ a handle from 'pgConnect'@@ -523,7 +625,7 @@ if cd == d && s /= StateClosed then return c{ connDatabase = d } else do- when (s /= StateClosed) $ pgDisconnect c+ pgDisconnectOnce c pgConnect d pgSync :: PGConnection -> IO ()@@ -531,28 +633,16 @@ s <- readIORef sr case s of StateClosed -> fail "pgSync: operation on closed connection"- StatePending -> wait True- StateUnsync -> wait False+ StatePending -> wait+ StateUnsync -> do+ pgSend c Sync+ pgFlush c+ wait _ -> return () where- wait s = do- r <- pgRecv s c- case r of- Nothing- | s -> do- writeIORef sr StateClosed- fail $ "pgReceive: connection closed"- | otherwise -> do- pgSend c Sync- pgFlush c- wait True- (Just (ErrorResponse{ messageFields = m })) -> do- connLogMessage c m- wait s- (Just (ReadyForQuery _)) -> return ()- (Just m) -> do- connLogMessage c $ makeMessage (BSC.pack $ "Unexpected server message: " ++ show m) $ BSC.pack "Each statement should only contain a single query"- wait s+ wait = do+ RecvSync <- pgRecv c+ return () rowDescription :: PGBackendMessage -> PGRowDescription rowDescription (RowDescription d) = d@@ -573,11 +663,11 @@ pgSend h DescribeStatement{ statementName = BS.empty } pgSend h Sync pgFlush h- ParseComplete <- pgReceive h- ParameterDescription ps <- pgReceive h- (,) ps <$> (mapM desc . rowDescription =<< pgReceive h)+ ParseComplete <- pgRecv h+ ParameterDescription ps <- pgRecv h+ (,) ps <$> (mapM desc . rowDescription =<< pgRecv h) where- desc (PGColDescription{ colName = name, colTable = tab, colNumber = col, colType = typ }) = do+ desc (PGColDescription{ pgColName = name, pgColTable = tab, pgColNumber = col, pgColType = typ }) = do n <- nullable tab col return (name, typ, n) -- We don't get nullability indication from PostgreSQL, at least not directly.@@ -587,7 +677,7 @@ | nulls && oid /= 0 = do -- In cases where the resulting field is tracable to the column of a -- table, we can check there.- (_, r) <- pgPreparedQuery h (BSC.pack "SELECT attnotnull FROM pg_catalog.pg_attribute WHERE attrelid = $1 AND attnum = $2") [26, 21] [pgEncodeRep (oid :: OID), pgEncodeRep (col :: Int16)] []+ (_, r) <- pgPreparedQuery h "SELECT attnotnull FROM pg_catalog.pg_attribute WHERE attrelid = $1 AND attnum = $2" [26, 21] [pgEncodeRep (oid :: OID), pgEncodeRep (col :: Int16)] [] case r of [[s]] -> return $ not $ pgDecodeRep s [] -> return True@@ -617,8 +707,8 @@ pgSend h $ SimpleQuery sql pgFlush h go start where - go = (pgReceive h >>=)- start (RowDescription rd) = go $ row (map colBinary rd) id+ go = (pgRecv h >>=)+ start (RowDescription rd) = go $ row (map pgColBinary rd) id start (CommandComplete c) = got c [] start EmptyQueryResponse = return (0, []) start m = fail $ "pgSimpleQuery: unexpected response: " ++ show m@@ -636,13 +726,12 @@ pgSend h $ SimpleQuery sql pgFlush h go where- go = pgReceive h >>= res- res (RowDescription _) = go- res (CommandComplete _) = go- res EmptyQueryResponse = go- res (DataRow _) = go- res (ParameterStatus _ _) = go- res (ReadyForQuery _) = return ()+ go = pgRecv h >>= res+ res (Left (RowDescription _)) = go+ res (Left (CommandComplete _)) = go+ res (Left EmptyQueryResponse) = go+ res (Left (DataRow _)) = go+ res (Right RecvSync) = return () res m = fail $ "pgSimpleQueries_: unexpected response: " ++ show m pgPreparedBind :: PGConnection -> BS.ByteString -> [OID] -> PGValues -> [Bool] -> IO (IO ())@@ -656,9 +745,9 @@ pgSend c Parse{ queryString = BSL.fromStrict sql, statementName = preparedStatementName n, parseTypes = types } pgSend c Bind{ portalName = BS.empty, statementName = preparedStatementName n, bindParameters = bind, binaryColumns = bc } let- go = pgReceive c >>= start+ go = pgRecv c >>= start start ParseComplete = do- modifyIORef (connPreparedStatementMap c) $+ modifyIORef' (connPreparedStatementMap c) $ Map.insert key n go start BindComplete = return ()@@ -681,7 +770,7 @@ start go id where- go r = pgReceive c >>= row r+ go r = pgRecv c >>= row r row r (DataRow fs) = go (r . (fixBinary bc fs :)) row r (CommandComplete d) = return (rowsAffected d, r []) row r EmptyQueryResponse = return (0, r [])@@ -702,7 +791,7 @@ pgSend c Execute{ portalName = BS.empty, executeRows = count } pgSend c Flush pgFlush c- go r = pgReceive c >>= row r+ go r = pgRecv c >>= row r row r (DataRow fs) = go (r . (fixBinary bc fs :)) row r PortalSuspended = r <$> unsafeInterleaveIO (execute >> go id) row r (CommandComplete _) = return (r [])@@ -770,7 +859,7 @@ pgSend c Sync pgFlush c go where- go = pgReceive c >>= res+ go = pgRecv c >>= res res ParseComplete = go res BindComplete = go res (DataRow _) = go@@ -787,7 +876,7 @@ pgSend c Parse{ queryString = sql, statementName = preparedStatementName n, parseTypes = types } pgSend c Sync pgFlush c- ParseComplete <- pgReceive c+ ParseComplete <- pgRecv c return n -- |Close a previously prepared query.@@ -798,8 +887,8 @@ pgSend c CloseStatement{ statementName = preparedStatementName n } pgSend c Sync pgFlush c- CloseComplete <- pgReceive c- CloseComplete <- pgReceive c+ CloseComplete <- pgRecv c+ CloseComplete <- pgRecv c return () -- |Bind a prepared statement, and return the row description.@@ -812,12 +901,12 @@ pgSend c DescribePortal{ portalName = sn } pgSend c Sync pgFlush c- CloseComplete <- pgReceive c- BindComplete <- pgReceive c- rowDescription <$> pgReceive c+ CloseComplete <- pgRecv c+ BindComplete <- pgRecv c+ rowDescription <$> pgRecv c where sn = preparedStatementName n --- |Fetch a single row from an executed prepared statement, returning the next N result rows (if any) and number of affected rows when complete.+-- |Fetch some rows from an executed prepared statement, returning the next N result rows (if any) and number of affected rows when complete. pgFetch :: PGConnection -> PGPreparedStatement -> Word32 -- ^Maximum number of rows to return, or 0 for all -> IO ([PGValues], Maybe Integer) pgFetch c n count = do@@ -826,7 +915,7 @@ pgSend c Sync pgFlush c go where- go = pgReceive c >>= res+ go = pgRecv c >>= res res (DataRow v) = first (v :) <$> go res PortalSuspended = return ([], Nothing) res (CommandComplete d) = do@@ -834,7 +923,19 @@ pgSend c ClosePortal{ portalName = preparedStatementName n } pgSend c Sync pgFlush c- CloseComplete <- pgReceive c+ CloseComplete <- pgRecv c return ([], Just $ rowsAffected d) res EmptyQueryResponse = return ([], Just 0) res m = fail $ "pgFetch: unexpected response: " ++ show m++-- |Retrieve any pending notifications. Non-blocking.+pgGetNotifications :: PGConnection -> IO [PGNotification]+pgGetNotifications c = do+ RecvNonBlock <- pgRecv c+ queueToList <$> atomicModifyIORef' (connNotifications c) (emptyQueue, )++-- |Retrieve a notifications, blocking if necessary.+pgGetNotification :: PGConnection -> IO PGNotification+pgGetNotification c =+ maybe (pgRecv c) return+ =<< atomicModifyIORef' (connNotifications c) deQueue
Database/PostgreSQL/Typed/Range.hs view
@@ -14,17 +14,21 @@ module Database.PostgreSQL.Typed.Range where #if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), (<$))+import Control.Applicative ((<$>), (<$)) #endif-import Control.Monad (guard)+import Control.Monad (guard) import qualified Data.Attoparsec.ByteString.Char8 as P import qualified Data.ByteString.Builder as BSB import qualified Data.ByteString.Char8 as BSC-import Data.Monoid ((<>))+#if MIN_VERSION_base(4,9,0)+import Data.Semigroup (Semigroup(..))+#else+import Data.Monoid ((<>)) #if !MIN_VERSION_base(4,8,0)-import Data.Monoid (Monoid(..))+import Data.Monoid (Monoid(..)) #endif-import GHC.TypeLits (Symbol)+#endif+import GHC.TypeLits (Symbol) import Database.PostgreSQL.Typed.Types @@ -205,18 +209,25 @@ intersect (Range la ua) (Range lb ub) = normalize $ Range (max la lb) (min ua ub) intersect _ _ = Empty +-- |Union ranges. Fails if ranges are disjoint.+union :: Ord a => Range a -> Range a -> Range a+union Empty r = r+union r Empty = r+union _ra@(Range la ua) _rb@(Range lb ub)+ -- isEmpty _ra = _rb+ -- isEmpty _rb = _ra+ | Bounded False False <- compareBounds lb ua = error "union: disjoint Ranges"+ | Bounded False False <- compareBounds la ub = error "union: disjoint Ranges"+ | otherwise = Range (min la lb) (max ua ub)++#if MIN_VERSION_base(4,9,0)+instance Ord a => Semigroup (Range a) where+ (<>) = union+#endif+ instance Ord a => Monoid (Range a) where mempty = Empty- -- |Union ranges. Fails if ranges are disjoint.- mappend Empty r = r- mappend r Empty = r- mappend _ra@(Range la ua) _rb@(Range lb ub)- -- isEmpty _ra = _rb- -- isEmpty _rb = _ra- | Bounded False False <- compareBounds lb ua = error "mappend: disjoint Ranges"- | Bounded False False <- compareBounds la ub = error "mappend: disjoint Ranges"- | otherwise = Range (min la lb) (max ua ub)-+ mappend = union -- |Class indicating that the first PostgreSQL type is a range of the second. -- This implies 'PGParameter' and 'PGColumn' instances that will work for any type.@@ -235,7 +246,7 @@ <> pc ']' ')' u where pb Nothing = mempty- pb (Just b) = pgDQuote "(),[]" $ pgEncode (pgRangeElementType tr) b+ pb (Just b) = pgDQuoteFrom "(),[]" $ pgEncode (pgRangeElementType tr) b pc c o b = BSB.char7 $ if boundClosed b then c else o instance (PGRangeType t, PGColumn (PGSubType t) a) => PGColumn t (Range a) where pgDecode tr a = either (error . ("pgDecode range (" ++) . (++ ("): " ++ BSC.unpack a))) id $ P.parseOnly per a where
Database/PostgreSQL/Typed/Types.hs view
@@ -38,6 +38,7 @@ -- * Conversion utilities , pgQuote , pgDQuote+ , pgDQuoteFrom , parsePGDQuote , buildPGValue ) where@@ -119,11 +120,13 @@ -- Nothing values represent unknown. data PGTypeEnv = PGTypeEnv { pgIntegerDatetimes :: Maybe Bool -- ^ If @integer_datetimes@ is @on@; only relevant for binary encoding.+ , pgServerVersion :: Maybe BS.ByteString -- ^ The @server_version@ parameter } deriving (Show) unknownPGTypeEnv :: PGTypeEnv unknownPGTypeEnv = PGTypeEnv { pgIntegerDatetimes = Nothing+ , pgServerVersion = Nothing } -- |A PostgreSQL literal identifier, generally corresponding to the \"name\" type (63-byte strings), but as it would be entered in a query, so may include double-quoting for special characters or schema-qualification.@@ -235,17 +238,20 @@ buildPGValue :: BSB.Builder -> BS.ByteString buildPGValue = BSL.toStrict . BSB.toLazyByteString --- |Double-quote a value if it's \"\", \"null\", or contains any whitespace, \'\"\', \'\\\', or the characters given in the first argument.--- Checking all these things may not be worth it. We could just double-quote everything.-pgDQuote :: [Char] -> BS.ByteString -> BSB.Builder-pgDQuote unsafe s- | BS.null s || BSC.any (\c -> isSpace c || c == '"' || c == '\\' || c `elem` unsafe) s || BSC.map toLower s == BSC.pack "null" =- dq <> BSBP.primMapByteStringBounded ec s <> dq- | otherwise = BSB.byteString s where+-- |Double-quote a value (e.g., as an identifier).+-- Does not properly handle unicode escaping (yet).+pgDQuote :: BS.ByteString -> BSB.Builder+pgDQuote s = dq <> BSBP.primMapByteStringBounded ec s <> dq where dq = BSB.char7 '"' ec = BSBP.condB (\c -> c == c2w '"' || c == c2w '\\') bs (BSBP.liftFixedToBounded BSBP.word8) bs = BSBP.liftFixedToBounded $ ((,) '\\') BSBP.>$< (BSBP.char7 BSBP.>*< BSBP.word8) +-- |Double-quote a value if it's \"\", \"null\", or contains any whitespace, \'\"\', \'\\\', or the characters given in the first argument.+pgDQuoteFrom :: [Char] -> BS.ByteString -> BSB.Builder+pgDQuoteFrom unsafe s+ | BS.null s || BSC.any (\c -> isSpace c || c == '"' || c == '\\' || c `elem` unsafe) s || BSC.map toLower s == BSC.pack "null" = pgDQuote s+ | otherwise = BSB.byteString s+ -- |Parse double-quoted values ala 'pgDQuote'. parsePGDQuote :: Bool -> [Char] -> (BS.ByteString -> Bool) -> P.Parser (Maybe BS.ByteString) parsePGDQuote blank unsafe isnul = (Just <$> q) <> (mnul <$> uq) where@@ -770,7 +776,7 @@ class PGType t => PGRecordType t instance PGRecordType t => PGParameter t PGRecord where pgEncode _ (PGRecord l) =- buildPGValue $ BSB.char7 '(' <> mconcat (intersperse (BSB.char7 ',') $ map (maybe mempty (pgDQuote "(),")) l) <> BSB.char7 ')'+ buildPGValue $ BSB.char7 '(' <> mconcat (intersperse (BSB.char7 ',') $ map (maybe mempty (pgDQuoteFrom "(),")) l) <> BSB.char7 ')' pgLiteral _ (PGRecord l) = BSC.pack "ROW(" <> BS.intercalate (BSC.singleton ',') (map (maybe (BSC.pack "NULL") pgQuote) l) `BSC.snoc` ')' instance PGRecordType t => PGColumn t PGRecord where
postgresql-typed.cabal view
@@ -1,5 +1,5 @@ Name: postgresql-typed-Version: 0.5.2+Version: 0.5.3.0 Cabal-Version: >= 1.8 License: BSD3 License-File: COPYING
test/Connect.hs view
@@ -11,7 +11,7 @@ , pgDBPort = UnixSocket "/tmp/.s.PGSQL.5432" #endif , pgDBUser = "templatepg"- , pgDBDebug = True+ -- , pgDBDebug = True , pgDBParams = [("TimeZone", "UTC")] }
test/Main.hs view
@@ -3,6 +3,7 @@ --{-# OPTIONS_GHC -ddump-splices #-} module Main (main) where +import Control.Exception (try) import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BSC import Data.Char (isDigit, toUpper)@@ -14,13 +15,15 @@ import Test.QuickCheck.Test (isSuccess) import Database.PostgreSQL.Typed-import Database.PostgreSQL.Typed.Types (OID)+import Database.PostgreSQL.Typed.Types+import Database.PostgreSQL.Typed.Protocol import Database.PostgreSQL.Typed.Array () import qualified Database.PostgreSQL.Typed.Range as Range import Database.PostgreSQL.Typed.Enum import Database.PostgreSQL.Typed.Inet import Database.PostgreSQL.Typed.SQLToken import Database.PostgreSQL.Typed.Relation+import qualified Database.PostgreSQL.Typed.ErrCodes as PGErr import Connect @@ -155,6 +158,21 @@ ["box"] <- preparedApply c 603 [Just "line"] <- prepared c 628 "line" ["line"] <- preparedApply c 628++ pgSimpleQueries_ c "LISTEN channame; NOTIFY channame, 'oh hello'; SELECT pg_notify('channame', 'there')"+ PGNotification _ "channame" "oh hello" <- pgGetNotification c+ (-1, []) <- pgSimpleQuery c "NOTIFY channame"++ pgTransaction c $ do+ (1, [[PGTextValue "1"]]) <- pgSimpleQuery c "SELECT 1"+ (-1, []) <- pgSimpleQuery c "NOTIFY channame, 'nope'"+ Left e1 <- try $ pgSimpleQuery c "SYNTAX_ERROR"+ assert $ pgErrorCode e1 == PGErr.syntax_error+ Left e2 <- try $ pgSimpleQuery c "SELECT 1"+ assert $ pgErrorCode e2 == PGErr.in_failed_sql_transaction++ [PGNotification _ "channame" "there", PGNotification _ "channame" ""] <- pgGetNotifications c+ [] <- pgGetNotifications c pgDisconnect c exitSuccess