7.9.9. SQL-09 — _select Projections
_select(...) controls what columns the SQL emits and what shape
the result has. Three forms ship in chunk 3:
Form |
Result shape |
|---|---|
default (no |
|
|
|
|
|
7.9.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.9.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.9.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.9.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.9.9.5. 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.9.9.6. 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.9.9.7. Quick reference
Form |
Description |
|---|---|
default (no |
Full row; |
|
Single column; |
|
Named tuple; |
|
Result fields can rename source columns |
Deferred to chunk 4: struct-type projection _select(type<T2>) for
projecting into a different [sql_table] struct. Today users get
the same result via named-tuple projection.
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