Skip to main content

Crate qbrs

Crate qbrs 

Source
Expand description

A Drizzle-flavored, type-safe SQL query builder for Rust — not an ORM and not a raw-SQL macro. Column and join references are checked at compile time without giving up dynamic composition, and scope resolution stays linear as the join count grows.

This is the facade users depend on: qbrs_core’s builders and renderer plus the derive and macros from qbrs-macros. Nothing here touches a database — executing a rendered statement against Postgres is qbrs-sqlx.

use qbrs::prelude::*;

#[derive(Table)]
#[table(name = "users")]
struct Users {
    #[column(primary_key, generated)]
    id: i64,
    email: String,
    #[column(default)]
    active: bool,
}

#[derive(Table)]
#[table(name = "orders")]
struct Orders {
    #[column(primary_key, generated)]
    id: i64,
    user_id: i64,
    total: i64,
}

fn main() {
    let (sql, params) = select((users::id, orders::total))
        .from(users::Table)
        .left_join(orders::Table, orders::user_id.eq(users::id))
        .filter(users::active.eq(true))
        .to_sql(Postgres);

    assert_eq!(
        sql,
        "SELECT \"users\".\"id\", \"orders\".\"total\" FROM \"users\" \
         LEFT JOIN \"orders\" ON (\"orders\".\"user_id\" = \"users\".\"id\") \
         WHERE (\"users\".\"active\" = $1)"
    );
    assert_eq!(params.len(), 1);
}

Dropping the .left_join(..) line makes that query a compile error rather than a runtime one: orders is in scope for neither the selection nor the ON clause. The join is also what decides nullability — orders::total decodes as Option<i64> above and as i64 after an INNER JOIN.

Modules§

cte
Common table expressions: WITH name AS (..) SELECT .. FROM name ...
delete
DELETE FROM .. WHERE ...
dialect
Dialect markers and capability gating.
expr
SQL types, columns, and the typed expression AST.
insert
INSERT INTO .. VALUES ...
prelude
A name belongs here if user source has to spell it: the builder entry points, the extension traits whose methods would otherwise be unreachable, the dialect markers, the macros, and every type that turns up in a signature or a type alias a user may have to write — a Scope list, a Row list, a Predicate, an insert field’s Defaultable, and the three *Seeds a single-dialect app wraps to stop repeating ::<Postgres, _>. Nothing that only ever appears as impl Trait in an argument position.
prepare
prepare!{}: declares a named, typed parameter struct for a reusable prepared query (select::Prepared). See that module’s doc comment for the design and its soundness caveat.
raw
The sql!{} escape hatch: a pressure valve for SQL constructs the typed builder doesn’t cover yet.
render
Rendering ExprKind into a SQL string plus a positional parameter list. Generic over D: Dialect for identifier quoting and placeholder style, but ExprKind/Value themselves stay closed, non-generic types, so this costs one instantiation per dialect used in a program rather than one per query shape.
row
Rows keyed by column rather than by position.
scope
Type-level FROM/JOIN scope tracking.
select
The SELECT builder: select(..).from(..).join(..).filter(..) .order_by(..).limit(..), in SQL keyword order, with tables passed as arguments rather than by turbofish.
statement
What INSERT, UPDATE and DELETE have in common: they write to one table, and any of them can be asked for the rows it touched.
update
UPDATE .. SET .. WHERE ...
window
Window functions: row_number()/rank()/dense_rank() .over(window() .partition_by(..).order_by(..)).

Macros§

label
label!(rank_in_user, rank_overall); — declares output-column names for computed selections, in a label module so a local binding of the same name can never shadow one. A scope holds one label module, so a scope gets one invocation listing every name it needs; an invocation inside the function that runs the query keeps those names next to their use.
prepare
sql
sql!(Bool, "lower(?) = ?", users::email, "dan") -> a Declared expression over whatever tables its slots name — keyed as row::Anon, so it is selectable but not readable by name until .label(..). Slots are filled positionally, left to right.
with
with! { struct recent_orders { id: Integer, total: BigInt } } — declares a CTE’s pseudo-table. Generates exactly what #[derive(Table)] does — a Table marker, per-column ColumnKey/Named markers, Column consts, and accessor traits — plus the CteShape impl cte::with checks a body against, so a bound CTE is a real table everywhere in the crate.

Derive Macros§

FromRow
#[derive(FromRow)]: fills the struct from a Row by matching each field’s name against the row’s keys. The struct itself stays free of column paths and query shape — the only thing it declares is what it wants called what, and #[from_row(rename = "..")] where the two names differ.
Table