# 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.
```toml
[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.
```rust
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.
```rust
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(...)`.
```rust
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.
```rust
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.
```rust
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`.