Skip to content

qail-io/qail

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

220 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Qail — Rust PostgreSQL Driver + AST Toolkit

Qail is a Rust PostgreSQL toolkit that makes typed AST queries the default for safe, tenant-isolated execution, with optional auto-REST/WebSocket exposure.

Crates.io Crates.io Docs License Version


Start Here (Driver First)

If you are searching for a Rust PostgreSQL driver, start with qail-pg + qail-core.

[dependencies]
qail-core = "0.27.10"
qail-pg = "0.27.10"
use qail_core::prelude::*;
use qail_pg::PgDriver;

let mut driver = PgDriver::connect("localhost", 5432, "user", "mydb").await?;
let ctx = RlsContext::tenant(tenant_id);

let query = Qail::get("users")
    .columns(["id", "email"])
    .eq("active", true)
    .with_rls(&ctx);

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

Product Map

Component Use it for Primary audience
qail-pg PostgreSQL driver (wire protocol, pooling, pipeline, COPY) Backend app code
qail-core Typed AST, parser, validator, RLS model, transpiler Query/model layer
qail-gateway Optional auto-REST + WebSocket server API platform teams
qail (CLI) Schema pull/diff, migrations, codegen, lint DevEx and CI

Choose Your Mode

  1. Driver mode: add qail-pg and execute AST queries in your app.
  2. Gateway mode: run qail-gateway when you want auto-generated REST/WebSocket.
  3. Tooling mode: use qail CLI for schema and migration workflows.

qail-pg vs tokio-postgres vs sqlx

Need qail-pg tokio-postgres sqlx
Query API Typed Qail AST SQL strings SQL strings (+ compile-time checked macros)
App-side SQL interpolation path No on AST path Yes Yes
Tenant scoping model Built-in RLS context APIs App-managed App-managed
Auto-REST/WebSocket companion Yes (qail-gateway) No No

Use qail-pg when your priority is typed query construction, tenant isolation discipline, and direct wire-level execution from Rust.

Problem It Solves

Every SaaS backend has the same three recurring failure modes:

  1. N+1 query explosions
  2. SQL injection from ad-hoc string assembly
  3. Tenant data leaks from missing scope filters

Qail addresses these by making AST-first query execution and explicit tenant context the default.

SQL String vs SQL Bytes (Exact Meaning)

  • SQL string: query text assembled in app code (manual concatenation/interpolation, dynamic formatting, etc.).
  • SQL bytes: frontend/backend protocol frames and typed bind-value bytes emitted by the driver.
  • Qail claim: "no SQL strings" means no application-level string interpolation path on the AST flow.
  • PostgreSQL behavior: PostgreSQL still performs normal parse/plan/execute on received statements (or reuses prepared plans).

Legacy Syntax Notice

Older pre-1.0 QAIL experiments used symbolic text forms such as get::users•@id@email@role[active=true][lim=10] and macro examples such as qail!("get::users:'id'email [ 'active == true ]").

Those forms are historical and are not the canonical 0.27.x API surface. Current application code should use the AST/DSL path:

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

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

Benchmark: Pattern Cost Under RTT

We benchmarked six execution patterns for the same endpoint objective and canonical payload shape (id, name, origin_harbor, dest_harbor) on real PostgreSQL data. The pattern differs (single JOIN vs batched vs N+1), but output is validated as equivalent before timing.

Snapshot runs: March 25, 2026 (--release, BATTLE_ITERATIONS=200, warmup 15, global warmup 15).

Network Profile Qail AST (uncached, 1 query) Gateway/REST ?expand= (1 query) GraphQL + DataLoader (2 queries) GraphQL naive (N+1, 101 queries)
Loopback (BATTLE_SIMULATED_RTT_US=0) 146.9us 164.5us 146.8us 4.74ms
+250us/query RTT 475.4us 486.4us 779.4us 35.1ms
+1000us/query RTT 1237.8us 1248.4us 2287.0us 111.6ms

Main signal: on loopback, single-query and DataLoader are close; once RTT is non-trivial, fewer round trips dominate latency.

