chain-builder 3.1.0

A typed, dialect-aware SQL query builder for Rust (PostgreSQL/MySQL/SQLite).
Documentation
# SELECT

Every builder starts as a `SELECT` — `QueryBuilder::<Dialect>::table("users")`
with no other method calls compiles to `SELECT * FROM "users"`. This page
covers everything that shapes the SELECT list: plain columns, aliases,
aggregates, raw expressions, subquery columns, and `DISTINCT` /
`DISTINCT ON`. Filtering belongs to [WHERE](where.md), combining tables to
[JOIN](join.md).

## `select` — plain columns

`select` takes any iterable of column names and replaces the column list.
Every name is escaped per dialect; dotted identifiers are escaped per segment;
a literal `"*"` passes through unquoted; an empty list selects `*`:

```rust,ignore
let (sql, binds) = QueryBuilder::<Postgres>::table("users")
    .select(["id", "name"])
    .where_eq("status", "active")
    .to_sql();
// SELECT "id", "name" FROM "users" WHERE "status" = $1
```

```rust,ignore
let (sql, _) = QueryBuilder::<Postgres>::table("users")
    .select(["users.id"])
    .to_sql();
// SELECT "users"."id" FROM "users"
```

Escaping is not optional decoration — a hostile column name is neutralized by
quote-doubling rather than spliced in:

```rust,ignore
let (sql, _) = QueryBuilder::<Postgres>::table("users")
    .select([r#"id" ; DROP TABLE users; --"#])
    .to_sql();
// SELECT "id"" ; DROP TABLE users; --" FROM "users"
```

## `select_as` — column aliases

`select_as(col, alias)` emits `col AS alias`, with **both** identifiers
escaped:

```rust,ignore
let (sql, _) = QueryBuilder::<Postgres>::table("users")
    .select_as("created_at", "joined")
    .to_sql();
// SELECT "created_at" AS "joined" FROM "users"
```

## Aggregates

Five aggregate helpers — `select_count`, `select_sum`, `select_avg`,
`select_min`, `select_max` — each with an `_as` twin that adds an escaped
alias. The column is escaped; for `select_count` (and `select_count_as`) the
special column `"*"` passes through as `COUNT(*)`:

```rust,ignore
let (sql, _) = QueryBuilder::<Postgres>::table("users")
    .select_count("*")
    .to_sql();
// SELECT COUNT(*) FROM "users"
```

```rust,ignore
let (sql, _) = QueryBuilder::<Postgres>::table("orders")
    .select_sum("amount")
    .select_avg("amount")
    .select_min("amount")
    .select_max("amount")
    .to_sql();
// SELECT SUM("amount"), AVG("amount"), MIN("amount"), MAX("amount") FROM "orders"
```

Aggregates mix freely with plain columns and `GROUP BY` (see
[GROUP BY · HAVING · ORDER · LIMIT](group-having-order-limit.md)):

```rust,ignore
let (sql, _) = QueryBuilder::<Postgres>::table("orders")
    .select(["status"])
    .select_count_as("*", "cnt")
    .select_sum_as("amount", "total")
    .group_by(["status"])
    .to_sql();
// SELECT "status", COUNT(*) AS "cnt", SUM("amount") AS "total" FROM "orders" GROUP BY "status"
```

Qualified columns work inside aggregates too:
`select_sum("orders.amount")` → `SUM("orders"."amount")`.

## `select_raw` — verbatim expressions

`select_raw(sql, binds)` is the escape hatch for anything the structured API
does not cover (functions, casts, window expressions). The fragment is
appended to the column list after any `select` columns and aggregate/alias
expressions; multiple calls
accumulate; `binds` is an `Option<Vec<Value>>` (`None` for no binds).

> **⚠️ Positional placeholder contract**
>
> The `sql` fragment is emitted **verbatim** — it is NOT escaped and NOT
> renumbered. Its binds are appended to the running bind list in order. On
> **Postgres** you must hand-write `$N` numbers that match the actual bind
> position (number of binds already accumulated + 1, + 2, …); on MySQL/SQLite
> use `?`. A wrong `$N` produces a malformed query. The same contract applies
> to every `*_raw` method — see the [Security Model]../security.md for the
> full escape-hatch inventory.

```rust,ignore
let (sql, _) = QueryBuilder::<Postgres>::table("orders")
    .select_raw("COUNT(*)", None)
    .to_sql();
// SELECT COUNT(*) FROM "orders"
```

## `select_subquery` — scalar subquery columns

`select_subquery(alias, sub)` takes another builder by value and emits
`(<sub>) AS "alias"` after the regular columns and any `select_raw`
expressions. The subquery is compiled with **placeholder continuity**: since
the SELECT list renders before `WHERE`, the subquery's binds take the earlier
`$N` numbers — automatically, no manual numbering involved:

```rust,ignore
let (sql, binds) = QueryBuilder::<Postgres>::table("users")
    .select(["id"])
    .select_subquery(
        "cnt",
        QueryBuilder::<Postgres>::table("orders")
            .select_raw("COUNT(*)", None)
            .where_eq("status", 1i64),
    )
    .where_eq("active", true)
    .to_sql();
// SELECT "id", (SELECT COUNT(*) FROM "orders" WHERE "status" = $1) AS "cnt" FROM "users" WHERE "active" = $2
// binds == [Value::I64(1), Value::Bool(true)]
```

Correlated subqueries use `where_column` to reference the outer table —
see [WHERE](where.md).

## `distinct`

`distinct()` emits `SELECT DISTINCT …` and works on all dialects:

```rust,ignore
let (sql, _) = QueryBuilder::<Postgres>::table("t")
    .distinct()
    .select(["a"])
    .to_sql();
// SELECT DISTINCT "a" FROM "t"
```

## `distinct_on` — Postgres only

`distinct_on(cols)` emits Postgres' `SELECT DISTINCT ON (cols) …`; the columns
are escaped like any identifier:

```rust,ignore
let (sql, _) = QueryBuilder::<Postgres>::table("t")
    .distinct_on(["a", "b"])
    .select(["a"])
    .to_sql();
// SELECT DISTINCT ON ("a", "b") "a" FROM "t"
```

> **Dialect note** — MySQL and SQLite have no `DISTINCT ON`. Compiling a
> `distinct_on` builder against either dialect fails:
> [`try_to_sql()`]../error-handling.md returns
> `BuildError::DistinctOnRequiresPostgres`, and `to_sql()` panics with
> `DISTINCT ON requires PostgreSQL`. If the dialect is decided at runtime,
> use the `try_` form.

## Related pages

- [WHERE]where.md — filtering the rows you select
- [JOIN]join.md — combining tables
- [GROUP BY · HAVING · ORDER · LIMIT]group-having-order-limit.md — shaping the result set
- [Error Handling]../error-handling.md`to_sql()` vs `try_to_sql()` and `BuildError`
- [Security Model]../security.md — what `select_raw` does and does not protect