qail-pg 0.27.10

Rust PostgreSQL driver for typed AST queries with direct wire-protocol execution
Documentation

qail-pg

Rust PostgreSQL driver for typed AST queries and native wire-protocol execution

Crates.io License: Apache-2.0

A high-performance async PostgreSQL driver that speaks the wire protocol directly. qail-pg is the driver layer of Qail and is intended for backend code that wants AST-first query construction.

Positioning

qail-pg is a Rust PostgreSQL driver for teams that want:

  • a typed AST query surface instead of SQL-string-centric app code
  • explicit tenant-scoping APIs on query objects
  • direct wire-protocol execution with pipelining and COPY support

Quick Comparison

Need qail-pg tokio-postgres sqlx
Primary query API Typed Qail AST SQL strings SQL strings (+ checked macros)
App-side SQL interpolation path No on AST path Yes Yes
Built-in tenant context model Yes (RlsContext) App-managed App-managed
Auto-REST companion Yes (qail-gateway) No No

SQL String vs SQL Bytes

  • SQL string: text query built in app code (format/concat/interpolate).
  • SQL bytes: PostgreSQL frontend/backend protocol bytes (Parse, Bind, Execute, result frames) and encoded bind values.
  • What qail-pg does: compiles QAIL AST into protocol messages and typed values.
  • What PostgreSQL still does: server-side parse/plan/execute is still normal PostgreSQL behavior.

Legacy Syntax Notice

You may still find pre-1.0 search results showing symbolic QAIL strings like get::users•@id@email@role[active=true][lim=10] or old macro examples like qail!("get::users:'id'email [ 'active == true ]").

Those are historical docs from older releases. qail-pg 0.27.x is documented and optimized around the native AST path:

let query = Qail::get("users")
    .columns(["id", "email", "role"])
    .eq("active", true)
    .limit(10);

let rows = driver.fetch_all(&query).await?;

Inspect Real Wire Bytes

Run the built-in demo:

cargo run -p qail-pg --example wire_bytes_demo

For:

Qail::get("users")
    .select_all()
    .filter("active", Eq, true)

you will see:

  • SQL view: SELECT * FROM users WHERE active = $1
  • bind param $1: 74 ('t' for boolean true)
  • wire frames: Parse (P) + Bind (B) + Describe (D) + Execute (E) + Sync (S)

This shows the exact protocol-byte path used by the driver.

Features

  • AST-Native - Compiles QAIL AST directly to PostgreSQL wire protocol
  • 28% Faster - Benchmarked at 1.36M rows/s COPY (vs asyncpg at 1.06M rows/s)
  • Query Pipelining - 24x faster batch operations via pipeline_execute_count()
  • SSL/TLS - Production-ready with tokio-rustls
  • Password Auth Modes - Supports SCRAM-SHA-256, MD5, and cleartext server flows
  • Protocol 3.2 Ready - Requests startup protocol 3.2 by default with one-shot fallback to 3.0 on explicit protocol rejection
  • Cancel-Key Compatibility - Supports variable-length cancel keys via bytes-native APIs (legacy i32 wrappers retained for 4-byte keys)
  • Connection Pooling - Built-in PgPool
  • Transactions - Full begin/commit/rollback support

Installation

[!CAUTION] Release Candidate: QAIL is now in the release-candidate phase. The API is near-stable and battle-tested in production. Breaking changes are expected to be rare and limited to critical correctness/security fixes before 1.0.

[dependencies]
qail-pg = "0.27.10"
qail-core = "0.27.10"

qail-pg is AST-only. Raw SQL helper APIs were removed.

Quick Start

use qail_core::ast::{QailCmd, builders::*};
use qail_pg::PgDriver;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect with password
    let mut driver = PgDriver::connect_with_password(
        "localhost", 5432, "postgres", "mydb", "password"
    ).await?;

    // Build a query using QAIL AST
    let cmd = QailCmd::get("users")
        .columns([col("id"), col("name"), col("email")])
        .filter(eq("active", true))
        .order_by([("created_at", Desc)])
        .limit(10);

    // Execute and fetch rows
    let rows = driver.fetch_all(&cmd).await?;
    
    for row in rows {
        let name: String = row.get("name");
        println!("User: {}", name);
    }

    Ok(())
}

High-Performance Batch Operations

// Execute 10,000 queries in a single network round-trip
// pipeline_execute_count() uses AstPipelineMode::Auto by default
let cmds: Vec<QailCmd> = (0..10_000)
    .map(|i| QailCmd::add("events")
        .columns(["user_id", "event_type"])
        .values([Value::Int(i), Value::String("login".to_string())])
    ).collect();

let count = driver.pipeline_execute_count(&cmds).await?;
println!("Inserted {} rows", count);

// Override strategy explicitly when needed
let count_cached = driver
    .pipeline_execute_count_with_mode(&cmds, qail_pg::AstPipelineMode::Cached)
    .await?;

COPY Protocol (Bulk Insert)

use qail_pg::protocol::CopyEncoder;

// Build COPY data
let mut encoder = CopyEncoder::new();
for i in 0..1_000_000 {
    encoder.begin_row();
    encoder.write_i64(i);
    encoder.write_str(&format!("user_{}", i));
    encoder.end_row();
}

// Execute COPY
driver.copy_bulk_bytes("users", &["id", "name"], encoder.finish()).await?;

Connection Pooling

use qail_pg::PgPool;

// Create a pool with 10 connections
let pool = PgPool::new(
    "localhost", 5432, "postgres", "mydb", Some("password"), 10
).await?;

// Acquire a connection
let mut conn = pool.acquire().await?;
let rows = conn.fetch_all(&cmd).await?;

SSL/TLS Support

qail-pg uses tokio-rustls for TLS connections:

// SSL is auto-negotiated during connection
let driver = PgDriver::connect_with_password(
    "pg.example.com", 5432, "user", "db", "pass"
).await?;

Ergonomic Expression Builders

qail-pg works seamlessly with qail-core's ergonomic builders:

use qail_core::ast::builders::*;

// COUNT(*) FILTER (WHERE condition)
count_filter(vec![eq("status", "active")]).alias("active_count")

// NOW() - INTERVAL '24 hours'
now_minus("24 hours")

// CASE WHEN ... ELSE ... END
case_when(gt("score", 80), text("pass"))
    .otherwise(text("fail"))
    .alias("result")

// Type casting
cast(col("amount"), "float8")

Type Support

PostgreSQL Type Rust Type
text, varchar String
int4, int8 i32, i64
float8 f64
bool bool
uuid uuid::Uuid
jsonb serde_json::Value
timestamp chrono::DateTime<Utc>
date chrono::NaiveDate
numeric rust_decimal::Decimal

License

Apache-2.0

🤝 Contributing & Support

We welcome issue reports on GitHub! Please provide detailed descriptions to help us reproduce and fix the problem. We aim to address critical issues within 1-5 business days.