Methodology notes (click to expand)
  • Schema used: current swb_staging_local schema (odyssey_connections + harbors) with a 2×LEFT JOIN shape.
  • Equivalence gate: benchmark aborts if any approach does not produce the same canonical payload.
  • Cache equalization: global warmup before timed runs.
  • Config emitted: iterations, per-approach warmup, and run order are printed in output.
  • RTT simulation knob: BATTLE_SIMULATED_RTT_US injects per-query transport delay in the harness.
  • Measured stats: median + p95 (and avg in raw output), query count per request, JSON bytes for REST variants.
  • Run it yourself:
    DATABASE_URL=postgresql://orion@localhost:5432/swb_staging_local?sslmode=disable BATTLE_SIMULATED_RTT_US=1000 cargo run -p qail-pg --example battle_comparison --features chrono,uuid,legacy-raw-examples --release

Quick Start

use qail_core::prelude::*;
use qail_pg::PgDriver;

// Connect
let mut driver = PgDriver::connect("localhost", 5432, "user", "mydb").await?;

// Multi-tenant: scope every query to this tenant
let ctx = RlsContext::tenant(tenant_id);

// Build & execute
let orders = Qail::get("orders")
    .columns(["id", "total", "status"])
    .join(JoinKind::Left, "users", "orders.user_id", "users.id")
    .column("users.email AS customer_email")
    .eq("orders.status", "paid")
    .order_by("orders.created_at", Desc)
    .limit(25)
    .with_rls(&ctx);  // ← tenant-scoped automatically

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

CLI

cargo install qail

qail init --name myapp --mode postgres --url postgres://localhost/mydb
qail exec "get users'id'email[active=true]" --url postgres://localhost/mydb
qail pull postgres://localhost/mydb              # Introspect → schema.qail
qail diff _ schema.qail --live --url postgres://...    # Drift detection
qail migrate up v1:v2 postgres://...             # Apply migrations
qail types schema.qail > src/generated/schema.rs # Typed codegen

Why Qail > String SQL

🔐 Security Is Compiled In

Threat String SQL Qail
SQL injection Possible (one mistake) Prevented on AST path (no app-side SQL interpolation)
Tenant data leak Missing WHERE clause RLS injected automatically
Query abuse Unbounded depth/joins AST validates at compile time
IDOR Must check per endpoint Tenant isolation built into protocol
// RLS: tenant-first constructors
let ctx = RlsContext::tenant(tenant_id);            // Single tenant (preferred)
let ctx = RlsContext::tenant_and_agent(tenant_id, agent_id); // Agent/reseller within tenant
let ctx = RlsContext::global();                     // Shared data (tenant_id IS NULL)
let token = SuperAdminToken::for_system_process("admin");
let ctx = RlsContext::super_admin(token);           // Full bypass (internal only)

// Every query is automatically scoped
Qail::get("bookings").with_rls(&ctx)  // ← no manual WHERE needed

🔗 Compile-Time Relation Safety

Invalid joins fail at compile time, not at 3am in production:

use schema::{users, posts};

// ✅ Compiles — tables are related via ref:users.id
Qail::typed(users::table)
    .join_related(posts::table)
    .typed_column(users::email())
    .typed_eq(users::active(), true)

// ❌ Compile Error — no RelatedTo<Products> impl
Qail::typed(users::table).join_related(products::table)

🛡️ Protected Columns

Compile-time data governance — sensitive columns require capability proof:

let admin_cap = CapabilityProvider::mint_admin();  // After JWT verification

Qail::get(users::table)
    .with_cap(&admin_cap)
    .column(users::email)                    // Public — always allowed
    .column_protected(users::password_hash)  // Protected — requires cap

Qail Gateway

Auto-REST API server with zero backend code. Point it at a Postgres database, get a full API:

GET    /api/{table}?expand=users&sort=-created_at&limit=10
GET    /api/{table}/:id
POST   /api/{table}
PATCH  /api/{table}/:id
DELETE /api/{table}/:id
GET    /api/{table}/_explain    # EXPLAIN ANALYZE
GET    /api/{table}/_aggregate  # count, sum, avg, min, max

A complete REST API layer for PostgreSQL:

  • ✅ Auto-REST CRUD for all tables
  • ✅ FK-based JOIN expansion (?expand=) + nested expansion
  • ✅ Full-text search (?search=)
  • ✅ WebSocket subscriptions + live queries
  • ✅ Event triggers (mutation → webhook with retry)
  • ✅ JWT auth (HS256/RS256) + API key auth
  • ✅ YAML policy engine + column permissions
  • ✅ Query allow-listing + complexity limits
  • ✅ Prometheus metrics + request tracing
  • ✅ NDJSON streaming + cursor pagination
  • ✅ OpenAPI spec generation

