peque 0.1.0

Light postgresql binding to libpq (ImportC)


To use this package, run the following command in your project's root directory:

Manual usage
Put the following dependency into your project's dependences section:

Peque — PostgreSQL client for D

Peque is a lightweight libpq wrapper for the D programming language.

It uses SafeRefCounted (from std.typecons) to manage libpq objects deterministically — connections and results are freed as soon as they go out of scope, without depending on the GC.

Features

  • Reference-counted Connection and Result — deterministic cleanup, no GC dependency
  • Parameterized queries via execParams — SQL injection safe by design
  • Automatic bidirectional type conversion between PostgreSQL text format and D types
  • Nullable!T support for NULL columns
  • RAII transaction helper with auto-commit / auto-rollback on exception
  • Configurable transaction isolation level (readCommitted, repeatableRead, serializable, serverDefault)
  • Savepoint support for partial rollbacks within a transaction
  • Static or dynamic (bindbc-loader) loading of libpq

Supported types

D typePostgreSQL type
stringtext, varchar, char, …
JSONValuejson, jsonb
int, long, shortinteger, bigint, smallint
float, doublereal, double precision
boolboolean
Datedate
DateTimetimestamp
SysTimetimestamptz
T[]one-dimensional arrays
Nullable!Tany nullable column

Installation

dub.json:

"dependencies": {
    "peque": "~>0.1.0"
}

dub.sdl:

dependency "peque" version="~>0.1.0"

Choose a configuration depending on how you want to link libpq:

ConfigurationDescription
libraryStaticStatic library — links libpq directly
libraryDynamicDynamic library — loads libpq at runtime via bindbc-loader

Quick start

import peque;
import std.stdio;

auto c = Connection(
    dbname: "mydb",
    user:   "myuser",
    password: "secret",
    host:   "localhost",
    port:   "5432",
);

// Raw SQL — fine for DDL or trusted input
c.exec("
    CREATE TABLE IF NOT EXISTS items (id serial, name text, qty int);
    INSERT INTO items (name, qty) VALUES ('apple', 5), ('banana', 3);
");

// Parameterized query — SQL-injection safe
auto res = c.execParams(
    "SELECT name, qty FROM items WHERE qty > $1", 2);

foreach (row; res)
    writeln(row["name"].get!string, ": ", row["qty"].get!int);

// Nullable column
auto maybeQty = res[0]["qty"].get!(Nullable!int);
if (!maybeQty.isNull)
    writeln(maybeQty.get);

Transactions

Connection.transaction() runs a delegate inside a BEGIN/COMMIT block. On exception the transaction is always rolled back. The delegate receives a ref Transaction handle that exposes exec, execParams, and escapeString but intentionally hides commit() and rollback() — making accidental early termination of the transaction a compile-time error rather than a silent runtime bug.

c.transaction((ref tx) {
    tx.execParams("INSERT INTO items (name, qty) VALUES ($1, $2)", "cherry", 10);
    tx.execParams("UPDATE items SET qty = qty - 1 WHERE name = $1", "apple");
});

transaction() can return a value:

auto newQty = c.transaction((ref tx) {
    tx.execParams("UPDATE items SET qty = qty - 1 WHERE name = $1", "apple");
    return tx.execParams(
        "SELECT qty FROM items WHERE name = $1", "apple")[0][0].get!int;
});

Use OnSuccess.rollback for dry-runs or test helpers that must not persist changes:

c.transaction!(OnSuccess.rollback)((ref tx) {
    tx.execParams("DELETE FROM items");
    auto count = tx.execParams("SELECT count(*) FROM items")[0][0].get!long;
    assert(count == 0);
    // transaction is rolled back after the delegate returns
});

Business logic functions take ref Transaction and are completely unaware of nesting depth:

void deductStock(ref Transaction tx, string item, int qty) {
    tx.execParams(
        "UPDATE items SET qty = qty - $1 WHERE name = $2", qty, item);
}

c.transaction((ref tx) { deductStock(tx, "apple", 1); });

Savepoints

Transaction.savepoint() creates a PostgreSQL savepoint. On exception, only the savepoint changes are rolled back — the enclosing transaction remains open and intact. The delegate receives the same ref Transaction, so the same business logic functions work at any nesting depth without modification.

c.transaction((ref tx) {
    tx.execParams("INSERT INTO items (name, qty) VALUES ($1, $2)", "date", 7);

    try {
        tx.savepoint((ref tx) {
            tx.execParams(
                "INSERT INTO items (name, qty) VALUES ($1, $2)", "elderberry", 2);
            throw new Exception("changed my mind");
            // only the elderberry insert is rolled back
        });
    } catch (Exception e) {}

    // date is still in the transaction and will be committed
});

Savepoints can be nested arbitrarily. OnSuccess.rollback works on savepoints too — useful for dry-run sub-operations inside a larger transaction.

Isolation levels

The IsolationLevel template parameter controls the PostgreSQL transaction isolation level. The default is readCommitted, which is always set explicitly in the BEGIN statement regardless of server configuration — so the behaviour is predictable even if the server or role has a different default_transaction_isolation configured.

import peque;

// Read committed (default) — explicit in BEGIN, immune to server config
c.transaction((ref tx) { ... });

// Repeatable read
c.transaction!(OnSuccess.commit, IsolationLevel.repeatableRead)((ref tx) { ... });

// Serializable — may throw on conflict, application must be prepared to retry
c.transaction!(OnSuccess.commit, IsolationLevel.serializable)((ref tx) { ... });

// Server default — emits plain BEGIN, defers to postgresql.conf / ALTER ROLE / ALTER DATABASE
c.transaction!(OnSuccess.commit, IsolationLevel.serverDefault)((ref tx) { ... });
ValueBEGIN emittedNotes
readCommitted (default)BEGIN ISOLATION LEVEL READ COMMITTEDPredictable regardless of server config
repeatableReadBEGIN ISOLATION LEVEL REPEATABLE READ
serializableBEGIN ISOLATION LEVEL SERIALIZABLEMay abort; application must retry
serverDefaultBEGINRespects server/role/database configuration

Running tests

Integration tests require a running PostgreSQL instance. Configure via environment variables (defaults shown):

POSTGRES_DB=peque-test \
POSTGRES_USER=peque \
POSTGRES_PASSWORD=peque \
POSTGRES_HOST=localhost \
POSTGRES_PORT=5432 \
dub test --config=unittestStatic

License

Mozilla Public License 2.0

Authors:
  • Dmytro Katyukha
Dependencies:
bindbc-common, versioned
Versions:
0.1.0 2026-Feb-22
0.0.6 2026-Jan-04
0.0.5 2025-Jun-03
0.0.4 2025-May-22
0.0.3 2025-Apr-20
Show all 12 versions
Download Stats:
  • 0 downloads today

  • 41 downloads this week

  • 631 downloads this month

  • 6552 downloads total

Score:
2.5
Short URL:
peque.dub.pm