Skip to content

Getting Started

chain-builder is published on crates.io as chain-builder; the current version is 3.0.0. The builder itself is driver-agnostic — the sqlx_* feature flags only control which sqlx driver the execution layer compiles against.

Installation

Pick the line that matches your database:

BackendCargo.toml
MySQL (default)chain-builder = "3.0.0"
PostgreSQLchain-builder = { version = "3.0.0", default-features = false, features = ["sqlx_postgres"] }
SQLitechain-builder = { version = "3.0.0", default-features = false, features = ["sqlx_sqlite"] }

⚠️ The default = ["sqlx_mysql"] gotcha

The crate's default feature set is ["sqlx_mysql"]. If you target Postgres or SQLite and write chain-builder = { version = "3.0.0", features = ["sqlx_postgres"] }without default-features = false, your build silently compiles the MySQL driver in addition to the one you asked for. Everything still compiles, so the mistake is easy to miss — always add default-features = false when you are not on MySQL.

The driver features (sqlx_mysql, sqlx_postgres, sqlx_sqlite) may be enabled in any combination if you talk to more than one database.

Optional value features

These features extend the set of Rust types you can pass as bind values:

FeatureEnables bindingPulls in
jsonserde_json::Valueserde_json
uuiduuid::Uuiduuid, sqlx/uuid
chronochrono date/time typeschrono, sqlx/chrono
decimalrust_decimal::Decimalrust_decimal, sqlx/rust_decimal

See Binds & Values for the full type-mapping table.

Your first query

A builder is created with QueryBuilder::<Dialect>::table(...), configured by chaining methods, and turned into SQL with to_sql():

rust
use chain_builder::{QueryBuilder, Postgres};

let (sql, binds) = QueryBuilder::<Postgres>::table("users")
    .select(["id", "name"])
    .where_eq("status", "active")
    .to_sql();

assert_eq!(sql, r#"SELECT "id", "name" FROM "users" WHERE "status" = $1"#);
// binds == [Value::Text("active")]

Step by step:

  1. QueryBuilder::<Postgres>::table("users") — picks the dialect at the type level and names the table. The dialect decides quoting ("users") and placeholder style ($1).
  2. .select(["id", "name"]) / .where_eq("status", "active") — chain as many clauses as you need; every method returns the builder. The value "active" is not spliced into the string — it is recorded as a typed bind.
  3. .to_sql() — compiles the chain into a (String, Vec<Value>) pair: the SQL text with placeholders, and the bind values in placeholder order. Hand both to your database layer, or skip straight to the built-in sqlx integration.

to_sql() vs try_to_sql()

A handful of builder combinations cannot be compiled — for example distinct_on on a non-Postgres dialect, offset without limit, or a having() operator outside the allowlist. to_sql() panics on these misuse errors (they are programmer bugs, and the panicking form keeps simple code simple), while try_to_sql() returns Result<(String, Vec<Value>), BuildError> so you can surface the problem gracefully — the right choice when any part of the query is shaped by runtime input. Every panicking method has a try_ twin; the variants and the policy behind them are covered in Error Handling.

Executing with sqlx

You rarely need to touch the (sql, binds) pair yourself: with the matching sqlx_* feature enabled, the builder hands off directly to sqlx:

rust
use chain_builder::{QueryBuilder, Postgres};

#[derive(sqlx::FromRow)]
struct User { id: i64, name: String }

let users: Vec<User> = QueryBuilder::<Postgres>::table("users")
    .select(["id", "name"])
    .where_eq("status", "active")
    .fetch_all(&pool)
    .await?;

fetch_all, fetch_one, fetch_optional, execute, count, scalar fetches, and the lower-level to_sqlx_query family are all documented in Executing with sqlx.