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. The user picks an adapter at connection time:
-- C-backed (safe, requires libpq)
connection <- connect (Proxy @Pqi.Ffi.Connection) settings
-- Pure Haskell (experimental, no C dependency)
connection <- connect (Proxy @Pqi.Native.Connection) 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 it produces — as a type
class instead of a single concrete type. 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 IsConnection class, the associated
ResultOf result type family, the shared type vocabulary (statuses, field
codes, formats, OIDs), and the connection-independent helpers.
Relationship to postgresql-libpq
The function names, argument order, and semantics mirror
Database.PostgreSQL.LibPQ. The deliberate departures are:
ConnectionandResultbecome the class parametercand the associated type familyResultOf c.- 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.
- base >=4.11 && <5
- bytestring >=0.10 && <0.13
- ptr-peeker >=0.2 && <0.3
- ptr-poker >=0.1 && <0.2
- 0.0.1.1
- 0.0.1.0