pqi
Driver-agnostic interface to the PostgreSQL libpq API
pqi
A driver-agnostic interface to the PostgreSQL libpq API.
Ecosystem
| Package | Description |
|---|---|
| pqi (this) | The interface: IsConnection class, shared types, and connection-independent helpers |
| pqi-ffi | FFI adapter backed by postgresql-libpq and the C libpq library. Battle-tested, production-safe |
| pqi-native | Pure-Haskell adapter speaking the PostgreSQL wire protocol directly. No C dependency. Experimental |
| pqi-conformance | Reusable hspec conformance suite that differentially tests any adapter against postgresql-libpq |
Motivation
Every major Haskell PostgreSQL driver today depends on
postgresql-libpq, a binding to the C libpq library.
This means every user of every driver needs libpq installed — on their
development machine, in CI, in production containers, on cross-compilation
targets. There is no way to opt out.
pqi solves this by separating the interface from the implementation. It
defines a driver-agnostic type class (IsConnection) that mirrors the libpq
API surface, then ships two adapters:
pqi-ffi— a thin wrapper aroundpostgresql-libpq. Battle-tested, production-safe. The default choice.pqi-native— a pure-Haskell implementation of the PostgreSQL wire protocol, generated with LLM assistance. Experimental. It produces byte-identical output topostgresql-libpqfor all protocol-derived values (verified by differential testing), but it has not yet been exercised in production at scale. If you adopt it, we want to hear from you.
A driver built against pqi gives its users transport choice without any
changes to the driver itself. Each adapter package exports a single
top-level Adapter value bundling its connection-establishing functions;
the user picks one at connection time:
-- C-backed (safe, requires libpq)
connection <- connectdb Pqi.Ffi.adapter settings
-- Pure Haskell (experimental, no C dependency)
connection <- connectdb Pqi.Native.adapter settings
Testing model
pqi comes accompanied by a comprehensive conformance suite isolated into an implementation-agnostic pqi-conformance package that covers various edge-cases and error conditions and covers most operations with a precondition that they must behave in exactly the same way that postgresql-libpq does.
Interface
pqi reproduces the API surface of the postgresql-libpq
package, but reifies the connection — and the results and cancellation
handles it produces — as plain records of closures instead of a single
concrete type tied to libpq. There is exactly one Connection, one
Result, and one Cancel type in the whole package; each field is an IO
action that an adapter has already closed over its own underlying handle
(a C PGconn pointer, a native socket, etc.). Code written against this
interface runs unchanged on any adapter:
pqi-ffi— a thin adapter backed by the Clibpqlibrary viapostgresql-libpq.pqi-native— a pure-Haskell adapter that speaks the PostgreSQL wire protocol directly.
The interface mirrors libpq in semantics, not just shape: every compliant
adapter must produce byte-identical output to libpq for all protocol-derived
values. This contract is enforced by
pqi-conformance, which
runs every operation differentially against postgresql-libpq and asserts equality.
This package ships only the interface: the Connection, Result, and
Cancel records, the Adapter type that adapter packages bundle their
connection-establishing functions under, and the shared type vocabulary
(statuses, field codes, formats, OIDs). It does not itself construct any
connections — that's each adapter package's job, exposed as a single
top-level adapter :: Adapter value.
Relationship to postgresql-libpq
The function names, argument order, and semantics mirror
Database.PostgreSQL.LibPQ. The deliberate departures are:
Connection,Result, andCancelare plain records of closures rather than a class-parameterised type and its associated types.- OIDs are a plain
Word32and row/column/parameter indices are a plainInt32, instead of the C-specific newtypes of the original. - There's no
invalidOidconstant. It's just0. - Ambiguous, rarely-useful helpers (e.g.
resStatus) are omitted, as islibpqVersion. unescapeByteais a field ofAdapterrather than a connection-independent top-level function, since its implementation is adapter-specific.
- base >=4.11 && <5
- bytestring >=0.10 && <0.13
- text >=1.2 && <2.2
- 0.1.0.1
- 0.1.0.0
- 0.0.1.1
- 0.0.1.0