oxisql-postgres 0.3.2

Pure-Rust PostgreSQL backend for OxiSQL via tokio-postgres + OxiTLS (no libpq, no OpenSSL)
Documentation

oxisql-postgres — Pure-Rust PostgreSQL backend for OxiSQL

PostgreSQL backend over the tokio-postgres wire protocol with OxiTLS / RustCrypto TLS (no libpq, no openssl-sys); COPY, LISTEN/NOTIFY, pipeline batching, and extended type mapping.

Crates.io License: Apache-2.0 MSRV: 1.89 Pure Rust: C-free

Status: Stable.

What it is

oxisql-postgres provides PgConnection, which implements oxisql_core::Connection over tokio-postgres — speaking the PostgreSQL Frontend/Backend Protocol version 3 directly on the wire. There is no C client library: no libpq, no libssl, no openssl-sys, and no ring. TLS is routed entirely through OxiTLS and the rustls + rustls-rustcrypto RustCrypto provider, so a build of this crate is 100% Pure Rust and needs no system C toolchain or *-dev headers.

Beyond plain execute/query, the crate exposes the higher-value parts of the PostgreSQL protocol that thin wrappers usually omit: the COPY bulk load/unload protocol, LISTEN/NOTIFY asynchronous notifications delivered as a Stream, pipeline batching (many statements flushed in a single network round-trip), prepared-statement caching, and an extended type mapping that round-trips all 13 oxisql_core::Value variants (UUID, JSONB, NUMERIC, arrays, timestamps, …).

It is part of the OxiSQL Pure-Rust workspace and is the backend selected when you open a postgres:// URL through the oxisql facade.

Installation

[dependencies]
oxisql-postgres = "0.1.2"

MSRV 1.89 · edition 2021 · Apache-2.0.

Quick start

use oxisql_postgres::{PgConnection, TlsMode};
use oxisql_core::Connection;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Plain TCP (suitable for localhost / trusted networks).
    let conn = PgConnection::connect(
        "host=localhost port=5432 user=postgres password=secret dbname=mydb",
        TlsMode::Disabled,
    ).await?;

    conn.execute("CREATE TABLE IF NOT EXISTS t (id BIGINT, val TEXT)", &[]).await?;
    conn.execute("INSERT INTO t VALUES ($1, $2)", &[&1i64, &"hello"]).await?;

    let rows = conn.query("SELECT id, val FROM t WHERE id = $1", &[&1i64]).await?;
    let id: i64 = rows[0].try_get("id")?;
    println!("id = {id}");
    Ok(())
}

TLS via OxiTLS (RustCrypto provider)

use oxisql_postgres::{PgConnection, TlsMode};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Trust the Mozilla CA bundle, built with the pure-Rust RustCrypto provider.
    let root_store = oxitls::webpki_root_certs();
    let client_cfg = oxitls::client_config(root_store)
        .map_err(|e| format!("TLS config: {e}"))?;

    let conn = PgConnection::connect(
        "host=db.example.com port=5432 user=postgres dbname=mydb sslmode=require",
        TlsMode::Rustls(client_cfg),
    ).await?;
    let _ = conn;
    Ok(())
}

Two ready-made TlsMode constructors cover common cases without hand-building a ClientConfig: TlsMode::skip_verify() (accept any server certificate — dev only) and TlsMode::with_ca_pem(pem_bytes) (trust a custom CA from PEM). Both default to the RustCrypto crypto provider.

Fluent builder

use oxisql_postgres::{PgConnectionBuilder, TlsMode};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let conn = PgConnectionBuilder::new()
        .host("localhost")
        .port(5432)
        .user("postgres")
        .password("secret")
        .dbname("mydb")
        .connect_timeout_secs(5)
        .tls_mode(TlsMode::Disabled)
        .connect()
        .await?;
    let _ = conn;
    Ok(())
}

Key API

Item Description
PgConnection::connect(conn_str, tls) Connect with a libpq-style key=value string or a postgres:// / postgresql:// URL (auto-detected)
PgConnection::connect_with_timeout(conn_str, tls, dur) As above, wrapped in a Tokio timeout
PgConnection::connect_skip_verify(conn_str) / connect_with_ca(conn_str, pem) TLS connect shortcuts
PgConnection::from_client(client) Wrap an existing tokio_postgres::Client
PgConnectionBuilder Fluent config: host / port / user / password / dbname / connect_timeout_secs / tls_mode (also tls_skip_verify / tls_with_ca_pem) → connect()
TlsMode Disabled or Rustls(ClientConfig); constructors skip_verify(), with_ca_pem(pem)
PgTransaction oxisql_core::Transaction with savepoint / release_savepoint / rollback_to_savepoint
PgPrepared Caches tokio_postgres::Statement, keyed by a hash of the SQL text
PgPipeline add_execute(sql, params) / add_query(sql, params) / finish() → PipelineResult — batch in one round-trip
NotificationStream / PgNotification Async stream returned by listen(channel)
ColumnDescription Column name + type metadata via describe(sql) (no execution)
parse_pg_conn_str(s) → PgConnParts Parse a connection string / URL into its parts

