7.10.9. SQL-09 — _select Projections
_select(...) controls what columns the SQL emits and what shape
the result has. The supported forms:
Form |
Result shape |
|---|---|
default (no |
|
|
|
|
|
|
named tuple with a computed column |
7.10.9.1. Default projection: full row
No _select — the macro emits SELECT of every column declared
in the [sql_table] struct, in declaration order, and materializes
each row as the source struct:
let cars <- _sql(db |> select_from(type<Car>))
// emits: SELECT "Id", "Name", "Price" FROM "Cars"
// result: array<Car>
7.10.9.2. Single-column projection
_select(_.Field) projects exactly one column. The result element
type matches the column’s daslang type (int, string, …):
let names <- _sql(db |> select_from(type<Car>) |> _select(_.Name))
// emits: SELECT "Name" FROM "Cars"
// result: array<string>
7.10.9.3. Named-tuple projection
_select((Name=_.Name, Price=_.Price)) projects multiple columns
into a daslang tuple whose recordNames preserve the chosen
names. You read tuple fields by the chosen name — the source field
name no longer matters at the use site:
let pairs <- _sql(db |> select_from(type<Car>)
|> _select((Name=_.Name, Price=_.Price)))
// emits: SELECT "Name", "Price" FROM "Cars"
// result: array<tuple<Name:string;Price:int>>
for (p in pairs) {
to_log(LOG_INFO, " {p.Name} (price={p.Price})\n")
}
The recordNames live on the result tuple’s TypeDecl.argNames;
build_row_builder constructs that recordType explicitly because
the ExprMakeTuple’s own recordNames vector isn’t bound to daslang
yet.
7.10.9.4. Renaming via named tuple
Because the tuple’s recordNames are independent of the source columns, you can rename freely. The result fields can shadow keywords or just match the domain language better than the SQL column names do:
let renamed <- _sql(db |> select_from(type<Car>)
|> _select((Identifier=_.Id, Label=_.Name)))
// emits: SELECT "Id", "Name" FROM "Cars"
// result: array<tuple<Identifier:int;Label:string>>
for (r in renamed) {
to_log(LOG_INFO, " Identifier={r.Identifier} Label={r.Label}\n")
}
7.10.9.5. Computed columns
A named-tuple value can be any computed expression over the row’s
columns, not just a bare column reference. It is rendered into the
SELECT list by the same translator that powers _where
predicates, so anything legal in a predicate works as a column. Result
fields map by position, so no SQL alias is emitted:
let bonuses <- _sql(db |> select_from(type<Car>)
|> _select((Name=_.Name, Bonus=_.Price / 10)))
// emits: SELECT "Name", ("Price") / (?) FROM "Cars"
// result: array<tuple<Name:string;Bonus:int>>
A computed column composes with a computed _where — the predicate
and projection binds are emitted in SQL-clause order.
Any predicate-legal shape works as a column: string concatenation
(Label = _.Name + " car" → ("Name") || (?)) and workhorse casts
(AsText = string(_.Price) → CAST("Price" AS TEXT)) both render via
the same translator. The runnable
09-select.das shows the
Bonus = _.Price / 10 form end-to-end.
7.10.9.6. Combining with terminals
_select composes with _first / _first_opt / count /
_where exactly as you’d expect — it’s just another chain stage:
let head = _sql(db |> select_from(type<Car>)
|> _select((Name=_.Name, Price=_.Price))
|> _first())
// result: tuple<Name:string;Price:int>
7.10.9.7. Inspecting the emitted SQL
_sql_text returns the SQL the macro would emit:
let sql = _sql_text(db |> select_from(type<Car>)
|> _select((Name=_.Name, Price=_.Price)))
// sql == 'SELECT "Name", "Price" FROM "Cars"'
7.10.9.8. Quick reference
Form |
Description |
|---|---|
default (no |
Full row; |
|
Single column; |
|
Named tuple; |
|
Result fields can rename source columns |
|
Computed column; rendered via the predicate translator |
See also
Full source: tutorials/sql/09-select.das
Previous tutorial: SQL-08 — _where Predicates: the Full Surface
Next tutorial: SQL-10 — _order_by and _order_by_descending