Home Expressions
Docs
Drivers Gateway SDKs Benchmarks
Changelog
GitHub
Blog Status Roadmap
The Manifesto

QAIL Philosophy

A story about the "Database Dilemma" and why we built QAIL. Move query structure from hand-written SQL strings to a typed AST, then encode protocol bytes for PostgreSQL natively.

The Struggle: "Polyglot Hell"

We are polyglot developers. On Monday, we write Rust. On Tuesday, Go. On Wednesday, we maintain a legacy PHP system.

Every time we switch languages, we have to switch our paradigms:

  • In Rust, we choose between Diesel's strict compile-time types or SQLx's raw flexibility.
  • In Go, we balance GORM's convenience with the performance of raw database/sql.
  • In Node.js, we rely on Prisma for safety but pay the cost of heavy binary sidecars.
  • In Python, we leverage Django's speed of development but hit performance walls at scale.
  • In PHP, we love Eloquent's expressiveness but sometimes need lighter, faster query options.

The problem isn't the data. The problem is the string layer. Every ORM generates SQL strings. Every driver parses them again. Why not skip strings entirely?

The Origin: "I Don't Want You to Suffer"

"I originally built QAIL for internal use to solve my own polyglot headaches. But I realized that keeping it private meant letting other engineers suffer through the same 'Database Dilemma'. I decided to open-source it so we can all skip app-side SQL string plumbing."

The Realization: "SQL is Assembly"

We realized something profound: SQL is not source code. SQL is Assembly.

SQL is verbose, powerful, and dangerous. It is the low-level instruction set of the database engine.

  • Raw SQL offers ultimate control, but maintaining raw strings across a large codebase requires immense discipline.
  • ORMs provide fantastic productivity, but the abstraction sometimes comes at the cost of fine-grained performance control.

We didn't need a compromise. We needed a Universal AST.

The Savior: QAIL

QAIL is the "Universal AST for Databases". It is not a text syntax that compiles to SQLβ€”it is a native data structure that encodes directly to database wire protocols.

It separates the Code (your query AST) from the Data (your parameters). It creates a unified structure that works the same way in Rust, Go, Node, and PHP.

Build This (Rust)
Qail::get("users")
  .columns(["id", "email"])
  .filter("active", Eq, true)
  .limit(10)
Encodes To (Wire Protocol)
SELECT id, email
FROM users
WHERE active = true
LIMIT 10

Now, your team builds with one AST. The Rust dev, the Go dev, and the Frontend dev all use the same data structure.

"Safety shouldn't require a prison of boilerplate. We moved validation from the 'String' layer to the 'AST' layer. Code is data. Data is typed. Injection risk is reduced on the AST path."

By preventing SQL injection at the AST level, QAIL ensures your queries are safe before they even reach the database. Unlike ORMs that generate strings at runtime, QAIL encodes directly to wire protocol bytesβ€”without app-side SQL string interpolation on the AST path.

Important: this does not mean PostgreSQL stops parsing. PostgreSQL still runs its normal parse/plan/execute stages on the server.

The Vision: Facts, Meaning, and Time

"Postgres stores facts, Qdrant stores meaning β€” QAIL decides."

Modern applications don't run on a single database. They need specialized engines for specialized workloads:

Engine Role Examples
PostgreSQL Source of truth. ACID transactions. Relational data. Users, orders, inventory
Qdrant Semantic understanding. Vector embeddings. AI search. Recommendations, similarity, RAG
Native Cache In-process moka cache with TTL and size bounds. Zero-network result caching. Query results, prepared stmts, EXPLAIN plans

QAIL is the decision layer that unifies these engines. One AST. Two powerhouses. Cache built-in.

The Architecture: Native Power

QAIL is written in Pure Rust. It exposes:

  • A C-API for Go, Python, and Java (via FFI).
  • A WASM Module for Node.js and Browsers.
  • A Native Crate for Rust users.

It runs in-process. It has zero network overhead. It turns your query generation into a simple CPU function call.


The AST Builder API

We designed the builder API for Type Safety and Clarity. Every method is purposeful. No hand-built SQL-string plumbing.

Method Purpose Example
Qail::get() SELECT query Qail::get("users")
.columns() Select specific columns .columns(["id", "email"])
.filter() WHERE condition .filter("active", Eq, true)
.order_by() Sort results .order_by("created_at", Desc)
.limit() Pagination .limit(10)
.left_join() Join tables .left_join("profiles", "users.id", "profiles.user_id")

πŸ›‘οΈ Why No Raw Strings?

QAIL intentionally does not provide a "Raw SQL" escape hatch. This is not a missing featureβ€”it's a deliberate design choice.

The Promise

"If it compiles in QAIL, it is a valid, safe protocol packet."

Soundness by Construction

By forcing queries through the AST path, QAIL enforces strong invariants:

  • SQL Injection Risk Removed on AST Path β€” Code and data are never mixed. Parameters are always separate from query structure.
  • Syntax Errors at Construction Time β€” Invalid query structures fail when building the AST, before ever touching the database.
  • Global Safety Invariant β€” Every query that executes was validated at the type level (compiled languages) or construction time (dynamic languages).

