pqi-ffi 0.0.1.1 → 0.1.0.0
raw patch · 6 files changed
+155/−149 lines, 6 filesdep ~pqidep ~pqi-conformancePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: pqi, pqi-conformance
API changes (from Hackage documentation)
- Pqi.Ffi: data Connection
- Pqi.Ffi: instance Pqi.IsCancel Pqi.Ffi.Cancel
- Pqi.Ffi: instance Pqi.IsConnection Pqi.Ffi.Connection
- Pqi.Ffi: instance Pqi.IsResult Pqi.Ffi.Result
+ Pqi.Ffi: adapter :: Adapter
Files
- CHANGELOG.md +5/−0
- README.md +5/−5
- pqi-ffi.cabal +3/−3
- src/library/Pqi/Ffi.hs +137/−135
- src/library/Pqi/Ffi/Prelude.hs +2/−2
- src/test/Spec.hs +3/−4
CHANGELOG.md view
@@ -0,0 +1,5 @@+# v0.1.0.0++## Breaking++- Migrate to `pqi` 0.1's record-of-functions redesign and switch to exporting the `adapter` value instead of the `Connection` type.
README.md view
@@ -7,11 +7,11 @@ backed by [`postgresql-libpq`](https://hackage.haskell.org/package/postgresql-libpq) (a binding to the C `libpq` library). -`Pqi.Ffi.Connection` wraps a C-backed `PGconn` and provides an-`IsConnection` instance whose every method is a near-mechanical delegation to-the matching `Database.PostgreSQL.LibPQ` function. The only work is converting-between this family's portable types (OIDs as `Word32`, indices as `Int32`,-the shared enums) and `postgresql-libpq`'s C-specific newtypes.+`Pqi.Ffi.adapter` produces `Pqi.Connection` values whose fields close over a+C-backed `PGconn`, each a near-mechanical delegation to the matching+`Database.PostgreSQL.LibPQ` function. The only work is converting between+this family's portable types (OIDs as `Word32`, indices as `Int32`, the+shared enums) and `postgresql-libpq`'s C-specific newtypes. This adapter is the **fidelity reference** for the `pqi` family: it is the oracle against which all other adapters are tested. The
pqi-ffi.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: pqi-ffi-version: 0.0.1.1+version: 0.1.0.0 category: Database, PostgreSQL synopsis: FFI adapter for pqi, backed by postgresql-libpq description:@@ -95,7 +95,7 @@ base >=4.11 && <5, bytestring >=0.10 && <0.13, postgresql-libpq >=0.11 && <0.12,- pqi ^>=0,+ pqi ^>=0.1, test-suite ffi-test import: test@@ -105,5 +105,5 @@ build-depends: base >=4.11 && <5, hspec >=2.11 && <2.12,- pqi-conformance,+ pqi-conformance ^>=0.1, pqi-ffi,
src/library/Pqi/Ffi.hs view
@@ -1,13 +1,16 @@ -- | The FFI adapter, backed by @postgresql-libpq@. ----- 'Connection' wraps the C-backed @PGconn@.+-- 'adapter' bundles the three functions that produce a 'Pqi.Connection'+-- whose fields are closures over the underlying C-backed @PGconn@.+-- 'Pqi.Result' and 'Pqi.Cancel' values are constructed the same way, closing+-- over the underlying @PGresult@\/@PGcancel@. ----- Each method is a near-mechanical delegation to the matching+-- Each closure is a near-mechanical delegation to the matching -- @Database.PostgreSQL.LibPQ@ function, with the only work being the -- conversion between this family's portable types (OIDs as 'Word32', indices -- as 'Int32', the shared enums) and @postgresql-libpq@'s C-specific newtypes. module Pqi.Ffi- ( Connection,+ ( adapter, ) where @@ -15,141 +18,140 @@ import qualified Pqi import Pqi.Ffi.Prelude --- | A handle to a PostgreSQL connection backed by the C @libpq@ library.-newtype Connection = Connection Pq.Connection---- | A result handle backed by a C @PGresult@.-newtype Result = Result Pq.Result---- | A cancellation handle backed by a C @PGcancel@.-newtype Cancel = Cancel Pq.Cancel--instance Pqi.IsResult Result where- resultStatus (Result r) = fromExecStatus <$> Pq.resultStatus r- resultErrorMessage (Result r) = Pq.resultErrorMessage r- resultErrorField (Result r) field = Pq.resultErrorField r (toFieldCode field)- unsafeFreeResult (Result r) = Pq.unsafeFreeResult r- ntuples (Result r) = fromRow <$> Pq.ntuples r- nfields (Result r) = fromColumn <$> Pq.nfields r- fname (Result r) column = Pq.fname r (toColumn column)- fnumber (Result r) name = fmap fromColumn <$> Pq.fnumber r name- ftable (Result r) column = fromOid <$> Pq.ftable r (toColumn column)- ftablecol (Result r) column = fromColumn <$> Pq.ftablecol r (toColumn column)- fformat (Result r) column = fromFormat <$> Pq.fformat r (toColumn column)- ftype (Result r) column = fromOid <$> Pq.ftype r (toColumn column)- fmod (Result r) column = Pq.fmod r (toColumn column)- fsize (Result r) column = Pq.fsize r (toColumn column)- getvalue (Result r) row column = Pq.getvalue r (toRow row) (toColumn column)- getvalue' (Result r) row column = Pq.getvalue' r (toRow row) (toColumn column)- getisnull (Result r) row column = Pq.getisnull r (toRow row) (toColumn column)- getlength (Result r) row column = Pq.getlength r (toRow row) (toColumn column)- nparams (Result r) = fromIntegral <$> Pq.nparams r- paramtype (Result r) index = fromOid <$> Pq.paramtype r (fromIntegral index)- cmdStatus (Result r) = Pq.cmdStatus r- cmdTuples (Result r) = Pq.cmdTuples r--instance Pqi.IsCancel Cancel where- cancel (Cancel handle) = Pq.cancel handle--instance Pqi.IsConnection Connection where- type ResultOf Connection = Result- type CancelOf Connection = Cancel-- connectdb conninfo = Connection <$> Pq.connectdb conninfo- connectStart conninfo = Connection <$> Pq.connectStart conninfo- connectPoll (Connection c) = fromPollingStatus <$> Pq.connectPoll c- newNullConnection = Connection <$> Pq.newNullConnection- isNullConnection (Connection c) = Pq.isNullConnection c- finish (Connection c) = Pq.finish c- reset (Connection c) = Pq.reset c- resetStart (Connection c) = Pq.resetStart c- resetPoll (Connection c) = fromPollingStatus <$> Pq.resetPoll c- db (Connection c) = Pq.db c- user (Connection c) = Pq.user c- pass (Connection c) = Pq.pass c- host (Connection c) = Pq.host c- port (Connection c) = Pq.port c- options (Connection c) = Pq.options c- status (Connection c) = fromConnStatus <$> Pq.status c- transactionStatus (Connection c) = fromTransactionStatus <$> Pq.transactionStatus c- parameterStatus (Connection c) name = Pq.parameterStatus c name- protocolVersion (Connection c) = Pq.protocolVersion c- serverVersion (Connection c) = Pq.serverVersion c- errorMessage (Connection c) = Pq.errorMessage c- socket (Connection c) = Pq.socket c- backendPID (Connection c) = fromIntegral <$> Pq.backendPID c- connectionNeedsPassword (Connection c) = Pq.connectionNeedsPassword c- connectionUsedPassword (Connection c) = Pq.connectionUsedPassword c-- exec (Connection c) sql =- fmap Result <$> Pq.exec c sql- execParams (Connection c) sql params resultFormat =- fmap Result <$> Pq.execParams c sql (fmap (fmap toParam) params) (toFormat resultFormat)- prepare (Connection c) name sql paramTypes =- fmap Result <$> Pq.prepare c name sql (fmap (fmap toOid) paramTypes)- execPrepared (Connection c) name params resultFormat =- fmap Result <$> Pq.execPrepared c name (fmap (fmap toBoundParam) params) (toFormat resultFormat)- describePrepared (Connection c) name =- fmap Result <$> Pq.describePrepared c name- describePortal (Connection c) name =- fmap Result <$> Pq.describePortal c name-- escapeStringConn (Connection c) = Pq.escapeStringConn c- escapeByteaConn (Connection c) = Pq.escapeByteaConn c- escapeIdentifier (Connection c) = Pq.escapeIdentifier c-- sendQuery (Connection c) sql = Pq.sendQuery c sql- sendQueryParams (Connection c) sql params resultFormat =- Pq.sendQueryParams c sql (fmap (fmap toParam) params) (toFormat resultFormat)- sendPrepare (Connection c) name sql paramTypes =- Pq.sendPrepare c name sql (fmap (fmap toOid) paramTypes)- sendQueryPrepared (Connection c) name params resultFormat =- Pq.sendQueryPrepared c name (fmap (fmap toBoundParam) params) (toFormat resultFormat)- sendDescribePrepared (Connection c) name = Pq.sendDescribePrepared c name- sendDescribePortal (Connection c) name = Pq.sendDescribePortal c name- getResult (Connection c) = fmap Result <$> Pq.getResult c- consumeInput (Connection c) = Pq.consumeInput c- isBusy (Connection c) = Pq.isBusy c- setnonblocking (Connection c) nonBlocking = Pq.setnonblocking c nonBlocking- isnonblocking (Connection c) = Pq.isnonblocking c- setSingleRowMode (Connection c) = Pq.setSingleRowMode c- flush (Connection c) = fromFlushStatus <$> Pq.flush c-- pipelineStatus (Connection c) = fromPipelineStatus <$> Pq.pipelineStatus c- enterPipelineMode (Connection c) = Pq.enterPipelineMode c- exitPipelineMode (Connection c) = Pq.exitPipelineMode c- pipelineSync (Connection c) = Pq.pipelineSync c- sendFlushRequest (Connection c) = Pq.sendFlushRequest c-- getCancel (Connection c) = fmap Cancel <$> Pq.getCancel c-- notifies (Connection c) = fmap fromNotify <$> Pq.notifies c- disableNoticeReporting (Connection c) = Pq.disableNoticeReporting c- enableNoticeReporting (Connection c) = Pq.enableNoticeReporting c- getNotice (Connection c) = Pq.getNotice c+-- | The FFI adapter.+adapter :: Pqi.Adapter+adapter =+ Pqi.Adapter+ { Pqi.name = "pqi-ffi",+ Pqi.connectdb = \conninfo -> mkConnection <$> Pq.connectdb conninfo,+ Pqi.connectStart = \conninfo -> mkConnection <$> Pq.connectStart conninfo,+ Pqi.newNullConnection = mkConnection <$> Pq.newNullConnection,+ Pqi.unescapeBytea = \input -> Pq.unescapeBytea input+ } - putCopyData (Connection c) value = fromCopyInResult <$> Pq.putCopyData c value- putCopyEnd (Connection c) reason = fromCopyInResult <$> Pq.putCopyEnd c reason- getCopyData (Connection c) nonBlocking = fromCopyOutResult <$> Pq.getCopyData c nonBlocking+-- | Build a 'Pqi.Connection' whose fields close over the given+-- @postgresql-libpq@ connection handle.+mkConnection :: Pq.Connection -> Pqi.Connection+mkConnection c =+ Pqi.Connection+ { Pqi.connectPoll = fromPollingStatus <$> Pq.connectPoll c,+ Pqi.isNullConnection = Pq.isNullConnection c,+ Pqi.finish = Pq.finish c,+ Pqi.reset = Pq.reset c,+ Pqi.resetStart = Pq.resetStart c,+ Pqi.resetPoll = fromPollingStatus <$> Pq.resetPoll c,+ Pqi.db = Pq.db c,+ Pqi.user = Pq.user c,+ Pqi.pass = Pq.pass c,+ Pqi.host = Pq.host c,+ Pqi.port = Pq.port c,+ Pqi.options = Pq.options c,+ Pqi.status = fromConnStatus <$> Pq.status c,+ Pqi.transactionStatus = fromTransactionStatus <$> Pq.transactionStatus c,+ Pqi.parameterStatus = \name -> Pq.parameterStatus c name,+ Pqi.protocolVersion = Pq.protocolVersion c,+ Pqi.serverVersion = Pq.serverVersion c,+ Pqi.errorMessage = Pq.errorMessage c,+ Pqi.socket = Pq.socket c,+ Pqi.backendPID = fromIntegral <$> Pq.backendPID c,+ Pqi.connectionNeedsPassword = Pq.connectionNeedsPassword c,+ Pqi.connectionUsedPassword = Pq.connectionUsedPassword c,+ Pqi.exec = \sql -> fmap mkResult <$> Pq.exec c sql,+ Pqi.execParams = \sql params resultFormat ->+ fmap mkResult <$> Pq.execParams c sql (fmap (fmap toParam) params) (toFormat resultFormat),+ Pqi.prepare = \name sql paramTypes ->+ fmap mkResult <$> Pq.prepare c name sql (fmap (fmap toOid) paramTypes),+ Pqi.execPrepared = \name params resultFormat ->+ fmap mkResult <$> Pq.execPrepared c name (fmap (fmap toBoundParam) params) (toFormat resultFormat),+ Pqi.describePrepared = \name -> fmap mkResult <$> Pq.describePrepared c name,+ Pqi.describePortal = \name -> fmap mkResult <$> Pq.describePortal c name,+ Pqi.escapeStringConn = \s -> Pq.escapeStringConn c s,+ Pqi.escapeByteaConn = \s -> Pq.escapeByteaConn c s,+ Pqi.escapeIdentifier = \s -> Pq.escapeIdentifier c s,+ Pqi.sendQuery = \sql -> Pq.sendQuery c sql,+ Pqi.sendQueryParams = \sql params resultFormat ->+ Pq.sendQueryParams c sql (fmap (fmap toParam) params) (toFormat resultFormat),+ Pqi.sendPrepare = \name sql paramTypes ->+ Pq.sendPrepare c name sql (fmap (fmap toOid) paramTypes),+ Pqi.sendQueryPrepared = \name params resultFormat ->+ Pq.sendQueryPrepared c name (fmap (fmap toBoundParam) params) (toFormat resultFormat),+ Pqi.sendDescribePrepared = \name -> Pq.sendDescribePrepared c name,+ Pqi.sendDescribePortal = \name -> Pq.sendDescribePortal c name,+ Pqi.getResult = fmap mkResult <$> Pq.getResult c,+ Pqi.consumeInput = Pq.consumeInput c,+ Pqi.isBusy = Pq.isBusy c,+ Pqi.setnonblocking = \nonBlocking -> Pq.setnonblocking c nonBlocking,+ Pqi.isnonblocking = Pq.isnonblocking c,+ Pqi.setSingleRowMode = Pq.setSingleRowMode c,+ Pqi.flush = fromFlushStatus <$> Pq.flush c,+ Pqi.pipelineStatus = fromPipelineStatus <$> Pq.pipelineStatus c,+ Pqi.enterPipelineMode = Pq.enterPipelineMode c,+ Pqi.exitPipelineMode = Pq.exitPipelineMode c,+ Pqi.pipelineSync = Pq.pipelineSync c,+ Pqi.sendFlushRequest = Pq.sendFlushRequest c,+ Pqi.getCancel = fmap mkCancel <$> Pq.getCancel c,+ Pqi.notifies = fmap fromNotify <$> Pq.notifies c,+ Pqi.disableNoticeReporting = Pq.disableNoticeReporting c,+ Pqi.enableNoticeReporting = Pq.enableNoticeReporting c,+ Pqi.getNotice = Pq.getNotice c,+ Pqi.putCopyData = \value -> fromCopyInResult <$> Pq.putCopyData c value,+ Pqi.putCopyEnd = \reason -> fromCopyInResult <$> Pq.putCopyEnd c reason,+ Pqi.getCopyData = \nonBlocking -> fromCopyOutResult <$> Pq.getCopyData c nonBlocking,+ Pqi.loCreat = fmap fromOid <$> Pq.loCreat c,+ Pqi.loCreate = \oid -> fmap fromOid <$> Pq.loCreate c (toOid oid),+ Pqi.loImport = \path -> fmap fromOid <$> Pq.loImport c path,+ Pqi.loImportWithOid = \path oid -> fmap fromOid <$> Pq.loImportWithOid c path (toOid oid),+ Pqi.loExport = \oid path -> Pq.loExport c (toOid oid) path,+ Pqi.loOpen = \oid mode -> fmap fromLibPQLoFd <$> Pq.loOpen c (toOid oid) mode,+ Pqi.loWrite = \fd value -> Pq.loWrite c (toLibPQLoFd fd) value,+ Pqi.loRead = \fd len -> Pq.loRead c (toLibPQLoFd fd) len,+ Pqi.loSeek = \fd mode offset -> Pq.loSeek c (toLibPQLoFd fd) mode offset,+ Pqi.loTell = \fd -> Pq.loTell c (toLibPQLoFd fd),+ Pqi.loTruncate = \fd len -> Pq.loTruncate c (toLibPQLoFd fd) len,+ Pqi.loClose = \fd -> Pq.loClose c (toLibPQLoFd fd),+ Pqi.loUnlink = \oid -> Pq.loUnlink c (toOid oid),+ Pqi.clientEncoding = Pq.clientEncoding c,+ Pqi.setClientEncoding = \encoding -> Pq.setClientEncoding c encoding,+ Pqi.setErrorVerbosity = \verbosity ->+ fromVerbosity <$> Pq.setErrorVerbosity c (toVerbosity verbosity)+ } - loCreat (Connection c) = fmap fromOid <$> Pq.loCreat c- loCreate (Connection c) oid = fmap fromOid <$> Pq.loCreate c (toOid oid)- loImport (Connection c) path = fmap fromOid <$> Pq.loImport c path- loImportWithOid (Connection c) path oid = fmap fromOid <$> Pq.loImportWithOid c path (toOid oid)- loExport (Connection c) oid path = Pq.loExport c (toOid oid) path- loOpen (Connection c) oid mode = fmap fromLibPQLoFd <$> Pq.loOpen c (toOid oid) mode- loWrite (Connection c) fd value = Pq.loWrite c (toLibPQLoFd fd) value- loRead (Connection c) fd len = Pq.loRead c (toLibPQLoFd fd) len- loSeek (Connection c) fd mode offset = Pq.loSeek c (toLibPQLoFd fd) mode offset- loTell (Connection c) fd = Pq.loTell c (toLibPQLoFd fd)- loTruncate (Connection c) fd len = Pq.loTruncate c (toLibPQLoFd fd) len- loClose (Connection c) fd = Pq.loClose c (toLibPQLoFd fd)- loUnlink (Connection c) oid = Pq.loUnlink c (toOid oid)+-- | Build a 'Pqi.Result' whose fields close over the given+-- @postgresql-libpq@ result handle.+mkResult :: Pq.Result -> Pqi.Result+mkResult r =+ Pqi.Result+ { Pqi.resultStatus = fromExecStatus <$> Pq.resultStatus r,+ Pqi.resultErrorMessage = Pq.resultErrorMessage r,+ Pqi.resultErrorField = \field -> Pq.resultErrorField r (toFieldCode field),+ Pqi.unsafeFreeResult = Pq.unsafeFreeResult r,+ Pqi.ntuples = fromRow <$> Pq.ntuples r,+ Pqi.nfields = fromColumn <$> Pq.nfields r,+ Pqi.fname = \column -> Pq.fname r (toColumn column),+ Pqi.fnumber = \name -> fmap fromColumn <$> Pq.fnumber r name,+ Pqi.ftable = \column -> fromOid <$> Pq.ftable r (toColumn column),+ Pqi.ftablecol = \column -> fromColumn <$> Pq.ftablecol r (toColumn column),+ Pqi.fformat = \column -> fromFormat <$> Pq.fformat r (toColumn column),+ Pqi.ftype = \column -> fromOid <$> Pq.ftype r (toColumn column),+ Pqi.fmod = \column -> Pq.fmod r (toColumn column),+ Pqi.fsize = \column -> Pq.fsize r (toColumn column),+ Pqi.getvalue = \row column -> Pq.getvalue' r (toRow row) (toColumn column),+ Pqi.getvalue' = \row column -> Pq.getvalue' r (toRow row) (toColumn column),+ Pqi.getisnull = \row column -> Pq.getisnull r (toRow row) (toColumn column),+ Pqi.getlength = \row column -> Pq.getlength r (toRow row) (toColumn column),+ Pqi.nparams = fromIntegral <$> Pq.nparams r,+ Pqi.paramtype = \index -> fromOid <$> Pq.paramtype r (fromIntegral index),+ Pqi.cmdStatus = Pq.cmdStatus r,+ Pqi.cmdTuples = Pq.cmdTuples r+ } - clientEncoding (Connection c) = Pq.clientEncoding c- setClientEncoding (Connection c) encoding = Pq.setClientEncoding c encoding- setErrorVerbosity (Connection c) verbosity =- fromVerbosity <$> Pq.setErrorVerbosity c (toVerbosity verbosity)+-- | Build a 'Pqi.Cancel' whose field closes over the given+-- @postgresql-libpq@ cancellation handle.+mkCancel :: Pq.Cancel -> Pqi.Cancel+mkCancel handle =+ Pqi.Cancel+ { Pqi.cancel = Pq.cancel handle+ } -- * Type conversions
src/library/Pqi/Ffi/Prelude.hs view
@@ -17,11 +17,11 @@ import Data.Ord as Exports import Data.Traversable as Exports import Data.Word as Exports-import System.IO as Exports (IO)-import Text.Show as Exports import Prelude as Exports ( Enum (..), Integral (..), Num (..), fromIntegral, )+import System.IO as Exports (IO)+import Text.Show as Exports
src/test/Spec.hs view
@@ -4,11 +4,10 @@ -- and tears the container down again. module Main (main) where -import Data.Proxy (Proxy (..)) import Pqi.Conformance (specs)-import Pqi.Ffi (Connection)-import Test.Hspec+import qualified Pqi.Ffi import Prelude+import Test.Hspec main :: IO ()-main = hspec (specs (Proxy @Connection))+main = hspec (specs Pqi.Ffi.adapter)