8.11.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 _select)

array<Car> — all columns, source struct

_select(_.FieldName)

array<FieldType> — one column, that column’s type

_select((Name=_.Name, Price=_.Price))

array<tuple<Name:string;Price:int>> — named tuple

_select((Name=_.Name, Bonus=_.Price/10))

named tuple with a computed column

8.11.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>

8.11.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>

8.11.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 chosen names live on the result tuple’s TypeDecl.argNamesbuild_row_builder sets that record type when it builds the row reader.

8.11.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" AS "Identifier", "Name" AS "Label" FROM "Cars"
// result: array<tuple<Identifier:int;Label:string>>

for (r in renamed) {
    to_log(LOG_INFO, "  Identifier={r.Identifier} Label={r.Label}\n")
}

8.11.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. The macro adds an AS "<name>" alias whenever the chosen name differs from the source column name, and a computed column always gets one. The row reader still maps result fields by position:

let bonuses <- _sql(db |> select_from(type<Car>)
                      |> _select((Name=_.Name, Bonus=_.Price / 10)))
// emits:  SELECT "Name", ("Price") / (?) AS "Bonus" 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.

8.11.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>

8.11.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"'

8.11.9.8. Quick reference

Form

Description

default (no _select)

Full row; array<T>

_select(_.Field)

Single column; array<FieldType>

_select((Name=_.X, Other=_.Y))

Named tuple; array<tuple<Name:..;Other:..>>

_select((Renamed=_.Id, ...))

Result fields can rename source columns

_select((N=_.Name, Bonus=_.Price/10))

Computed column; rendered via the predicate translator