Capabilities

COPY — bulk load / unload (src/copy.rs)

# async fn run(conn: &oxisql_postgres::PgConnection) -> Result<(), Box<dyn std::error::Error>> {
// Bulk-insert via COPY ... FROM STDIN (text/TSV).
let rows = vec![
    vec!["1".to_string(), "alice".to_string()],
    vec!["2".to_string(), "bob".to_string()],
];
let n = conn.copy_in_text("users", &["id", "name"], rows.into_iter()).await?;

// Extract rows via COPY ... TO STDOUT.
let out: Vec<Vec<String>> = conn.copy_out_text("users", &["id", "name"]).await?;
let _ = (n, out);
# Ok(())
# }

LISTEN / NOTIFY (src/notify.rs)

# async fn run(conn: &oxisql_postgres::PgConnection) -> Result<(), Box<dyn std::error::Error>> {
use std::time::Duration;

let mut stream = conn.listen("my_channel").await?;   // NotificationStream
conn.notify("my_channel", "ping").await?;
if let Some(n) = stream.recv_timeout(Duration::from_secs(1)).await {
    println!("{} -> {}", n.channel, n.payload);       // PgNotification
}
stream.unlisten().await?;
# Ok(())
# }

Notifications are only routed on connections created via PgConnection::connect (the background driver that dispatches NotificationResponse is spawned there); they are not available on a from_client connection.

Pipeline batching (src/pipeline.rs)

# async fn run(conn: &oxisql_postgres::PgConnection) -> Result<(), Box<dyn std::error::Error>> {
let mut pipeline = conn.pipeline();
pipeline.add_execute("INSERT INTO t VALUES ($1)", &[&1i64]);
pipeline.add_query("SELECT * FROM t", &[]);
let result = pipeline.finish().await?;   // PipelineResult, single round-trip
let _ = result;
# Ok(())
# }

Prepared statements, describe & introspection

prepare(sql) returns a PgPrepared whose underlying Statement is cached by SQL hash, so re-preparing the same text reuses the server-side plan. describe(sql) returns Vec<ColumnDescription> without executing the query. Schema introspection (tables, columns, indexes, foreign_keys) is served from information_schema and pg_indexes.

Type mapping

All 13 oxisql_core::Value variants round-trip. Parameters are encoded with explicit type OIDs; results can be requested in binary format (query_binary).

PostgreSQL type oxisql_core::Value
BOOL Bool
INT2 / INT4 / INT8 I64
FLOAT4 / FLOAT8 F64
TEXT / VARCHAR / BPCHAR Text
BYTEA Blob
TIMESTAMP / TIMESTAMPTZ Timestamp (µs since epoch)
DATE Date (days since epoch)
TIME Time (µs since midnight)
UUID Uuid (u128)
JSONB Json
NUMERIC Decimal (string)
ARRAY (e.g. INT[], TEXT[]) Array

Test coverage

61 tests passing under cargo test (which includes doctests). In addition, 6 integration tests are live-server-gated (TLS live connect, COPY IN/OUT, and 4× LISTEN/NOTIFY). These live-server tests are marked #[ignore] because they require a real PostgreSQL instance; the remaining #[ignore]d integration tests (CRUD cycles, isolation, reconnect, pooling) are also gated the same way and are skipped when no server is available. Enable them with a running server and the integration-postgres feature.

cargo test -p oxisql-postgres                              # 61 passed (incl. doctests), live tests skipped
cargo nextest run -p oxisql-postgres --run-ignored all     # also runs live-server tests (nextest excludes doctests)

Part of the OxiSQL workspace

This crate is one of 17 crates in the Pure-Rust OxiSQL workspace (1,720 workspace tests pass). It is the PostgreSQL backend behind the unified oxisql facade; see the workspace README for the embedded engine, MySQL backend, connection pool, migrations, and DataFusion integration.

License

Apache-2.0 — Copyright © 2024–2026 COOLJAPAN OU (Team Kitasan).