14 releases (4 breaking)
Uses new Rust 2024
| 0.8.10 | Dec 23, 2025 |
|---|---|
| 0.8.9 | Dec 23, 2025 |
| 0.7.0 | Dec 21, 2025 |
| 0.6.1 | Dec 21, 2025 |
| 0.3.0 | Dec 20, 2025 |
#407 in #sqlx
18KB
160 lines
🪝 QAIL — The Universal Query Transpiler
Safe but Free. Write queries once. Run them everywhere. Zero lock-in.
Why QAIL?
For years, developers have been trapped between two choices:
- Raw SQL — Maximum freedom, but dangerous strings scattered across your codebase.
- ORMs / Query Builders — Maximum safety, but a "prison" of boilerplate and language lock-in.
QAIL is the third way.
"I originally built QAIL for internal use to solve my own headaches. But I realized that other engineers shouldn't have to suffer through the same 'Database Dilemma'. That's why I decided to open-source it." — The Creator
We moved validation from the networking layer to the grammar level. By treating queries as a compiled language instead of raw strings, QAIL provides compile-time safety with the freedom of raw SQL.
- ✅ No language lock-in — Same syntax in Rust, Node.js, Go, Python, PHP
- ✅ No heavy dependencies — Pure logic, zero networking code
- ✅ No "big bang" migration — Adopt incrementally, one query at a time
- ✅ Works with your existing driver — SQLx, pg, PDO, psycopg2, etc.
-- SQL (The Assembly)
SELECT id, email FROM users WHERE active = true LIMIT 10;
# QAIL (The Source Code)
get::users:'id'email [ 'active == true, 0..10 ]
One line. Zero ceremony. Runs everywhere.
🚀 Installation
Rust (Native)
cargo install qail
[dependencies]
qail-core = "0.8"
Node.js / Browser (WASM)
npm i qail-wasm
💡 Usage
Rust
use qail_core::prelude::*;
// Parse and transpile
let sql = parse("get::users:'id'email [ 'active == true ]")?.to_sql();
// Returns: "SELECT id, email FROM users WHERE active = true"
// Use with your existing driver (sqlx, diesel, etc.)
let users = sqlx::query_as::<_, User>(&sql)
.fetch_all(&pool)
.await?;
JavaScript / TypeScript
import { parseAndTranspile } from 'qail-wasm';
const sql = parseAndTranspile("get::users:'id'email [ 'active == true ]");
// Returns: "SELECT id, email FROM users WHERE active = true"
// Use with your existing driver (pg, mysql2, etc.)
const result = await client.query(sql);
📖 Quick Reference
| Symbol | Name | Function | Example |
|---|---|---|---|
:: |
The Gate | Action (get/set/del/add) | get:: |
: |
The Link | Connect table to columns | users:'id |
' |
The Label | Mark a column | 'email'name |
'_ |
The Wildcard | All columns | users:'_ |
[ ] |
The Cage | Constraints block | [ 'active == true ] |
== |
The Equal | Equality check | 'status == "active" |
~ |
The Fuse | Fuzzy match (ILIKE) | 'name ~ "john" |
| |
The Split | Logical OR | 'a == 1 | 'b == 2 |
& |
The Bind | Logical AND | 'a == 1 & 'b == 2 |
+/- |
Sort Order | ASC/DESC | -created_at |
N..M |
The Range | Pagination | 0..10 |
$ |
The Var | Parameter placeholder | $1 |
! |
The Unique | DISTINCT | get!:: |
<- |
Left Join | LEFT JOIN | users<-profiles |
-> |
Inner Join | INNER JOIN | users->orders |
📚 Examples
Basic SELECT
get::users:'id'email [ 'active == true ]
# → SELECT id, email FROM users WHERE active = true
All Columns
get::users:'_
# → SELECT * FROM users
Sorting & Pagination
get::users:'_ [ -created_at, 0..10 ]
# → SELECT * FROM users ORDER BY created_at DESC LIMIT 10
Fuzzy Search
get::users:'id'name [ 'name ~ "john" ]
# → SELECT id, name FROM users WHERE name ILIKE '%john%'
UPDATE
set::users:[ status = "active" ] [ 'id == $1 ]
# → UPDATE users SET status = 'active' WHERE id = $1
DELETE
del::users:[ 'id == $1 ]
# → DELETE FROM users WHERE id = $1
JOINs
get::users<-profiles:'name'avatar
# → SELECT name, avatar FROM users LEFT JOIN profiles ON ...
📦 Schema Management (Migrations)
Create and modify tables with the same concise syntax.
Create Table (make::)
make::users:'id:uuid^pk'email:varchar^unique^comment("User email")
# → CREATE TABLE users (id UUID PRIMARY KEY, email VARCHAR(255) UNIQUE);
# → COMMENT ON COLUMN users.email IS 'User email'
Constraints & Defaults
make::posts:'id:uuid^pk'status:varchar^def("draft")'views:int^def(0)
# → CREATE TABLE posts (
# id UUID PRIMARY KEY,
# status VARCHAR(255) DEFAULT 'draft',
# views INT DEFAULT 0
# )
Composite Constraints
make::bookings:'user_id:uuid'slot_id:uuid^unique(user_id, slot_id)
# → CREATE TABLE bookings (..., UNIQUE (user_id, slot_id))
Create Index (index::)
index::idx_email^on(users:'email)^unique
# → CREATE UNIQUE INDEX idx_email ON users (email)
🌐 One Syntax. Every Stack.
QAIL provides multiple integration paths:
| Platform | Package | Description |
|---|---|---|
| Rust | qail-core |
Native crate, zero overhead |
| Node.js / Browser | qail-wasm |
WebAssembly module (~50KB) |
| C / C++ | libqail |
Universal C-API for FFI |
| Python, Go, PHP, Java | via C-API/FFI | Native bindings available |
The C-API Advantage
Instead of building separate bindings for each language, we expose a Universal C-API (libqail). Any language with FFI support can call QAIL directly.
Installation (Linux/macOS):
- Download
libqail-v0.8.0.tar.gzfrom Releases. - Install header and library:
sudo cp include/qail.h /usr/local/include/ sudo cp lib/libqail_ffi.so /usr/local/lib/ # .dylib on macOS sudo ldconfig # Linux only
Usage (C/C++):
Compile with -lqail_ffi:
#include <qail.h>
// ...
// char* sql = qail_transpile("get::users:'_");
Usage (Python):
from ctypes import cdll
lib = cdll.LoadLibrary("libqail_ffi.so") # or .dylib
# ...
Usage (Go):
/*
#cgo LDFLAGS: -lqail_ffi
#include <qail.h>
*/
import "C"
// ...
🤝 Contributing
We welcome contributions!
git clone https://github.com/qail-rs/qail.git
cd qail
cargo test
📄 License
MIT © 2025 QAIL Contributors
Built with 🦀 Rust
qail.rs
Dependencies
~4–15MB
~166K SLoC