HomePlaygroundExpressionsDocsDriversBlogStatusRoadmapChangelog GitHub

Protocol-First Data Layer

Typed query AST from app to database.

QAIL builds queries as typed objects and encodes them as protocol bytes. No hand-built SQL interpolation on the AST path, with RLS and validation still enforced where they belong.

1 AST for API, driver, and database
449us on the N+1 benchmark path
RLS Native policy context remains in PostgreSQL
Rust cargo install qail
Zig zig fetch --save ...qail-zig/v0.2.0.tar.gz

First-party language drivers: Rust and Zig

Live Query Shape Protocol path overview
QAIL AST
Qail::get("users")
    .select_all()
    .filter("active", Eq, true)
encode + bind
POSTGRESQL SELECT * FROM users WHERE active = $1
QDRANT { "must": [{ "key": "active", "match": true }] }
RustRust ZigZig

Real Benchmark: N+1 Battle Test

Same query. Same database. Same 50-row result set with 3 JOINs.
100 iterations, release build, buffer cache warmed.
Snapshot run: February 2026.

QAIL AST
449ยตs
1 query
GQL + DataLoader
1.52ms
~3 queries
GQL Naive
18.2ms ยท 40ร— slower
~151 queries
REST Naive
22.5ms ยท 50ร— slower
~151 queries

QAIL executes one planned query on this path. Naive resolver/endpoint patterns can explode to 151 round-trips without join batching.

View all benchmarks โ†’

The QAIL Stack

Four layers. One AST flows through all of them.

๐Ÿงฌ

qail-core

THE AST

Parser, transpiler, type system. Queries are typed data structures, not hand-built SQL strings in app code. Schema validation at compile time.

get users fields id, email
  where active = true
  order by created_at desc
  limit 10
๐Ÿ”Œ

qail-pg / qdrant

NATIVE DRIVERS

Protocol-level PostgreSQL driver with typed values and built-in RLS context per connection.

let driver = PgDriver::connect_env().await?;
driver.set_rls_context(
    RlsContext::operator("op-456")
).await?;
driver.fetch_all(&cmd).await?
๐Ÿš€

qail-gateway

AUTO-REST + BINARY PROTOCOL

Zero-code API from your schema. Auto-REST with FK expansion, policy engine, WebSocket subscriptions, binary AST wire format.

GET /api/bookings
    ?status.eq=confirmed
    &sort=created_at:desc
    &expand=routes,payments
    &limit=20
โš™๏ธ

qail-workflow

STATE MACHINES

Declarative multi-step orchestration. Database queries, notifications, and events in typed state machines.

Workflow::new("booking")
    .step("validate", validate_fn)
    .step("charge", payment_fn)
    .step("confirm", confirm_fn)
    .on_error(rollback_fn)

Every Entry Point โ†’ One Pipeline

REST params, text queries, or binary AST all converge to the same AST. Policy injection and schema validation happen once. SQL text output is optional for tooling/debug views.

REST
GET /api/users?active=true
Text
get users where active = true
Binary
postcard::to_allocvec(&cmd)
โ†“
QAIL AST
Qail { action: Get, table: "users", cages: [Filter(active = true)] }
โ†“
POLICY ENGINE
+ injects AND operator_id = 'op-456'
โ†“
PROTOCOL ENCODER
Encode AST โ†’ Parse / Bind / Execute + typed bind values
โ†“
POSTGRESQL (with PG-native RLS)
1 query. 1 connection. Done.

Supported Databases

Two production databases, one typed AST, plus built-in cache for hot paths.

PostgreSQL

PostgreSQL

Relational โ€ข Transactions โ€ข ACID

Production
Qdrant

Qdrant

Vector โ€ข AI/ML โ€ข Semantic Search

Production

Native Cache

Moka โ€ข TTL โ€ข Zero Latency

Built-in

Language Drivers

Officially supported today: Zig and Rust.

View all drivers โ†’

๐Ÿ›ก๏ธ The "Wait, Stop!" Feature

The only migration tool that reads your code before it touches your data.

Terminal
$ qail migrate up schema.qail --codebase ./src

๐Ÿ›‘ BLOCKED: Safety Check Failed

You are dropping column 'status', but it is still used in:

   ๐Ÿ“„ src/queries.ts:25  โ†’ get portfolio fields status
   ๐Ÿ“„ src/api.rs:102     โ†’ Qail::get("portfolio").filter("status"...)

Fix your code first, or use --force to proceed.
๐Ÿ”

Scans Before Running

Searches .rs, .ts, .js, .py for queries that reference changing columns

๐Ÿ“

Points to Line Numbers

Shows exactly where your code needs updating before migration

๐ŸŽฏ

You're the Boss

Use --force to override when you know better

The data layer you don't write.

One AST. Protocol bytes. Compile-time safety. Built-in RLS.