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

See all snapshots pqi appears in

MIT licensed and maintained by Nikita Volkov
This version can be pinned in stack with:pqi-1.1.0.1@sha256:19b97130418ec6e399d88d641abb2a81f5026f05cc4232c8286d7d4b69ec4c9e,2730

Module documentation for 1.1.0.1

Depends on 3 packages(full list with versions):
Used by 3 packages in nightly-2026-08-19(full list with versions):

pqi

Hackage Continuous Haddock

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 around postgresql-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 to postgresql-libpq for all protocol-derived values (verified by differential testing), but it has not yet been exercised in production at scale. Because it implements the same pqi interface as pqi-ffi, the two are fully interchangeable - switching between them is a one-line change, so adopting pqi-native now 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 C libpq library via postgresql-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, 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 are a plain Int32, instead of the C-specific newtypes of the original.
  • There’s no invalidOid constant. It’s just 0.
  • libpqVersion is omitted.
  • unescapeBytea and resStatus are fields of Adapter rather 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 Notify fields to match postgresql-libpq exactly: relnamenotifyRelname, bePidnotifyBePid, extranotifyExtra

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 resStatus field to Adapter

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