Architecture

qail.rs/
├── core/       AST engine, parser, validator, typed system, RLS, migrations
├── pg/         PostgreSQL driver (binary wire protocol, connection pool)
├── gateway/    Auto-REST API server (Axum)
├── cli/        qail exec, pull, diff, migrate, types
├── encoder/    Wire protocol encoder + FFI/runtime internals
├── qdrant/     Qdrant vector DB driver (optional)
├── workflow/   Workflow engine
└── sdk/        Direct SDKs (TypeScript, Swift, Kotlin)

SDK Status

Platform Status Distribution
TypeScript ✅ Supported npm install @qail/client
Swift ✅ Supported Source package in sdk/swift
Kotlin ✅ Supported Gradle module in sdk/kotlin
Node.js native binding ⏸ Deferred Not shipped yet

tenant_id is the runtime contract across gateway and RLS paths. Legacy operator_id runtime compatibility aliases were removed in v0.26.0.


N+1 Detection

Compile-time static analysis catches query-in-loop patterns before they reach production. Powered by QAIL semantic Rust scanning (no syn dependency on the runtime analyzer path).

Rules

Code Severity Trigger Example
N1-001 ⚠ Warning Query inside for/while/loop for x in items { conn.fetch_all(&q) }
N1-002 ⚠ Warning Loop variable used in query args for id in ids { Qail::get("t").eq("id", id) }
N1-003 ⚠ Warning Function with query called in loop for x in xs { load_user(conn, x) }
N1-004 ❌ Error Query in nested loop (depth ≥ 2) for g in groups { for x in g { ... } }

Suppression

// Disable on the next line
// qail-lint:disable-next-line N1-001
conn.fetch_all(&cmd).await?;

// Disable inline
conn.fetch_all(&cmd).await?; // qail-lint:disable-line N1-001

Build Integration (build.rs)

Runs automatically via validate() when using Qail's build-time checks:

Env Var Values Default
QAIL_NPLUS1 off | warn | deny warn
QAIL_NPLUS1_MAX_WARNINGS integer 50
QAIL_SCAN_DIRS comma-separated source roots src

Monorepo example:

# Scan multiple Rust roots during build validation + N+1 checks
QAIL_SCAN_DIRS="src,apps/api/src,crates/billing/src" cargo build

CLI

qail check schema.qail --src ./src              # Shows N+1 warnings
qail check schema.qail --src ./src --nplus1-deny # Fails on any N+1

LSP

N+1 diagnostics appear automatically in your editor for .rs files with diagnostic codes N1-001..N1-004.

Remediation

// ❌ N+1: one query per item
for id in &ids {
    let user = conn.fetch_one(&Qail::get("users").eq("id", id)).await?;
}

// ✅ Batch: single query
let users = conn.fetch_all(
    &Qail::get("users").in_vals("id", &ids)
).await?;

Feature Status (March 2026)

Category Features
Core SQL SELECT, INSERT, UPDATE, DELETE, UPSERT, RETURNING, COPY
Joins INNER, LEFT, RIGHT, FULL, CROSS, LATERAL, self-joins
Advanced CTEs, Subqueries, EXISTS, Window Functions, UNION/INTERSECT/EXCEPT
Types JSON/JSONB, Arrays, UUID, Timestamps, Enums, Composite
Security RLS, TypedQail, Protected Columns, Capability Witnesses
Migrations AST-native diffing, drift detection, impact analysis
Schema Views, Materialized Views, Functions, Triggers, Extensions, Sequences, Grants
Performance Connection Pool, Query Cache (LRU+TTL), Prepared Statements, Binary Protocol
Connection SSL/TLS, SCRAM-SHA-256, Unix Socket
Operations EXPLAIN ANALYZE, Statement Timeout, LOCK TABLE, Batch Transactions

Connection Pool Maintenance

Activate background pool health maintenance (idle connection cleanup + min_connections backfill) by calling spawn_pool_maintenance after creating the pool:

let pool = qail_pg::PgPool::connect(config).await?;
qail_pg::spawn_pool_maintenance(pool.clone());

Documentation


License

Apache-2.0 © 2025-2026 Qail Contributors

Built with 🦀 Rust
dev.qail.io

About

AST-native PostgreSQL toolkit: typed queries to wire protocol, with built-in RLS tenant isolation.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors