pqi: Driver-agnostic interface to the PostgreSQL libpq API

[ database, library, mit, postgresql ] [ Propose Tags ] [ Report a vulnerability ]

pqi reproduces the API surface of the postgresql-libpq package, but reifies the connection (and its results) as a plain record of closures instead of a single concrete type tied to libpq. This lets callers program against one interface and pick an adapter package to construct it:

  • 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. Alpha, and fully interchangeable with pqi-ffi: switching between them is a one-line Adapter change.

This package ships only the interface: the record types, and an Adapter type for adapter packages to bundle their connection-establishing functions under.


[Skip to Readme]

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.0.1.0, 0.0.1.1, 0.1.0.0, 0.1.0.1, 0.1.0.2
Change log CHANGELOG.md
Dependencies base (>=4.11 && <5), bytestring (>=0.10 && <0.13), text (>=1.2 && <2.2) [details]
License MIT
Copyright (c) 2026, Nikita Volkov
Author Nikita Volkov <nikita.y.volkov@mail.ru>
Maintainer Nikita Volkov <nikita.y.volkov@mail.ru>
Uploaded by NikitaVolkov at 2026-08-05T10:29:38Z
Category Database, PostgreSQL
Home page https://github.com/nikita-volkov/pqi
Bug tracker https://github.com/nikita-volkov/pqi/issues
Source repo head: git clone https://github.com/nikita-volkov/pqi
Distributions
Reverse Dependencies 3 direct, 0 indirect [details]
Downloads 14 total (8 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2026-08-05 [all 1 reports]

Readme for pqi-0.1.0.2

[back to package description]

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 byte-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 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 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 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, 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.
  • Ambiguous, rarely-useful helpers (e.g. resStatus) are omitted, as is libpqVersion.
  • unescapeBytea is a field of Adapter rather than a connection-independent top-level function, since its implementation is adapter-specific.