pqi 0.0.1.1 → 0.1.0.0
raw patch · 5 files changed
+342/−604 lines, 5 filesdep +textdep −QuickCheckdep −hspecdep −postgresql-libpqPVP ok
version bump matches the API change (PVP)
Dependencies added: text
Dependencies removed: QuickCheck, hspec, postgresql-libpq, pqi, ptr-peeker, ptr-poker
API changes (from Hackage documentation)
- Pqi: -- | The cancellation-handle type produced by this connection.
- Pqi: backendPID :: IsConnection c => c -> IO Int32
- Pqi: cancel :: IsCancel k => k -> IO (Either ByteString ())
- Pqi: class IsCancel k
- Pqi: class (IsResult ResultOf c, IsCancel CancelOf c) => IsConnection c where {
- Pqi: class IsResult r
- Pqi: clientEncoding :: IsConnection c => c -> IO ByteString
- Pqi: cmdStatus :: IsResult r => r -> IO (Maybe ByteString)
- Pqi: cmdTuples :: IsResult r => r -> IO (Maybe ByteString)
- Pqi: connectPoll :: IsConnection c => c -> IO PollingStatus
- Pqi: connectStart :: IsConnection c => ByteString -> IO c
- Pqi: connectdb :: IsConnection c => ByteString -> IO c
- Pqi: connectionNeedsPassword :: IsConnection c => c -> IO Bool
- Pqi: connectionUsedPassword :: IsConnection c => c -> IO Bool
- Pqi: consumeInput :: IsConnection c => c -> IO Bool
- Pqi: db :: IsConnection c => c -> IO (Maybe ByteString)
- Pqi: describePortal :: IsConnection c => c -> ByteString -> IO (Maybe (ResultOf c))
- Pqi: describePrepared :: IsConnection c => c -> ByteString -> IO (Maybe (ResultOf c))
- Pqi: disableNoticeReporting :: IsConnection c => c -> IO ()
- Pqi: enableNoticeReporting :: IsConnection c => c -> IO ()
- Pqi: enterPipelineMode :: IsConnection c => c -> IO Bool
- Pqi: errorMessage :: IsConnection c => c -> IO (Maybe ByteString)
- Pqi: escapeByteaConn :: IsConnection c => c -> ByteString -> IO (Maybe ByteString)
- Pqi: escapeIdentifier :: IsConnection c => c -> ByteString -> IO (Maybe ByteString)
- Pqi: escapeStringConn :: IsConnection c => c -> ByteString -> IO (Maybe ByteString)
- Pqi: exec :: IsConnection c => c -> ByteString -> IO (Maybe (ResultOf c))
- Pqi: execParams :: IsConnection c => c -> ByteString -> [Maybe (Word32, ByteString, Format)] -> Format -> IO (Maybe (ResultOf c))
- Pqi: execPrepared :: IsConnection c => c -> ByteString -> [Maybe (ByteString, Format)] -> Format -> IO (Maybe (ResultOf c))
- Pqi: exitPipelineMode :: IsConnection c => c -> IO Bool
- Pqi: fformat :: IsResult r => r -> Int32 -> IO Format
- Pqi: finish :: IsConnection c => c -> IO ()
- Pqi: flush :: IsConnection c => c -> IO FlushStatus
- Pqi: fmod :: IsResult r => r -> Int32 -> IO Int
- Pqi: fname :: IsResult r => r -> Int32 -> IO (Maybe ByteString)
- Pqi: fnumber :: IsResult r => r -> ByteString -> IO (Maybe Int32)
- Pqi: fsize :: IsResult r => r -> Int32 -> IO Int
- Pqi: ftable :: IsResult r => r -> Int32 -> IO Word32
- Pqi: ftablecol :: IsResult r => r -> Int32 -> IO Int32
- Pqi: ftype :: IsResult r => r -> Int32 -> IO Word32
- Pqi: getCancel :: IsConnection c => c -> IO (Maybe (CancelOf c))
- Pqi: getCopyData :: IsConnection c => c -> Bool -> IO CopyOutResult
- Pqi: getNotice :: IsConnection c => c -> IO (Maybe ByteString)
- Pqi: getResult :: IsConnection c => c -> IO (Maybe (ResultOf c))
- Pqi: getisnull :: IsResult r => r -> Int32 -> Int32 -> IO Bool
- Pqi: getlength :: IsResult r => r -> Int32 -> Int32 -> IO Int
- Pqi: getvalue :: IsResult r => r -> Int32 -> Int32 -> IO (Maybe ByteString)
- Pqi: getvalue' :: IsResult r => r -> Int32 -> Int32 -> IO (Maybe ByteString)
- Pqi: host :: IsConnection c => c -> IO (Maybe ByteString)
- Pqi: isBusy :: IsConnection c => c -> IO Bool
- Pqi: isNullConnection :: IsConnection c => c -> Bool
- Pqi: isnonblocking :: IsConnection c => c -> IO Bool
- Pqi: loClose :: IsConnection c => c -> Int32 -> IO (Maybe ())
- Pqi: loCreat :: IsConnection c => c -> IO (Maybe Word32)
- Pqi: loCreate :: IsConnection c => c -> Word32 -> IO (Maybe Word32)
- Pqi: loExport :: IsConnection c => c -> Word32 -> FilePath -> IO (Maybe ())
- Pqi: loImport :: IsConnection c => c -> FilePath -> IO (Maybe Word32)
- Pqi: loImportWithOid :: IsConnection c => c -> FilePath -> Word32 -> IO (Maybe Word32)
- Pqi: loOpen :: IsConnection c => c -> Word32 -> IOMode -> IO (Maybe Int32)
- Pqi: loRead :: IsConnection c => c -> Int32 -> Int -> IO (Maybe ByteString)
- Pqi: loSeek :: IsConnection c => c -> Int32 -> SeekMode -> Int -> IO (Maybe Int)
- Pqi: loTell :: IsConnection c => c -> Int32 -> IO (Maybe Int)
- Pqi: loTruncate :: IsConnection c => c -> Int32 -> Int -> IO (Maybe ())
- Pqi: loUnlink :: IsConnection c => c -> Word32 -> IO (Maybe ())
- Pqi: loWrite :: IsConnection c => c -> Int32 -> ByteString -> IO (Maybe Int)
- Pqi: newNullConnection :: IsConnection c => IO c
- Pqi: nfields :: IsResult r => r -> IO Int32
- Pqi: notifies :: IsConnection c => c -> IO (Maybe Notify)
- Pqi: nparams :: IsResult r => r -> IO Int32
- Pqi: ntuples :: IsResult r => r -> IO Int32
- Pqi: options :: IsConnection c => c -> IO (Maybe ByteString)
- Pqi: parameterStatus :: IsConnection c => c -> ByteString -> IO (Maybe ByteString)
- Pqi: paramtype :: IsResult r => r -> Int32 -> IO Word32
- Pqi: pass :: IsConnection c => c -> IO (Maybe ByteString)
- Pqi: pipelineStatus :: IsConnection c => c -> IO PipelineStatus
- Pqi: pipelineSync :: IsConnection c => c -> IO Bool
- Pqi: port :: IsConnection c => c -> IO (Maybe ByteString)
- Pqi: prepare :: IsConnection c => c -> ByteString -> ByteString -> Maybe [Word32] -> IO (Maybe (ResultOf c))
- Pqi: protocolVersion :: IsConnection c => c -> IO Int
- Pqi: putCopyData :: IsConnection c => c -> ByteString -> IO CopyInResult
- Pqi: putCopyEnd :: IsConnection c => c -> Maybe ByteString -> IO CopyInResult
- Pqi: reset :: IsConnection c => c -> IO ()
- Pqi: resetPoll :: IsConnection c => c -> IO PollingStatus
- Pqi: resetStart :: IsConnection c => c -> IO Bool
- Pqi: resultErrorField :: IsResult r => r -> FieldCode -> IO (Maybe ByteString)
- Pqi: resultErrorMessage :: IsResult r => r -> IO (Maybe ByteString)
- Pqi: resultStatus :: IsResult r => r -> IO ExecStatus
- Pqi: sendDescribePortal :: IsConnection c => c -> ByteString -> IO Bool
- Pqi: sendDescribePrepared :: IsConnection c => c -> ByteString -> IO Bool
- Pqi: sendFlushRequest :: IsConnection c => c -> IO Bool
- Pqi: sendPrepare :: IsConnection c => c -> ByteString -> ByteString -> Maybe [Word32] -> IO Bool
- Pqi: sendQuery :: IsConnection c => c -> ByteString -> IO Bool
- Pqi: sendQueryParams :: IsConnection c => c -> ByteString -> [Maybe (Word32, ByteString, Format)] -> Format -> IO Bool
- Pqi: sendQueryPrepared :: IsConnection c => c -> ByteString -> [Maybe (ByteString, Format)] -> Format -> IO Bool
- Pqi: serverVersion :: IsConnection c => c -> IO Int
- Pqi: setClientEncoding :: IsConnection c => c -> ByteString -> IO Bool
- Pqi: setErrorVerbosity :: IsConnection c => c -> Verbosity -> IO Verbosity
- Pqi: setSingleRowMode :: IsConnection c => c -> IO Bool
- Pqi: setnonblocking :: IsConnection c => c -> Bool -> IO Bool
- Pqi: socket :: IsConnection c => c -> IO (Maybe Fd)
- Pqi: status :: IsConnection c => c -> IO ConnStatus
- Pqi: transactionStatus :: IsConnection c => c -> IO TransactionStatus
- Pqi: type CancelOf c;
- Pqi: type ResultOf c;
- Pqi: unescapeBytea :: ByteString -> ByteString
- Pqi: unsafeFreeResult :: IsResult r => r -> IO ()
- Pqi: user :: IsConnection c => c -> IO (Maybe ByteString)
- Pqi: }
+ Pqi: Adapter :: Text -> (ByteString -> IO Connection) -> (ByteString -> IO Connection) -> IO Connection -> (ByteString -> IO (Maybe ByteString)) -> Adapter
+ Pqi: Cancel :: IO (Either ByteString ()) -> Cancel
+ Pqi: Connection :: IO PollingStatus -> Bool -> IO () -> IO () -> IO Bool -> IO PollingStatus -> IO (Maybe ByteString) -> IO (Maybe ByteString) -> IO (Maybe ByteString) -> IO (Maybe ByteString) -> IO (Maybe ByteString) -> IO (Maybe ByteString) -> IO ConnStatus -> IO TransactionStatus -> (ByteString -> IO (Maybe ByteString)) -> IO Int -> IO Int -> IO (Maybe ByteString) -> IO (Maybe Fd) -> IO Int32 -> IO Bool -> IO Bool -> (ByteString -> IO (Maybe Result)) -> (ByteString -> [Maybe (Word32, ByteString, Format)] -> Format -> IO (Maybe Result)) -> (ByteString -> ByteString -> Maybe [Word32] -> IO (Maybe Result)) -> (ByteString -> [Maybe (ByteString, Format)] -> Format -> IO (Maybe Result)) -> (ByteString -> IO (Maybe Result)) -> (ByteString -> IO (Maybe Result)) -> (ByteString -> IO (Maybe ByteString)) -> (ByteString -> IO (Maybe ByteString)) -> (ByteString -> IO (Maybe ByteString)) -> (ByteString -> IO Bool) -> (ByteString -> [Maybe (Word32, ByteString, Format)] -> Format -> IO Bool) -> (ByteString -> ByteString -> Maybe [Word32] -> IO Bool) -> (ByteString -> [Maybe (ByteString, Format)] -> Format -> IO Bool) -> (ByteString -> IO Bool) -> (ByteString -> IO Bool) -> IO (Maybe Result) -> IO Bool -> IO Bool -> (Bool -> IO Bool) -> IO Bool -> IO Bool -> IO FlushStatus -> IO PipelineStatus -> IO Bool -> IO Bool -> IO Bool -> IO Bool -> IO (Maybe Cancel) -> IO (Maybe Notify) -> IO () -> IO () -> IO (Maybe ByteString) -> (ByteString -> IO CopyInResult) -> (Maybe ByteString -> IO CopyInResult) -> (Bool -> IO CopyOutResult) -> IO (Maybe Word32) -> (Word32 -> IO (Maybe Word32)) -> (FilePath -> IO (Maybe Word32)) -> (FilePath -> Word32 -> IO (Maybe Word32)) -> (Word32 -> FilePath -> IO (Maybe ())) -> (Word32 -> IOMode -> IO (Maybe Int32)) -> (Int32 -> ByteString -> IO (Maybe Int)) -> (Int32 -> Int -> IO (Maybe ByteString)) -> (Int32 -> SeekMode -> Int -> IO (Maybe Int)) -> (Int32 -> IO (Maybe Int)) -> (Int32 -> Int -> IO (Maybe ())) -> (Int32 -> IO (Maybe ())) -> (Word32 -> IO (Maybe ())) -> IO ByteString -> (ByteString -> IO Bool) -> (Verbosity -> IO Verbosity) -> Connection
+ Pqi: Result :: IO ExecStatus -> IO (Maybe ByteString) -> (FieldCode -> IO (Maybe ByteString)) -> IO () -> IO Int32 -> IO Int32 -> (Int32 -> IO (Maybe ByteString)) -> (ByteString -> IO (Maybe Int32)) -> (Int32 -> IO Word32) -> (Int32 -> IO Int32) -> (Int32 -> IO Format) -> (Int32 -> IO Word32) -> (Int32 -> IO Int) -> (Int32 -> IO Int) -> (Int32 -> Int32 -> IO (Maybe ByteString)) -> (Int32 -> Int32 -> IO (Maybe ByteString)) -> (Int32 -> Int32 -> IO Bool) -> (Int32 -> Int32 -> IO Int) -> IO Int32 -> (Int32 -> IO Word32) -> IO (Maybe ByteString) -> IO (Maybe ByteString) -> Result
+ Pqi: [backendPID] :: Connection -> IO Int32
+ Pqi: [cancel] :: Cancel -> IO (Either ByteString ())
+ Pqi: [clientEncoding] :: Connection -> IO ByteString
+ Pqi: [cmdStatus] :: Result -> IO (Maybe ByteString)
+ Pqi: [cmdTuples] :: Result -> IO (Maybe ByteString)
+ Pqi: [connectPoll] :: Connection -> IO PollingStatus
+ Pqi: [connectStart] :: Adapter -> ByteString -> IO Connection
+ Pqi: [connectdb] :: Adapter -> ByteString -> IO Connection
+ Pqi: [connectionNeedsPassword] :: Connection -> IO Bool
+ Pqi: [connectionUsedPassword] :: Connection -> IO Bool
+ Pqi: [consumeInput] :: Connection -> IO Bool
+ Pqi: [db] :: Connection -> IO (Maybe ByteString)
+ Pqi: [describePortal] :: Connection -> ByteString -> IO (Maybe Result)
+ Pqi: [describePrepared] :: Connection -> ByteString -> IO (Maybe Result)
+ Pqi: [disableNoticeReporting] :: Connection -> IO ()
+ Pqi: [enableNoticeReporting] :: Connection -> IO ()
+ Pqi: [enterPipelineMode] :: Connection -> IO Bool
+ Pqi: [errorMessage] :: Connection -> IO (Maybe ByteString)
+ Pqi: [escapeByteaConn] :: Connection -> ByteString -> IO (Maybe ByteString)
+ Pqi: [escapeIdentifier] :: Connection -> ByteString -> IO (Maybe ByteString)
+ Pqi: [escapeStringConn] :: Connection -> ByteString -> IO (Maybe ByteString)
+ Pqi: [execParams] :: Connection -> ByteString -> [Maybe (Word32, ByteString, Format)] -> Format -> IO (Maybe Result)
+ Pqi: [execPrepared] :: Connection -> ByteString -> [Maybe (ByteString, Format)] -> Format -> IO (Maybe Result)
+ Pqi: [exec] :: Connection -> ByteString -> IO (Maybe Result)
+ Pqi: [exitPipelineMode] :: Connection -> IO Bool
+ Pqi: [fformat] :: Result -> Int32 -> IO Format
+ Pqi: [finish] :: Connection -> IO ()
+ Pqi: [flush] :: Connection -> IO FlushStatus
+ Pqi: [fmod] :: Result -> Int32 -> IO Int
+ Pqi: [fname] :: Result -> Int32 -> IO (Maybe ByteString)
+ Pqi: [fnumber] :: Result -> ByteString -> IO (Maybe Int32)
+ Pqi: [fsize] :: Result -> Int32 -> IO Int
+ Pqi: [ftable] :: Result -> Int32 -> IO Word32
+ Pqi: [ftablecol] :: Result -> Int32 -> IO Int32
+ Pqi: [ftype] :: Result -> Int32 -> IO Word32
+ Pqi: [getCancel] :: Connection -> IO (Maybe Cancel)
+ Pqi: [getCopyData] :: Connection -> Bool -> IO CopyOutResult
+ Pqi: [getNotice] :: Connection -> IO (Maybe ByteString)
+ Pqi: [getResult] :: Connection -> IO (Maybe Result)
+ Pqi: [getisnull] :: Result -> Int32 -> Int32 -> IO Bool
+ Pqi: [getlength] :: Result -> Int32 -> Int32 -> IO Int
+ Pqi: [getvalue'] :: Result -> Int32 -> Int32 -> IO (Maybe ByteString)
+ Pqi: [getvalue] :: Result -> Int32 -> Int32 -> IO (Maybe ByteString)
+ Pqi: [host] :: Connection -> IO (Maybe ByteString)
+ Pqi: [isBusy] :: Connection -> IO Bool
+ Pqi: [isNullConnection] :: Connection -> Bool
+ Pqi: [isnonblocking] :: Connection -> IO Bool
+ Pqi: [loClose] :: Connection -> Int32 -> IO (Maybe ())
+ Pqi: [loCreat] :: Connection -> IO (Maybe Word32)
+ Pqi: [loCreate] :: Connection -> Word32 -> IO (Maybe Word32)
+ Pqi: [loExport] :: Connection -> Word32 -> FilePath -> IO (Maybe ())
+ Pqi: [loImportWithOid] :: Connection -> FilePath -> Word32 -> IO (Maybe Word32)
+ Pqi: [loImport] :: Connection -> FilePath -> IO (Maybe Word32)
+ Pqi: [loOpen] :: Connection -> Word32 -> IOMode -> IO (Maybe Int32)
+ Pqi: [loRead] :: Connection -> Int32 -> Int -> IO (Maybe ByteString)
+ Pqi: [loSeek] :: Connection -> Int32 -> SeekMode -> Int -> IO (Maybe Int)
+ Pqi: [loTell] :: Connection -> Int32 -> IO (Maybe Int)
+ Pqi: [loTruncate] :: Connection -> Int32 -> Int -> IO (Maybe ())
+ Pqi: [loUnlink] :: Connection -> Word32 -> IO (Maybe ())
+ Pqi: [loWrite] :: Connection -> Int32 -> ByteString -> IO (Maybe Int)
+ Pqi: [name] :: Adapter -> Text
+ Pqi: [newNullConnection] :: Adapter -> IO Connection
+ Pqi: [nfields] :: Result -> IO Int32
+ Pqi: [notifies] :: Connection -> IO (Maybe Notify)
+ Pqi: [nparams] :: Result -> IO Int32
+ Pqi: [ntuples] :: Result -> IO Int32
+ Pqi: [options] :: Connection -> IO (Maybe ByteString)
+ Pqi: [parameterStatus] :: Connection -> ByteString -> IO (Maybe ByteString)
+ Pqi: [paramtype] :: Result -> Int32 -> IO Word32
+ Pqi: [pass] :: Connection -> IO (Maybe ByteString)
+ Pqi: [pipelineStatus] :: Connection -> IO PipelineStatus
+ Pqi: [pipelineSync] :: Connection -> IO Bool
+ Pqi: [port] :: Connection -> IO (Maybe ByteString)
+ Pqi: [prepare] :: Connection -> ByteString -> ByteString -> Maybe [Word32] -> IO (Maybe Result)
+ Pqi: [protocolVersion] :: Connection -> IO Int
+ Pqi: [putCopyData] :: Connection -> ByteString -> IO CopyInResult
+ Pqi: [putCopyEnd] :: Connection -> Maybe ByteString -> IO CopyInResult
+ Pqi: [resetPoll] :: Connection -> IO PollingStatus
+ Pqi: [resetStart] :: Connection -> IO Bool
+ Pqi: [reset] :: Connection -> IO ()
+ Pqi: [resultErrorField] :: Result -> FieldCode -> IO (Maybe ByteString)
+ Pqi: [resultErrorMessage] :: Result -> IO (Maybe ByteString)
+ Pqi: [resultStatus] :: Result -> IO ExecStatus
+ Pqi: [sendDescribePortal] :: Connection -> ByteString -> IO Bool
+ Pqi: [sendDescribePrepared] :: Connection -> ByteString -> IO Bool
+ Pqi: [sendFlushRequest] :: Connection -> IO Bool
+ Pqi: [sendPrepare] :: Connection -> ByteString -> ByteString -> Maybe [Word32] -> IO Bool
+ Pqi: [sendQueryParams] :: Connection -> ByteString -> [Maybe (Word32, ByteString, Format)] -> Format -> IO Bool
+ Pqi: [sendQueryPrepared] :: Connection -> ByteString -> [Maybe (ByteString, Format)] -> Format -> IO Bool
+ Pqi: [sendQuery] :: Connection -> ByteString -> IO Bool
+ Pqi: [serverVersion] :: Connection -> IO Int
+ Pqi: [setClientEncoding] :: Connection -> ByteString -> IO Bool
+ Pqi: [setErrorVerbosity] :: Connection -> Verbosity -> IO Verbosity
+ Pqi: [setSingleRowMode] :: Connection -> IO Bool
+ Pqi: [setnonblocking] :: Connection -> Bool -> IO Bool
+ Pqi: [socket] :: Connection -> IO (Maybe Fd)
+ Pqi: [status] :: Connection -> IO ConnStatus
+ Pqi: [transactionStatus] :: Connection -> IO TransactionStatus
+ Pqi: [unescapeBytea] :: Adapter -> ByteString -> IO (Maybe ByteString)
+ Pqi: [unsafeFreeResult] :: Result -> IO ()
+ Pqi: [user] :: Connection -> IO (Maybe ByteString)
+ Pqi: data Adapter
+ Pqi: data Cancel
+ Pqi: data Connection
+ Pqi: data Result
Files
- CHANGELOG.md +10/−0
- pqi.cabal +7/−26
- src/library/Pqi.hs +325/−362
- src/library/Pqi/UnescapeBytea.hs +0/−158
- src/test/Spec.hs +0/−58
CHANGELOG.md view
@@ -0,0 +1,10 @@+# v0.1.0.0++## Breaking++- Replace IsConnection/IsResult/IsCancel type classes with concrete records+- Move unescapeBytea into Adapter++## Non-breaking++- Add the Adapter type
pqi.cabal view
@@ -1,13 +1,13 @@ cabal-version: 3.0 name: pqi-version: 0.0.1.1+version: 0.1.0.0 category: Database, PostgreSQL synopsis: Driver-agnostic interface to the PostgreSQL libpq API description: @pqi@ reproduces the API surface of the [@postgresql-libpq@](https://hackage.haskell.org/package/postgresql-libpq) package, but- reifies the connection (and its results) as a type class instead of a single- concrete type. This lets callers program against one interface and pick an- adapter at the type level:+ reifies the connection (and its results) as a plain record of closures+ instead of a single concrete type tied to @libpq@. This lets callers+ program against one interface and pick an adapter package to construct it: * [@pqi-ffi@](https://hackage.haskell.org/package/pqi-ffi) — a thin adapter backed by the C @libpq@ library via [@postgresql-libpq@](https://hackage.haskell.org/package/postgresql-libpq).@@ -15,7 +15,8 @@ * [@pqi-native@](https://hackage.haskell.org/package/pqi-native) — a pure-Haskell adapter that speaks the PostgreSQL wire protocol directly. - This package ships only the interface: the typeclasses, types and a couple of connection-independent helpers.+ This package ships only the interface: the record types, and an 'Adapter' type for adapter packages to bundle their+ connection-establishing functions under. homepage: https://github.com/nikita-volkov/pqi bug-reports: https://github.com/nikita-volkov/pqi/issues@@ -49,7 +50,6 @@ DeriveTraversable DerivingStrategies DerivingVia- DuplicateRecordFields EmptyDataDecls FlexibleContexts FlexibleInstances@@ -62,11 +62,9 @@ MultiParamTypeClasses MultiWayIf NamedFieldPuns- NoFieldSelectors NoImplicitPrelude NoMonomorphismRestriction NumericUnderscores- OverloadedRecordDot OverloadedStrings ParallelListComp PatternGuards@@ -96,24 +94,7 @@ exposed-modules: Pqi - other-modules:- Pqi.UnescapeBytea- build-depends: base >=4.11 && <5, bytestring >=0.10 && <0.13,- ptr-peeker >=0.2 && <0.3,- ptr-poker >=0.1 && <0.2,--test-suite pqi-test- import: test- type: exitcode-stdio-1.0- hs-source-dirs: src/test- main-is: Spec.hs- build-depends:- base >=4.11 && <5,- bytestring >=0.10 && <0.13,- hspec >=2.11 && <2.12,- postgresql-libpq >=0.11 && <0.12,- pqi,- QuickCheck >=2.14 && <3,+ text >=1.2 && <2.2,
src/library/Pqi.hs view
@@ -1,19 +1,37 @@ -- | A driver-agnostic reproduction of the [@postgresql-libpq@](https://hackage.haskell.org/package/postgresql-libpq) API (version -- @0.11@, the pipelining-capable release). ----- The connection is reified as a single class @'IsConnection' c@ parameterised--- over the connection type. Result accessors live in the independent--- @'IsResult' r@ class, and cancellation handles in @'IsCancel' k@.--- @'IsResult' ('ResultOf' c)@ and @'IsCancel' ('CancelOf' c)@ are superclass--- constraints of @'IsConnection' c@, so callers who have access to a connection--- get result inspection and cancellation automatically.+-- The connection is reified as a single concrete record type, 'Connection',+-- whose fields are the closures that implement each capability. Result+-- accessors live in the independent 'Result' record, and cancellation+-- handles in the 'Cancel' record. A 'Connection' produces 'Result's and+-- 'Cancel's directly (via its 'exec', 'getResult', 'getCancel', etc.+-- fields) — there is no type-level indirection: the whole package defines+-- exactly one 'Connection', one 'Result', and one 'Cancel' type. --+-- Each field of these records is a closure that has already captured+-- whatever underlying handle (e.g. a C @PGconn@ pointer, or a native+-- socket) it needs; from the caller's perspective a 'Connection' is simply+-- a bundle of @IO@ actions. This trades the old class-based polymorphism+-- for a concrete, monomorphic value that can be passed around, stored, and+-- constructed by whichever adapter package is in use.+--+-- Adapter packages (e.g. @pqi-ffi@, @pqi-native@) are responsible for+-- constructing 'Connection' values — this package does not provide any+-- @connectdb@\/@connectStart@\/@newNullConnection@-style constructors of+-- its own, since those don't have a connection to close over yet. Instead,+-- each adapter package exports a single top-level value of type 'Adapter',+-- bundling its connection-establishing functions together so that callers+-- who need to be adapter-agnostic (e.g. a differential test harness, or a+-- consumer that lets its own users pick an adapter) can hold onto one value+-- rather than a family of adapter-qualified functions.+-- -- Function names, argument order, and semantics mirror the API of the C library binding -- @postgresql-libpq@. -- The only deliberate departures are: ----- * @Connection@, @Result@, and @Cancel@ become the class parameter @c@ and the--- associated types @'ResultOf' c@ \/ @'CancelOf' c@.+-- * @Connection@, @Result@, and @Cancel@ are plain records of closures+-- rather than a class-parameterised type and its associated types. -- -- * OIDs are a plain 'Word32' and row\/column\/parameter indices and LoFds are a -- plain 'Int32', rather than the C-specific newtypes of the original.@@ -23,13 +41,21 @@ -- -- * There's no @invalidOid@ constant. It's just 0. ----- * The 'unescapeBytea' helper is bundled in this library and implemented natively without IO.+-- * @unescapeBytea@ is a field of 'Adapter' rather than a connection-independent+-- top-level function, since its implementation is adapter-specific. module Pqi- ( -- * Type classes- IsConnection (..),- IsResult (..),- IsCancel (..),+ ( -- * Adapter+ Adapter (..), + -- * Connection+ Connection (..),++ -- * Result inspection+ Result (..),++ -- * Cancellation+ Cancel (..),+ -- * Shared types Format (..), ExecStatus (..),@@ -43,9 +69,6 @@ CopyInResult (..), CopyOutResult (..), Notify (..),-- -- * Connection-independent helpers- unescapeBytea, ) where @@ -54,15 +77,14 @@ import Data.Either import Data.Eq import Data.Int-import Data.Kind (Type) import Data.Maybe import Data.Ord+import Data.Text (Text) import Data.Word-import Pqi.UnescapeBytea (unescapeBytea)+import Prelude (Bounded, Enum) import System.IO (FilePath, IO, IOMode, SeekMode) import System.Posix.Types (Fd) import Text.Show-import Prelude (Bounded, Enum) -- * Shared types @@ -93,14 +115,14 @@ NonfatalError | -- | A fatal error occurred. FatalError- | -- | The @'ResultOf'@ contains a single result tuple from the current command.+ | -- | The @'Result'@ contains a single result tuple from the current command. -- This status occurs only when single-row mode has been selected for the -- query. SingleTuple- | -- | The @'ResultOf'@ represents a synchronization point in pipeline mode,+ | -- | The @'Result'@ represents a synchronization point in pipeline mode, -- requested by @'pipelineSync'@. This status occurs only in pipeline mode. PipelineSync- | -- | The @'ResultOf'@ represents a pipeline that has received an error from+ | -- | The @'Result'@ represents a pipeline that has received an error from -- the server. @'getResult'@ must be called repeatedly, and each time it will -- return this status code until the end of the current pipeline, at which -- point it will return @'PipelineSync'@ and normal processing can resume.@@ -217,353 +239,294 @@ -- * Result inspection --- | Result-accessor methods, independent of the connection type that produced+-- | Result-accessor closures, independent of the connection that produced -- the result. This allows row decoders and projection functions (such as--- 'observeResult' in @pqi-conformance@) to operate on any result type--- without knowing the originating connection.-class IsResult r where- -- | The status of the result.- resultStatus :: r -> IO ExecStatus-- -- | The flat error message associated with the result, if any. Best-effort;- -- see the note on 'errorMessage'.- resultErrorMessage :: r -> IO (Maybe ByteString)-- -- | A single structured field of the result's error report.- resultErrorField :: r -> FieldCode -> IO (Maybe ByteString)-- -- | Free the result. Adapters that manage results with the garbage collector- -- may implement this as a no-op; for the C-backed adapter it frees the- -- underlying @PGresult@, after which the result must not be used.- unsafeFreeResult :: r -> IO ()-- -- | Number of rows (tuples) in the result.- ntuples :: r -> IO Int32-- -- | Number of columns (fields) in the result.- nfields :: r -> IO Int32-- -- | Name of the column at the given index.- fname :: r -> Int32 -> IO (Maybe ByteString)-- -- | Index of the column with the given name, if present.- fnumber :: r -> ByteString -> IO (Maybe Int32)-- -- | OID of the table the given column was fetched from, or 0.- ftable :: r -> Int32 -> IO Word32-- -- | Column number (within its table) that the given result column was- -- fetched from, or 0.- ftablecol :: r -> Int32 -> IO Int32-- -- | Format (text or binary) of the given column.- fformat :: r -> Int32 -> IO Format-- -- | Data type OID of the given column.- ftype :: r -> Int32 -> IO Word32-- -- | Type modifier of the given column.- fmod :: r -> Int32 -> IO Int-- -- | Server-side storage size of the given column's type, or a negative value- -- for variable size.- fsize :: r -> Int32 -> IO Int-- -- | Value at @(row, column)@, or @'Nothing'@ for SQL @NULL@. Delegates to- -- @'getvalue''@ by default in case the adapter provides a copying variant.- getvalue :: r -> Int32 -> Int32 -> IO (Maybe ByteString)- getvalue = getvalue'-- -- | Like 'getvalue', but always returns a copy that remains valid after the- -- result is freed.- getvalue' :: r -> Int32 -> Int32 -> IO (Maybe ByteString)-- -- | Whether the value at @(row, column)@ is SQL @NULL@.- getisnull :: r -> Int32 -> Int32 -> IO Bool-- -- | Length in bytes of the value at @(row, column)@.- getlength :: r -> Int32 -> Int32 -> IO Int-- -- | Number of parameters of a prepared statement (for a 'describePrepared'- -- result).- nparams :: r -> IO Int32-- -- | Data type OID of the given prepared-statement parameter.- paramtype :: r -> Int32 -> IO Word32-- -- | The command status tag of the result (e.g. @\"INSERT 0 1\"@).- cmdStatus :: r -> IO (Maybe ByteString)-- -- | The number of rows affected by the command, as text.- cmdTuples :: r -> IO (Maybe ByteString)+-- 'observeResult' in @pqi-conformance@) to operate on any result value+-- without knowing the originating connection or adapter.+--+-- There is exactly one 'Result' type in the whole @pqi@ package; adapters+-- construct values of this type by closing each field over their own+-- underlying result representation (e.g. a C @PGresult@ pointer).+data Result = Result+ { -- | The status of the result.+ resultStatus :: IO ExecStatus,+ -- | The flat error message associated with the result, if any. Best-effort;+ -- see the note on 'errorMessage'.+ resultErrorMessage :: IO (Maybe ByteString),+ -- | A single structured field of the result's error report.+ resultErrorField :: FieldCode -> IO (Maybe ByteString),+ -- | Free the result. Adapters that manage results with the garbage collector+ -- may implement this as a no-op; for the C-backed adapter it frees the+ -- underlying @PGresult@, after which the result must not be used.+ unsafeFreeResult :: IO (),+ -- | Number of rows (tuples) in the result.+ ntuples :: IO Int32,+ -- | Number of columns (fields) in the result.+ nfields :: IO Int32,+ -- | Name of the column at the given index.+ fname :: Int32 -> IO (Maybe ByteString),+ -- | Index of the column with the given name, if present.+ fnumber :: ByteString -> IO (Maybe Int32),+ -- | OID of the table the given column was fetched from, or 0.+ ftable :: Int32 -> IO Word32,+ -- | Column number (within its table) that the given result column was+ -- fetched from, or 0.+ ftablecol :: Int32 -> IO Int32,+ -- | Format (text or binary) of the given column.+ fformat :: Int32 -> IO Format,+ -- | Data type OID of the given column.+ ftype :: Int32 -> IO Word32,+ -- | Type modifier of the given column.+ fmod :: Int32 -> IO Int,+ -- | Server-side storage size of the given column's type, or a negative value+ -- for variable size.+ fsize :: Int32 -> IO Int,+ -- | Value at @(row, column)@, or @'Nothing'@ for SQL @NULL@. In the old+ -- class-based API this delegated to @'getvalue''@ by default; since a+ -- record has no notion of default methods, adapters that want that+ -- behaviour should set this field to the same closure as 'getvalue''+ -- when constructing the 'Result'.+ getvalue :: Int32 -> Int32 -> IO (Maybe ByteString),+ -- | Like 'getvalue', but always returns a copy that remains valid after the+ -- result is freed.+ getvalue' :: Int32 -> Int32 -> IO (Maybe ByteString),+ -- | Whether the value at @(row, column)@ is SQL @NULL@.+ getisnull :: Int32 -> Int32 -> IO Bool,+ -- | Length in bytes of the value at @(row, column)@.+ getlength :: Int32 -> Int32 -> IO Int,+ -- | Number of parameters of a prepared statement (for a 'describePrepared'+ -- result).+ nparams :: IO Int32,+ -- | Data type OID of the given prepared-statement parameter.+ paramtype :: Int32 -> IO Word32,+ -- | The command status tag of the result (e.g. @\"INSERT 0 1\"@).+ cmdStatus :: IO (Maybe ByteString),+ -- | The number of rows affected by the command, as text.+ cmdTuples :: IO (Maybe ByteString)+ } -- * Cancellation --- | Cancellation of in-progress commands, isolated from the connection type.-class IsCancel k where- -- | Request cancellation of the in-progress command via the handle.- cancel :: k -> IO (Either ByteString ())+-- | A cancellation handle, isolated from the connection that produced it.+--+-- There is exactly one 'Cancel' type in the whole @pqi@ package; adapters+-- construct values of this type by closing over their own underlying+-- cancellation handle.+data Cancel = Cancel+ { -- | Request cancellation of the in-progress command via the handle.+ cancel :: IO (Either ByteString ())+ } -- * Connection --- | The single flat capability class: establishing, closing, inspecting,--- querying, escaping, async commands, pipelining, cancellation handle--- creation, notifications, copy, large objects, and control.+-- | The single flat capability record: closing, inspecting, querying,+-- escaping, async commands, pipelining, cancellation handle creation,+-- notifications, copy, large objects, and control. ----- See the individual capability-class documentation (now inlined below) for--- semantics of each method.-class (IsResult (ResultOf c), IsCancel (CancelOf c)) => IsConnection c where- -- | The result type produced by this connection.- type ResultOf c :: Type-- -- | The cancellation-handle type produced by this connection.- type CancelOf c :: Type-- -- | Make a new (blocking) connection from a conninfo string.- connectdb :: ByteString -> IO c-- -- | Begin establishing a connection asynchronously.- connectStart :: ByteString -> IO c-- -- | Drive an asynchronous connection attempt forward.- connectPoll :: c -> IO PollingStatus-- -- | A sentinel \"null\" connection.- newNullConnection :: IO c-- -- | Whether a connection is the null sentinel.- isNullConnection :: c -> Bool-- -- | Close the connection and release its resources.- finish :: c -> IO ()-- -- | Reset the communication channel to the server (blocking).- reset :: c -> IO ()-- -- | Begin resetting the connection asynchronously.- resetStart :: c -> IO Bool-- -- | Drive an asynchronous reset forward.- resetPoll :: c -> IO PollingStatus-- -- | The database name of the connection.- db :: c -> IO (Maybe ByteString)-- -- | The user name of the connection.- user :: c -> IO (Maybe ByteString)-- -- | The password of the connection.- pass :: c -> IO (Maybe ByteString)-- -- | The server host name of the connection.- host :: c -> IO (Maybe ByteString)-- -- | The port of the connection.- port :: c -> IO (Maybe ByteString)-- -- | The command-line options passed in the connection request.- options :: c -> IO (Maybe ByteString)-- -- | Current connection status.- status :: c -> IO ConnStatus-- -- | Current in-transaction status of the server.- transactionStatus :: c -> IO TransactionStatus-- -- | Look up a current parameter setting reported by the server.- parameterStatus :: c -> ByteString -> IO (Maybe ByteString)-- -- | The frontend\/backend protocol version.- protocolVersion :: c -> IO Int-- -- | The server version, as an integer of the form @MMmmpp@.- serverVersion :: c -> IO Int-- -- | The most recent error message, if any.- --- -- Note: unlike the structured fields available via @'resultErrorField'@, the- -- flat message text is formatted locally by the driver, so adapters are not- -- expected to produce byte-identical strings.- errorMessage :: c -> IO (Maybe ByteString)-- -- | The file descriptor of the connection socket.- socket :: c -> IO (Maybe Fd)-- -- | The process ID of the backend serving this connection.- backendPID :: c -> IO Int32-- -- | Whether the connection authentication method required a password but- -- none was available.- connectionNeedsPassword :: c -> IO Bool-- -- | Whether the connection authentication used a password.- connectionUsedPassword :: c -> IO Bool-- -- | Submit a command and wait for the result.- exec :: c -> ByteString -> IO (Maybe (ResultOf c))-- -- | Submit a parameterized command. Each parameter is given as- -- @(type oid, value, format)@, or @'Nothing'@ for SQL @NULL@. The final- -- @'Format'@ selects the result format.- execParams ::- c ->- ByteString ->- [Maybe (Word32, ByteString, Format)] ->- Format ->- IO (Maybe (ResultOf c))-- -- | Prepare a named statement. The OID list, when supplied, fixes parameter- -- types; @'Nothing'@ leaves them to be inferred.- prepare :: c -> ByteString -> ByteString -> Maybe [Word32] -> IO (Maybe (ResultOf c))-- -- | Execute a previously prepared statement. Each parameter is- -- @(value, format)@, or @'Nothing'@ for SQL @NULL@.- execPrepared ::- c ->- ByteString ->- [Maybe (ByteString, Format)] ->- Format ->- IO (Maybe (ResultOf c))-- -- | Describe a prepared statement.- describePrepared :: c -> ByteString -> IO (Maybe (ResultOf c))-- -- | Describe a portal.- describePortal :: c -> ByteString -> IO (Maybe (ResultOf c))-- -- | Escape a string for safe inclusion in an SQL literal.- escapeStringConn :: c -> ByteString -> IO (Maybe ByteString)-- -- | Escape binary data for use within a @bytea@ literal.- escapeByteaConn :: c -> ByteString -> IO (Maybe ByteString)-- -- | Escape a string for use as an SQL identifier (e.g. a table or column- -- name), including the surrounding double quotes.- escapeIdentifier :: c -> ByteString -> IO (Maybe ByteString)-- -- | Submit a command without waiting for the result.- sendQuery :: c -> ByteString -> IO Bool-- -- | Asynchronous @'execParams'@.- sendQueryParams :: c -> ByteString -> [Maybe (Word32, ByteString, Format)] -> Format -> IO Bool-- -- | Asynchronous @'prepare'@.- sendPrepare :: c -> ByteString -> ByteString -> Maybe [Word32] -> IO Bool-- -- | Asynchronous @'execPrepared'@.- sendQueryPrepared :: c -> ByteString -> [Maybe (ByteString, Format)] -> Format -> IO Bool-- -- | Asynchronous @'describePrepared'@.- sendDescribePrepared :: c -> ByteString -> IO Bool-- -- | Asynchronous @'describePortal'@.- sendDescribePortal :: c -> ByteString -> IO Bool-- -- | Collect the next result from an asynchronous command.- getResult :: c -> IO (Maybe (ResultOf c))-- -- | Read input from the server into the driver's buffer.- consumeInput :: c -> IO Bool-- -- | Whether a command is busy (a @'getResult'@ would block).- isBusy :: c -> IO Bool-- -- | Set the non-blocking flag of the connection.- setnonblocking :: c -> Bool -> IO Bool-- -- | Whether the connection is in non-blocking mode.- isnonblocking :: c -> IO Bool-- -- | Select single-row mode for the currently executing query.- setSingleRowMode :: c -> IO Bool-- -- | Flush queued output data to the server.- flush :: c -> IO FlushStatus-- -- | Current pipeline-mode status.- pipelineStatus :: c -> IO PipelineStatus-- -- | Enter pipeline mode.- enterPipelineMode :: c -> IO Bool-- -- | Leave pipeline mode.- exitPipelineMode :: c -> IO Bool-- -- | Mark a synchronization point in a pipeline.- pipelineSync :: c -> IO Bool-- -- | Request the server to flush its output buffer in pipeline mode.- sendFlushRequest :: c -> IO Bool-- -- | Obtain a cancellation handle for the connection.- getCancel :: c -> IO (Maybe (CancelOf c))-- -- | Return the next notification from the queue, if any.- notifies :: c -> IO (Maybe Notify)-- -- | Stop accumulating notices for retrieval via @'getNotice'@.- disableNoticeReporting :: c -> IO ()-- -- | Start accumulating notices for retrieval via @'getNotice'@.- enableNoticeReporting :: c -> IO ()-- -- | Retrieve the next accumulated notice, if any.- getNotice :: c -> IO (Maybe ByteString)-- -- | Send data on a @COPY FROM STDIN@ connection.- putCopyData :: c -> ByteString -> IO CopyInResult-- -- | Signal the end of @COPY FROM STDIN@; @'Just'@ aborts with the given error.- putCopyEnd :: c -> Maybe ByteString -> IO CopyInResult-- -- | Receive data on a @COPY TO STDOUT@ connection. The @'Bool'@ selects- -- non-blocking mode.- getCopyData :: c -> Bool -> IO CopyOutResult-- -- | Create a new large object.- loCreat :: c -> IO (Maybe Word32)-- -- | Create a new large object with the given OID.- loCreate :: c -> Word32 -> IO (Maybe Word32)-- -- | Import a file as a new large object.- loImport :: c -> FilePath -> IO (Maybe Word32)-- -- | Import a file as a new large object with the given OID.- loImportWithOid :: c -> FilePath -> Word32 -> IO (Maybe Word32)-- -- | Export a large object to a file.- loExport :: c -> Word32 -> FilePath -> IO (Maybe ())-- -- | Open a large object.- loOpen :: c -> Word32 -> IOMode -> IO (Maybe Int32)-- -- | Write to an open large object.- loWrite :: c -> Int32 -> ByteString -> IO (Maybe Int)-- -- | Read from an open large object.- loRead :: c -> Int32 -> Int -> IO (Maybe ByteString)-- -- | Seek within an open large object.- loSeek :: c -> Int32 -> SeekMode -> Int -> IO (Maybe Int)-- -- | Report the current seek position of an open large object.- loTell :: c -> Int32 -> IO (Maybe Int)-- -- | Truncate an open large object.- loTruncate :: c -> Int32 -> Int -> IO (Maybe ())-- -- | Close an open large object.- loClose :: c -> Int32 -> IO (Maybe ())-- -- | Remove a large object.- loUnlink :: c -> Word32 -> IO (Maybe ())-- -- | The current client encoding name.- clientEncoding :: c -> IO ByteString+-- There is exactly one 'Connection' type in the whole @pqi@ package.+-- Adapter packages (e.g. @pqi-ffi@, @pqi-native@) construct values of this+-- type from their own top-level @connectdb@\/@connectStart@ functions,+-- closing each field over their own underlying connection representation+-- (e.g. a C @PGconn@ pointer, or a native socket). This module only+-- defines the shape; it does not construct any connections.+--+-- See the field-level documentation for the semantics of each capability.+data Connection = Connection+ { -- | Drive an asynchronous connection attempt forward.+ connectPoll :: IO PollingStatus,+ -- | Whether the connection is the null sentinel.+ isNullConnection :: Bool,+ -- | Close the connection and release its resources.+ finish :: IO (),+ -- | Reset the communication channel to the server (blocking).+ reset :: IO (),+ -- | Begin resetting the connection asynchronously.+ resetStart :: IO Bool,+ -- | Drive an asynchronous reset forward.+ resetPoll :: IO PollingStatus,+ -- | The database name of the connection.+ db :: IO (Maybe ByteString),+ -- | The user name of the connection.+ user :: IO (Maybe ByteString),+ -- | The password of the connection.+ pass :: IO (Maybe ByteString),+ -- | The server host name of the connection.+ host :: IO (Maybe ByteString),+ -- | The port of the connection.+ port :: IO (Maybe ByteString),+ -- | The command-line options passed in the connection request.+ options :: IO (Maybe ByteString),+ -- | Current connection status.+ status :: IO ConnStatus,+ -- | Current in-transaction status of the server.+ transactionStatus :: IO TransactionStatus,+ -- | Look up a current parameter setting reported by the server.+ parameterStatus :: ByteString -> IO (Maybe ByteString),+ -- | The frontend\/backend protocol version.+ protocolVersion :: IO Int,+ -- | The server version, as an integer of the form @MMmmpp@.+ serverVersion :: IO Int,+ -- | The most recent error message, if any.+ --+ -- Note: unlike the structured fields available via @'resultErrorField'@, the+ -- flat message text is formatted locally by the driver, so adapters are not+ -- expected to produce byte-identical strings.+ errorMessage :: IO (Maybe ByteString),+ -- | The file descriptor of the connection socket.+ socket :: IO (Maybe Fd),+ -- | The process ID of the backend serving this connection.+ backendPID :: IO Int32,+ -- | Whether the connection authentication method required a password but+ -- none was available.+ connectionNeedsPassword :: IO Bool,+ -- | Whether the connection authentication used a password.+ connectionUsedPassword :: IO Bool,+ -- | Submit a command and wait for the result.+ exec :: ByteString -> IO (Maybe Result),+ -- | Submit a parameterized command. Each parameter is given as+ -- @(type oid, value, format)@, or @'Nothing'@ for SQL @NULL@. The final+ -- @'Format'@ selects the result format.+ execParams ::+ ByteString ->+ [Maybe (Word32, ByteString, Format)] ->+ Format ->+ IO (Maybe Result),+ -- | Prepare a named statement. The OID list, when supplied, fixes parameter+ -- types; @'Nothing'@ leaves them to be inferred.+ prepare :: ByteString -> ByteString -> Maybe [Word32] -> IO (Maybe Result),+ -- | Execute a previously prepared statement. Each parameter is+ -- @(value, format)@, or @'Nothing'@ for SQL @NULL@.+ execPrepared ::+ ByteString ->+ [Maybe (ByteString, Format)] ->+ Format ->+ IO (Maybe Result),+ -- | Describe a prepared statement.+ describePrepared :: ByteString -> IO (Maybe Result),+ -- | Describe a portal.+ describePortal :: ByteString -> IO (Maybe Result),+ -- | Escape a string for safe inclusion in an SQL literal.+ escapeStringConn :: ByteString -> IO (Maybe ByteString),+ -- | Escape binary data for use within a @bytea@ literal.+ escapeByteaConn :: ByteString -> IO (Maybe ByteString),+ -- | Escape a string for use as an SQL identifier (e.g. a table or column+ -- name), including the surrounding double quotes.+ escapeIdentifier :: ByteString -> IO (Maybe ByteString),+ -- | Submit a command without waiting for the result.+ sendQuery :: ByteString -> IO Bool,+ -- | Asynchronous @'execParams'@.+ sendQueryParams :: ByteString -> [Maybe (Word32, ByteString, Format)] -> Format -> IO Bool,+ -- | Asynchronous @'prepare'@.+ sendPrepare :: ByteString -> ByteString -> Maybe [Word32] -> IO Bool,+ -- | Asynchronous @'execPrepared'@.+ sendQueryPrepared :: ByteString -> [Maybe (ByteString, Format)] -> Format -> IO Bool,+ -- | Asynchronous @'describePrepared'@.+ sendDescribePrepared :: ByteString -> IO Bool,+ -- | Asynchronous @'describePortal'@.+ sendDescribePortal :: ByteString -> IO Bool,+ -- | Collect the next result from an asynchronous command.+ getResult :: IO (Maybe Result),+ -- | Read input from the server into the driver's buffer.+ consumeInput :: IO Bool,+ -- | Whether a command is busy (a @'getResult'@ would block).+ isBusy :: IO Bool,+ -- | Set the non-blocking flag of the connection.+ setnonblocking :: Bool -> IO Bool,+ -- | Whether the connection is in non-blocking mode.+ isnonblocking :: IO Bool,+ -- | Select single-row mode for the currently executing query.+ setSingleRowMode :: IO Bool,+ -- | Flush queued output data to the server.+ flush :: IO FlushStatus,+ -- | Current pipeline-mode status.+ pipelineStatus :: IO PipelineStatus,+ -- | Enter pipeline mode.+ enterPipelineMode :: IO Bool,+ -- | Leave pipeline mode.+ exitPipelineMode :: IO Bool,+ -- | Mark a synchronization point in a pipeline.+ pipelineSync :: IO Bool,+ -- | Request the server to flush its output buffer in pipeline mode.+ sendFlushRequest :: IO Bool,+ -- | Obtain a cancellation handle for the connection.+ getCancel :: IO (Maybe Cancel),+ -- | Return the next notification from the queue, if any.+ notifies :: IO (Maybe Notify),+ -- | Stop accumulating notices for retrieval via @'getNotice'@.+ disableNoticeReporting :: IO (),+ -- | Start accumulating notices for retrieval via @'getNotice'@.+ enableNoticeReporting :: IO (),+ -- | Retrieve the next accumulated notice, if any.+ getNotice :: IO (Maybe ByteString),+ -- | Send data on a @COPY FROM STDIN@ connection.+ putCopyData :: ByteString -> IO CopyInResult,+ -- | Signal the end of @COPY FROM STDIN@; @'Just'@ aborts with the given error.+ putCopyEnd :: Maybe ByteString -> IO CopyInResult,+ -- | Receive data on a @COPY TO STDOUT@ connection. The @'Bool'@ selects+ -- non-blocking mode.+ getCopyData :: Bool -> IO CopyOutResult,+ -- | Create a new large object.+ loCreat :: IO (Maybe Word32),+ -- | Create a new large object with the given OID.+ loCreate :: Word32 -> IO (Maybe Word32),+ -- | Import a file as a new large object.+ loImport :: FilePath -> IO (Maybe Word32),+ -- | Import a file as a new large object with the given OID.+ loImportWithOid :: FilePath -> Word32 -> IO (Maybe Word32),+ -- | Export a large object to a file.+ loExport :: Word32 -> FilePath -> IO (Maybe ()),+ -- | Open a large object.+ loOpen :: Word32 -> IOMode -> IO (Maybe Int32),+ -- | Write to an open large object.+ loWrite :: Int32 -> ByteString -> IO (Maybe Int),+ -- | Read from an open large object.+ loRead :: Int32 -> Int -> IO (Maybe ByteString),+ -- | Seek within an open large object.+ loSeek :: Int32 -> SeekMode -> Int -> IO (Maybe Int),+ -- | Report the current seek position of an open large object.+ loTell :: Int32 -> IO (Maybe Int),+ -- | Truncate an open large object.+ loTruncate :: Int32 -> Int -> IO (Maybe ()),+ -- | Close an open large object.+ loClose :: Int32 -> IO (Maybe ()),+ -- | Remove a large object.+ loUnlink :: Word32 -> IO (Maybe ()),+ -- | The current client encoding name.+ clientEncoding :: IO ByteString,+ -- | Set the client encoding.+ setClientEncoding :: ByteString -> IO Bool,+ -- | Set error verbosity, returning the previous setting.+ setErrorVerbosity :: Verbosity -> IO Verbosity+ } - -- | Set the client encoding.- setClientEncoding :: c -> ByteString -> IO Bool+-- * Adapter - -- | Set error verbosity, returning the previous setting.- setErrorVerbosity :: c -> Verbosity -> IO Verbosity+-- | An adapter package's connection-establishing functions, bundled into one+-- value.+--+-- 'Connection', 'Result', and 'Cancel' are per-connection and per-result;+-- they carry no information about which adapter produced them, so they+-- cannot themselves stand in for "the FFI adapter" or "the native adapter"+-- the way a driver-parameterised type could. 'Adapter' fills that gap: it is+-- the one value that names an adapter and knows how to bring a 'Connection'+-- into being, so a caller that must remain adapter-agnostic (a differential+-- test harness comparing two adapters, or a library that lets its users pick+-- an adapter at runtime) can hold onto a single 'Adapter' value instead of a+-- family of adapter-qualified top-level functions.+--+-- Each adapter package (e.g. @pqi-ffi@, @pqi-native@) exports exactly one+-- top-level value of this type, conventionally named @adapter@.+data Adapter = Adapter+ { -- | A short identifier for the adapter (e.g. @\"pqi-ffi\"@), for use in+ -- test descriptions, logs, and error messages.+ name :: Text,+ -- | Connect to a database, as @PQconnectdb@.+ connectdb :: ByteString -> IO Connection,+ -- | Begin connecting to a database asynchronously, as @PQconnectStart@.+ connectStart :: ByteString -> IO Connection,+ -- | The null-sentinel connection, as @PQnewNullConnection@.+ newNullConnection :: IO Connection,+ -- | Convert the textual representation of a @bytea@ value, as produced+ -- by the server, back into raw bytes, as @PQunescapeBytea@.+ unescapeBytea :: ByteString -> IO (Maybe ByteString)+ }
− src/library/Pqi/UnescapeBytea.hs
@@ -1,158 +0,0 @@--- | Pure implementation of @bytea@ unescaping. See 'unescapeBytea'.-module Pqi.UnescapeBytea- ( unescapeBytea,- )-where--import Data.ByteString (ByteString)-import Data.Either (fromRight)-import Data.Word (Word8)-import PtrPeeker (Variable, fixed, hasMore, runVariableOnByteString, unsignedInt1)-import PtrPoker.Write (Write)-import qualified PtrPoker.Write as Write-import Prelude---- | Convert the textual representation of a @bytea@ value, as produced by--- the server, back into raw bytes. Both the modern @\\x@ hex format (lowercase--- @x@ only) and the legacy escape format are accepted.------ Malformed input is tolerated exactly the way @PQunescapeBytea@ tolerates it:--- in hex format, characters that are not hex digits (including whitespace) are--- silently skipped, and a hex digit whose pair character is invalid is--- dropped; in escape format, an invalid escape simply drops the backslash, and--- an octal escape must start with @0@..@3@. Input is treated as a C string:--- the first NUL byte terminates processing.-unescapeBytea :: ByteString -> ByteString-unescapeBytea input =- Write.toByteString $- fromRight mempty $- runVariableOnByteString decoder input---- Inline NUL truncation and \x prefix detection so no intermediate ByteStrings--- are allocated before dispatching to the format-specific decoder.-decoder :: Variable Write-decoder = do- more <- hasMore- if not more- then return mempty- else do- b0 <- fixed unsignedInt1- case b0 of- 0x00 -> return mempty -- NUL: C-string terminator- 0x5c -> do- -- backslash: probe for the \x hex-format prefix- more2 <- hasMore- if not more2- then return mempty -- single trailing backslash- else do- b1 <- fixed unsignedInt1- if b1 == 0x78 -- lowercase 'x': enter hex mode- then hexDecoder- else afterBackslash b1 -- escape mode; b1 follows the consumed '\'- _ -> (Write.word8 b0 <>) <$> escapeDecoder---- | Hex-format decoder. Skips non-hex bytes (matching @PQunescapeBytea@),--- pairs hex nibbles, and stops at a NUL byte (C-string terminator).-hexDecoder :: Variable Write-hexDecoder = do- more <- hasMore- if not more- then return mempty- else do- a <- fixed unsignedInt1- if a == 0x00- then return mempty -- NUL: stop- else case hexValue a of- Nothing -> hexDecoder -- skip non-hex byte- Just hi -> do- more2 <- hasMore- if not more2- then return mempty -- drop unpaired nibble- else do- b <- fixed unsignedInt1- if b == 0x00- then return mempty -- NUL: stop, drop unpaired nibble- else case hexValue b of- Nothing -> hexDecoder -- skip b, look for next pair- Just lo -> (Write.word8 (hi * 16 + lo) <>) <$> hexDecoder- where- hexValue :: Word8 -> Maybe Word8- hexValue w- | w >= 0x30 && w <= 0x39 = Just (w - 0x30)- | w >= 0x61 && w <= 0x66 = Just (w - 0x57)- | w >= 0x41 && w <= 0x46 = Just (w - 0x37)- | otherwise = Nothing---- | Escape-format decoder. Processes bytes as escape sequences and stops at--- a NUL byte (C-string terminator).-escapeDecoder :: Variable Write-escapeDecoder = do- more <- hasMore- if not more- then return mempty- else do- b <- fixed unsignedInt1- case b of- 0x00 -> return mempty- 0x5c -> handleEscapeBackslash- _ -> (Write.word8 b <>) <$> escapeDecoder---- | Handle the bytes that follow a consumed backslash in escape format.--- Exported so the top-level dispatcher can reuse it after consuming the--- @\\x@ prefix check.-afterBackslash :: Word8 -> Variable Write-afterBackslash next = case next of- 0x00 -> return mempty- 0x5c -> (Write.word8 0x5c <>) <$> escapeDecoder- _ -> octalOrLiteralDecoder next--handleEscapeBackslash :: Variable Write-handleEscapeBackslash = do- more <- hasMore- if not more- then return mempty -- trailing backslash: drop it- else do- next <- fixed unsignedInt1- afterBackslash next---- | Try to decode a 3-digit octal starting with @a@ (already consumed).--- Falls back to emitting @a@ literally and re-routing the consumed lookahead--- byte(s) through 'afterEscape', reproducing @PQunescapeBytea@'s backtracking.-octalOrLiteralDecoder :: Word8 -> Variable Write-octalOrLiteralDecoder a- | isFirstOctal a = do- more <- hasMore- if not more- then return (Write.word8 a)- else do- b <- fixed unsignedInt1- if not (isOctal b)- then (Write.word8 a <>) <$> afterEscape b -- b isn't octal: emit a, re-route b- else do- more2 <- hasMore- if not more2- then return (Write.word8 a <> Write.word8 b) -- only two digits: both literal- else do- c <- fixed unsignedInt1- if isOctal c- then (Write.word8 (octal a b c) <>) <$> escapeDecoder- else (\x -> Write.word8 a <> Write.word8 b <> x) <$> afterEscape c- | otherwise = (Write.word8 a <>) <$> escapeDecoder- where- -- \| Route an already-consumed byte back through the escape-format main loop.- -- Used when a consumed lookahead byte must be re-processed after a failed- -- octal-triple attempt.- afterEscape :: Word8 -> Variable Write- afterEscape b = case b of- 0x00 -> return mempty- 0x5c -> handleEscapeBackslash- _ -> (Write.word8 b <>) <$> escapeDecoder-- isFirstOctal :: Word8 -> Bool- isFirstOctal w = w >= 0x30 && w <= 0x33-- isOctal :: Word8 -> Bool- isOctal w = w >= 0x30 && w <= 0x37-- octal :: Word8 -> Word8 -> Word8 -> Word8- octal a b c = (a - 0x30) * 64 + (b - 0x30) * 8 + (c - 0x30)
− src/test/Spec.hs
@@ -1,58 +0,0 @@-module Main (main) where--import qualified Data.ByteString as ByteString-import Data.Foldable-import Data.Word-import qualified Database.PostgreSQL.LibPQ as LibPQ-import qualified Pqi-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.QuickCheck-import Prelude--main :: IO ()-main = hspec spec--spec :: Spec-spec =- describe "unescapeBytea" do- for_- [ "",- "\\x",- "\\x00",- "\\x00ff",- "\\x48656c6c6f",- "\\X48656C6C6F",- "\\xAbCd",- "Hello, world",- "h\233llo bytes",- "\\\\",- "\\001\\002\\003",- "a\\010b",- "\\x4",- "\\x4g",- "\\xzz",- "\\x61 62",- "\\377",- "\\400",- "\\000",- "\\1",- "\\18",- "\\8",- "a\\b",- "trailing\\",- "mixed\\134text"- ]- \input ->- it (show input) do- theirs <- LibPQ.unescapeBytea input- theirs `shouldBe` Just (Pqi.unescapeBytea input)-- -- PQunescapeBytea treats its input as a null-terminated C string, so- -- embedded NUL bytes would truncate it early. Exclude them.- prop "matches PQunescapeBytea on arbitrary input" $- \(bytes :: [Word8]) ->- ioProperty do- let input = ByteString.pack bytes- theirs <- LibPQ.unescapeBytea input- return $ theirs === Just (Pqi.unescapeBytea input)