#query-builder #derive #model #row #projection #soft-delete #cte #sql

macro qraft-derive

Procedural derives for qraft query-builder models, rows, and projections

1 unstable release

Uses new Rust 2024

new 0.1.2 May 7, 2026

#6 in #soft-delete


Used in qraft

MIT/Apache

140KB
3.5K SLoC

qraft

qraft is a typed SQL query builder for rust, built around generated schema modules and quex for execution.

You define a rust struct, derive Qraft, and get a table handle, typed columns, and model helpers such as query(), find(), and create(). The point is simple: write query code in rust, keep column names and result shapes checked, and stop passing SQL around as loose strings unless you actually want raw SQL.

What it covers

qraft handles the parts of query building that tend to get messy once a project grows:

  • typed select, insert, update, and delete builders
  • derive macros for models, row decoding, projections, pivots, and CTEs
  • common table expressions
  • aliasing for self-joins and readable multi-table queries
  • optional soft-delete scopes and restore helpers
  • quex pool and executor types re-exported from the top-level crate

Install

Pick the features you need. Most projects will want derive plus one database backend.

[dependencies]
qraft = { version = "0.1.1", features = ["derive", "sqlite"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Feature flags:

  • derive: enables Qraft, Cte, FromRow, Projection, Insertable, and Pivot
  • sqlite, postgres, mariadb: enables the matching quex backend
  • soft-delete: enables derived soft-delete scopes and restore helpers
  • chrono, serde_json, uuid: enables optional type support
  • hash: enables Hashed, a bcrypt-backed text wrapper for stored passwords

Quick start

This is the core workflow. Derive Qraft, then build queries with the generated schema module.

use qraft::prelude::*;

#[derive(Debug, qraft::Qraft)]
struct User {
    id: i64,
    email: String,
    active: bool,
}

let sql = User::query()
    .filter(users::active.eq(true))
    .to_debug_sql::<qraft::Postgres>();

assert_eq!(
    sql,
    r#"select * from "users" where "users"."active" = $1; params=[true]"#
);

The derive generates a users module with:

  • users::table
  • typed column values such as users::id and users::email
  • helpers on the model, including User::query(), User::find(id), and User::create(values)

That gives you a fairly direct style of query code. You are still writing SQL-shaped logic, but with rust types attached to it.

Running queries

qraft re-exports quex, so you can open a pool and execute queries from the same crate surface.

use qraft::prelude::*;
use qraft::quex;

#[derive(Debug, qraft::Qraft)]
struct AuditLog {
    id: i64,
    message: String,
}

# async fn run() -> qraft::Result<()> {
let pool = quex::Pool::connect("sqlite::memory:")?.build().await?;

quex::query(
    r#"
    create table audit_logs (
        id integer primary key,
        message text not null
    )
    "#,
)
.execute(&pool)
.await?;

AuditLog::create((
    audit_logs::id.eq(1),
    audit_logs::message.eq("created"),
))
.execute(&pool)
.await?;

let row = AuditLog::find(1).one(&pool).await?;
assert_eq!(row.message, "created");
# Ok(())
# }

Common table expressions

CTEs use the same pattern. Derive Cte, build the inner query, then attach it with with(...).

use qraft::prelude::*;
use qraft::query::select;

#[derive(Debug, qraft::Qraft)]
struct User {
    id: i64,
    email: String,
    active: bool,
}

#[derive(Debug, qraft::Cte)]
struct RecentUser {
    id: i64,
    email: String,
}

let sql = with(recent_users::from(
    select((users::id, users::email))
        .from(users::table)
        .filter(users::active.eq(true)),
))
.select(recent_users::star)
.from(recent_users::table)
.to_debug_sql::<qraft::Postgres>();

assert_eq!(
    sql,
    r#"with "recent_users"("id", "email") as (select "users"."id", "users"."email" from "users" where "users"."active" = $1) select "recent_users".* from "recent_users"; params=[true]"#
);

This matters once queries stop being one flat select. You can keep intermediate shapes named and typed instead of dropping to handwritten SQL early.

Soft deletes

With the soft-delete feature, a model can hide deleted rows by default and expose explicit scopes when you need them.

use qraft::prelude::*;

#[derive(Debug, qraft::Qraft)]
#[qraft(soft_delete)]
struct User {
    id: i64,
    email: String,
    deleted_at: Option<String>,
}

let active = User::query().to_debug_sql::<qraft::Sqlite>();
let deleted = User::only_deleted().to_debug_sql::<qraft::Sqlite>();

assert_eq!(
    active,
    r#"select * from "users" where ("users"."deleted_at") is null; params=[]"#
);
assert_eq!(
    deleted,
    r#"select * from "users" where ("users"."deleted_at") is not null; params=[]"#
);

The derived API also adds:

  • User::with_deleted()
  • User::only_deleted()
  • query.restore(exec)
  • query.restore_query()
  • query.delete(exec) for soft delete
  • query.force_delete(exec) for physical delete

Hashed values

With the hash feature, qraft re-exports Hashed. It stores bcrypt hashes as text and gives you helpers for hashing and verification.

Hashed implements Encode, Decode, and Compatible<Text>, so it can be stored in text columns and used directly in derived models.

use qraft::Hashed;

# async fn run() -> Result<(), Box<dyn std::error::Error>> {
let password = Hashed::make("hunter2").await?;
assert!(password.verify("hunter2").await?);
assert!(!password.verify("wrong-password").await?);
# Ok(())
# }

This is useful when a model field should decode from the database as a password hash instead of a plain String.

Derives

  • Qraft: maps a model to a table and generates the schema module
  • Cte: declares a named common table expression
  • FromRow: decodes a result row into a custom rust type
  • Projection: derives projected field metadata
  • Insertable: derives insert assignments
  • Pivot: derives projected fields for pivot types

Workspace

  • crates/qraft: public API and re-exports
  • crates/core: query builders, expressions, lowering, and execution glue
  • crates/derive: proc macros exposed by qraft
  • crates/derive-core: shared derive implementation
  • crates/expression-macro: expression macro support

License

qraft is available under MIT or Apache-2.0.

Dependencies

~2.4–4MB
~71K SLoC