An Arrow-native ORM for Rust. Schema types map 1:1 to Apache Arrow types,
database connectivity goes through ADBC, and code generation produces
FlatBuffers, Protobuf, Rust, TypeScript, and SQL DDL from a single .quiver
schema file.
Traditional ORMs have an impedance mismatch: application types are converted to ORM types, then to SQL types, then to wire format, then to database types. Every conversion is lossy.
Quiver eliminates this by using the Arrow type system end-to-end:
Quiver schema types == Arrow types == ADBC wire types == Database types
Int16 Int16 Int16 SMALLINT
Decimal128(10,2) Decimal128(10,2) Decimal128(10,2) NUMERIC(10,2)
Timestamp(us, UTC) Timestamp(us,UTC) Timestamp(us,UTC) TIMESTAMPTZ
List<Utf8> List<Utf8> List<Utf8> TEXT[]
Zero lossy conversions from schema to wire to database.
// schema.quiver
config {
provider "postgresql"
url "postgresql://localhost/myapp"
}
enum Role {
User
Admin
Moderator
}
model User {
id Int32 PRIMARY KEY AUTOINCREMENT
email Utf8 UNIQUE
name Utf8?
balance Decimal128(10, 2) DEFAULT 0
active Boolean DEFAULT true
created Timestamp(Microsecond, UTC) DEFAULT now()
role Role DEFAULT User
FOREIGN KEY (role) REFERENCES Role (name)
INDEX (email)
MAP "users"
}
model Post {
id Int32 PRIMARY KEY AUTOINCREMENT
title Utf8
content LargeUtf8?
published Boolean DEFAULT false
authorId Int32
FOREIGN KEY (authorId) REFERENCES User (id)
INDEX (authorId)
}
# Type-safe Rust client (recommended -- generates typed queries, filters, structs)
quiver generate schema.quiver -t rust-client -o src/generated/
# Rust serde structs + TryFrom<&Row> deserialization
quiver generate schema.quiver -t rust-serde -o generated/
# TypeScript types
quiver generate schema.quiver -t typescript -o generated/
# FlatBuffers / Protobuf (wire formats)
quiver generate schema.quiver -t flatbuffers -o generated/
quiver generate schema.quiver -t protobuf -o generated/
# SQL DDL (per dialect)
quiver generate schema.quiver -t sql-postgres -o generated/
quiver generate schema.quiver -t sql-mysql -o generated/
quiver generate schema.quiver -t sql-sqlite -o generated/# Push schema directly to database
quiver db push schema.quiver
# Or use migrations
quiver migrate create schema.quiver add_users
quiver migrate apply schema.quiver
quiver migrate status schema.quiver
quiver migrate rollback schema.quiver
# Introspect existing database
quiver db pull schema.quiver -o introspected.quiver
# Run raw SQL
quiver db execute schema.quiver "SELECT * FROM users"// Generated by: quiver generate schema.quiver -t rust-client -o src/generated/
mod generated;
use generated::user;
use quiver_driver_core::{Driver, QuiverClient};
use quiver_driver_sqlite::SqliteDriver;
// Connect and wrap in QuiverClient (enforces transactional access)
let mut client = QuiverClient::new(SqliteDriver.connect(":memory:").await?);
// All data operations must be in a transaction
// Field names are compile-time constants -- typos are compile errors
let rows = client.transaction(|tx| Box::pin(async move {
let query = user::find_many()
.select(&[user::fields::ID, user::fields::EMAIL, user::fields::NAME])
.filter(user::filter::active_is_true())
.filter(user::filter::balance_gte(100.0))
.order_by(user::order::created_asc())
.limit(10)
.build();
tx.query(&query).await
})).await?;
// Typed create -- required fields enforced by the compiler
let data = user::CreateData {
email: "alice@example.com".into(),
name: Some("Alice".into()),
balance: None, // uses schema default
// ... all required fields must be set -- compiler enforces this
};
client.transaction(|tx| Box::pin(async move {
tx.execute(&data.to_query()).await
})).await?;quiver/
quiver-schema/ Schema parser, lexer, AST, validation, Arrow type mapping
quiver-codegen/ Code generation (FBS, Proto, Rust client, Rust serde, TS, SQL DDL)
quiver-query/ Type-safe query builder, pagination, relations, streaming
quiver-driver-core/ Dialect trait, generic AdbcConnection<D>, DriverPool<D>,
QuiverClient, Value/Row/RowStream, BoxFuture-based traits
quiver-driver-sqlite/ SQLite driver (type alias to AdbcConnection<SqliteDialect>)
quiver-driver-postgres/ PostgreSQL driver (type alias to AdbcConnection<PostgresDialect>)
quiver-driver-mysql/ MySQL driver (type alias to AdbcConnection<MysqlDialect>)
quiver-migrate/ Schema diffing, DDL generation, migration tracking, introspection
quiver-e2e/ End-to-end integration tests (SQLite)
quiver-cli/ CLI binary (parse, generate, migrate, db)
quiver-error/ Shared QuiverError enum with retry detection
arrow-adbc-rs/ Git submodule: clean-room ADBC core + drivers
Every Quiver type maps 1:1 to an arrow_schema::DataType:
| Category | Types |
|---|---|
| Integers (signed) | Int8, Int16, Int32, Int64 |
| Integers (unsigned) | UInt8, UInt16, UInt32, UInt64 |
| Floating point | Float16, Float32, Float64 |
| Decimal | Decimal128(p, s), Decimal256(p, s) |
| String | Utf8, LargeUtf8 |
| Binary | Binary, LargeBinary, FixedSizeBinary(n) |
| Boolean | Boolean |
| Temporal | Date32, Date64, Time32(unit), Time64(unit), Timestamp(unit, tz) |
| Nested | List<T>, LargeList<T>, Map<K, V>, Struct<{...}> |
| Nullability | Append ? to any type |
Field-level attributes are placed after the type on the same line. Model-level attributes are placed on their own lines within the model block.
| Attribute | Level | Meaning |
|---|---|---|
| PRIMARY KEY | field | Primary key |
| AUTOINCREMENT | field | Auto-increment |
| UNIQUE | field | Unique constraint |
| DEFAULT value | field | Default value (literal, now(), uuid(), cuid()) |
| FOREIGN KEY (col) REFERENCES Model (col) | model | Foreign key with optional ON DELETE/ON UPDATE |
| INDEX (col1, col2, ...) | model | Database index |
| MAP "name" | model | Database table name |
The query builder enforces injection safety at the type level:
- Table and column names must be
&'static str(compile-time literals) SafeIdentvalidates that identifiers contain only safe characters- Values are always parameterized, never interpolated into SQL
Filter::raw()accepts only&'static strTrustedSqlBuilderin migrations uses&'static strfor SQL fragments
// OK: string literals
let q = Query::table("User").find_many().filter(Filter::eq("email", email)).build();
// WON'T COMPILE: runtime String is not &'static str
let table = format!("{}; DROP TABLE users", user_input);
let q = Query::table(&table).find_many().build(); // compile error| Database | Driver Crate | Migration DDL | Introspection | ADBC TLS |
|---|---|---|---|---|
| SQLite | quiver-driver-sqlite | Yes | Yes | N/A |
| PostgreSQL | quiver-driver-postgres | Yes | Yes (information_schema) | Optional (tls feature) |
| MySQL | quiver-driver-mysql | Yes | Yes (information_schema) | -- |
| FlightSQL | (via adbc-flightsql) | -- | -- | Optional (tls feature) |
Each driver provides two APIs:
// QuiverClient API (transactional, Value/Row types)
use quiver_driver_postgres::PostgresDriver;
use quiver_driver_core::{Driver, QuiverClient};
let client = QuiverClient::new(PostgresDriver.connect("postgresql://localhost/myapp").await?);
// ADBC API (Arrow RecordBatch, bulk ingest, COPY protocol)
use quiver_driver_postgres::adbc_postgres;The ADBC layer (arrow-adbc-rs submodule) provides:
- Arrow-native data access -- query results as
RecordBatch, no row-by-row conversion - Bulk ingest -- PostgreSQL uses
COPY FROM STDINfor high-throughput loading - Compile-time SQL safety --
TrustedSqltype andtrusted_sql!macro prevent injection in ADBC queries - TLS support -- optional for PostgreSQL (
native-tls) and FlightSQL (tonicTLS)
cargo build --workspace
cargo test --workspace # 407 tests
cargo clippy --all-targets -- -D warnings| Feature | Prisma (TS) | Diesel | SQLx | SeaORM | Quiver |
|---|---|---|---|---|---|
| Type system | Custom (lossy) | SQL-mapped | SQL-mapped | Custom (lossy) | Arrow (lossless) |
| Schema language | .prisma | Rust macros | None (SQL) | Rust macros | .quiver (Arrow types) |
| Wire format | JSON | DB protocol | DB protocol | DB protocol | Arrow (columnar) |
| Zero-copy results | No | No | No | No | Yes (FlatBuffers) |
| Columnar access | No | No | No | No | Yes (RecordBatch) |
| Multi-database | Yes (adapters) | No | Limited | Limited | Yes (ADBC) |
| Remote/distributed | No | No | No | No | Yes (FlightSQL) |
| Nested types | JSON blob | Limited | Limited | JSON blob | First-class (List/Map/Struct) |
| Code generation | TS only | Rust macros | None | Rust macros | Typed client + FBS + Proto + TS |
| SQL injection prevention | Runtime | Compile-time | Compile-time | Runtime | Compile-time (&'static str) |
- Arrow IS the type system. No mapping layer. Schema types are Arrow types.
- ADBC is first-class. All drivers re-export their ADBC counterparts.
- Multiple backends from one schema. FlatBuffers, Protobuf, Rust serde,
TypeScript, SQL DDL -- all from the same
.quiversource. - SQL injection impossible by construction. The type system prevents it.
- Opinionated transactions. All DB operations must be in a transaction.
- No implicit side effects. No automatic timestamps or UUIDs. Users provide values explicitly via composable helpers.
- Dialect-aware migrations. SQLite, PostgreSQL, and MySQL have separate DDL generation paths because their DDL is incompatible.
Apache-2.0