The Rust vs C++ Analogy

Approach Philosophy Result
C++ (escape hatches) "Here's safe code, but use pointers if you need" People use pointers β†’ Bugs happen
Rust (no escape) "If it's not safe, you can't write it" People adapt β†’ Memory safety strongly enforced
ORM with Raw "Use the builder, or escape to strings" People use Raw β†’ Injection possible
QAIL (pure AST) "If it's not in the AST, you can't query it" People adapt β†’ Injection risk reduced by design

For Those Who Need Raw SQL

If you need raw SQL strings, use sqlx or another driver. QAIL is for those who want strong invariants, not string-level flexibility.

"QAIL: Sound by Construction."

🚫 Why QAIL is NOT an ORM

QAIL is not an Object-Relational Mapper. It does not provide lazy loading or magic .include() methods. This is not a limitationβ€”it's a deliberate architectural choice backed by hard data.

What QAIL Is

ORMs (Prisma, Diesel, SeaORM) QAIL
Object-Relational Mapper Query Language & Wire Protocol
Hide SQL behind objects Make queries portable & safe
Lazy loading hides N+1 .join() guides you to 1 query
"Don't think about SQL" "Think in queries, not strings"

Why No Lazy Loading? The Evidence.

We benchmarked six patterns for the same endpoint objective and canonical payload against a real PostgreSQL dataset (`odyssey_connections` + `harbors`, 2x LEFT JOIN). This is a pattern benchmark: single-query JOIN vs batched lookups vs N+1 fan-out.

Profile Approach Median p95 DB Queries
RTT=0us Qail AST (uncached) 146.9us 163.7us 1
RTT=0us GraphQL + DataLoader 146.8us 168.0us 2
RTT=0us GraphQL naive (N+1) 4.74ms 4.88ms 101
RTT=1000us Qail AST (uncached) 1237.8us 1252.5us 1
RTT=1000us GraphQL + DataLoader 2287.0us 2507.0us 2
RTT=1000us GraphQL naive (N+1) 111.56ms 112.19ms 101

Loopback can make single-query and DataLoader appear equivalent. As RTT grows, extra round trips dominate, and one-query plans pull ahead.

"Method first: same canonical payload, equivalence gate enabled, randomized run order, and explicit RTT profile (`BATTLE_SIMULATED_RTT_US`) in the benchmark harness."

Full benchmark methodology & source code β†’

Typed Schema Codegen

QAIL does provide optional compile-time type safety via TypedQail<T> and schema codegen:

  • TypedQail β€” Table-typed queries. Invalid joins fail at compile time via RelatedTo<T>.
  • Schema codegen β€” qail types schema.qail generates typed column accessors.
  • Protected columns β€” Capability witnesses guard sensitive fields at compile time.

But unlike ORMs, these are opt-in safety layers on top of the core AST β€” not mandatory boilerplate.

ORM Way (Hidden Queries)
user.posts().comments().author()
// 3+ hidden queries?
QAIL Way (Explicit)
Qail::get("posts")
  .left_join("comments", "post_id", "id")
  .left_join("users", "author_id", "id")

What QAIL Focuses On

  • βœ… Best-in-class query syntax β€” 3-5x shorter than SQL
  • βœ… Direct wire protocol encoding β€” No app-side SQL interpolation on AST path
  • βœ… Migration tooling with impact analysis β€” Unique to QAIL
  • βœ… Multi-database AST portability β€” Write once, run anywhere
"QAIL is the query transport layer, not an ORM. If you need magic lazy loading, use Prisma. If you want the high-performance, safety-focused way to talk to your database β€” with proof β€” use QAIL."

πŸš€ QAIL Gateway β€” Production Today

QAIL Gateway is an auto-REST API server that turns your PostgreSQL database into a full-featured API β€” with zero backend code. Point it at a database, get instant CRUD, real-time subscriptions, and enterprise security.

"Full-featured data API β€” without GraphQL complexity. Binary AST protocol with safer defaults than hand-built query strings."

What You Get

Feature Status
Auto-REST CRUD for all tables βœ…
FK-based JOIN expansion (?expand=) βœ…
Full-text search (?search=) βœ…
WebSocket subscriptions + live queries βœ…
Event triggers (mutation β†’ webhook) βœ…
JWT auth + webhook auth + RLS βœ…
YAML policy engine + column permissions βœ…
Prometheus metrics + request tracing βœ…
OpenAPI spec + schema introspection βœ…

Security Is Compiled In

Threat GraphQL / REST QAIL
SQL injection Possible (one mistake) Prevented on AST path (no app-side SQL interpolation)
Tenant data leak Missing WHERE clause RLS auto-injected
N+1 catastrophe Default behavior Detected and discouraged (AST + lint)

Read the Gateway documentation β†’

QAIL Gateway powers Sailtix β€” a multi-operator maritime booking platform with full RLS tenant isolation across 27+ tables.