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
Scopelist, aRowlist, aPredicate, an insert field’sDefaultable, and the three*Seeds a single-dialect app wraps to stop repeating::<Postgres, _>. Nothing that only ever appears asimpl Traitin 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
ExprKindinto a SQL string plus a positional parameter list. Generic overD: Dialectfor identifier quoting and placeholder style, butExprKind/Valuethemselves 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
SELECTbuilder:select(..).from(..).join(..).filter(..) .order_by(..).limit(..), in SQL keyword order, with tables passed as arguments rather than by turbofish. - statement
- What
INSERT,UPDATEandDELETEhave 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 alabelmodule so a local binding of the same name can never shadow one. A scope holds onelabelmodule, 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")-> aDeclaredexpression over whatever tables its slots name — keyed asrow::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 — aTablemarker, per-columnColumnKey/Namedmarkers,Columnconsts, and accessor traits — plus theCteShapeimplcte::withchecks a body against, so a bound CTE is a real table everywhere in the crate.