pqi
Driver-agnostic interface to the PostgreSQL libpq API
https://github.com/nikita-volkov/pqi
| Stackage Nightly 2026-08-19: | 1.1.0.1 |
| Latest on Hackage: | 1.1.0.1 |
pqi-1.1.0.1@sha256:19b97130418ec6e399d88d641abb2a81f5026f05cc4232c8286d7d4b69ec4c9e,2730Module documentation for 1.1.0.1
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. Alpha - interchangeable with pqi-ffi |
| 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 from-scratch pure-Haskell implementation of the PostgreSQL wire protocol. Alpha. It produces identical output topostgresql-libpqfor all protocol-derived values (verified by differential testing), but it has not yet been exercised in production at scale. Because it implements the samepqiinterface aspqi-ffi, the two are fully interchangeable - switching between them is a one-line change, so adoptingpqi-nativenow carries no lock-in. 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 (alpha, no C dependency)
connection <- connectdb Pqi.Native.adapter settings
Testing model
pqi comes accompanied by a 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 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. libpqVersionis omitted.unescapeByteaandresStatusare fields ofAdapterrather than connection-independent top-level functions, since their implementations are adapter-specific.
Changes
v1.1.0.1
Doc corrections.
v1.1.0.0
Breaking
- Renamed
Notifyfields to matchpostgresql-libpqexactly:relname→notifyRelname,bePid→notifyBePid,extra→notifyExtra
v1.0.0.3
Doc corrections.
v1.0.0.2
Release process fixes.
v1.0.0.1
Documentation corrections.
v1.0.0.0
Breaking
- Add the
resStatusfield toAdapter
v0.1.0.2
Polish the docs.
v0.1.0.1
Corrected the readme.
v0.1.0.0
Breaking
- Replace IsConnection/IsResult/IsCancel type classes with concrete records
- Move unescapeBytea into Adapter
Non-breaking
- Add the Adapter type