7.9.26. SQL-25 — Defaults + computed columns

Three ways to default a column, plus generated columns via @sql_computed.

7.9.26.1. Three default sources

Form

Behavior

Active : bool = true

native daslang field-init becomes DEFAULT 1

@sql_default_fn = "CURRENT_TIMESTAMP"

SQL built-in (whitelist below)

@sql_computed = "Quantity * 2"

generated column — excluded from INSERT/UPDATE bind

Native field-init handles bool / int / int64 / float / double / string literals. Non-literal initializers (e.g. some(5)) are silently dropped from the DDL but still construct in daslang.

Whitelisted @sql_default_fn values: CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME. Anything else is a compile error.

7.9.26.2. VIRTUAL vs STORED

@sql_computed defaults to VIRTUAL (recomputed on every SELECT). Add @sql_stored = true for STORED (materialized on INSERT / UPDATE, persisted on disk, read straight off disk on SELECT).

Use STORED when the expression is expensive and reads dominate writes. Default to VIRTUAL otherwise.

7.9.26.3. Schema example

[sql_table(name = "Items")]
struct Item {
    @sql_primary_key Id : int

    Name : string

    Active : bool = true                     // -> DEFAULT 1
    Quantity : int = 100                     // -> DEFAULT 100
    Tag : string = "unknown"                 // -> DEFAULT 'unknown'

    @sql_default_fn = "CURRENT_TIMESTAMP"
    CreatedAt : string

    @sql_computed = "Quantity * 2"
    DoubleQty : int                          // VIRTUAL

    @sql_computed = "Quantity + 1"
    @sql_stored = true
    QtyPlusOne : int                         // STORED
}

7.9.26.4. Macro INSERT binds every NON-COMPUTED column

The struct field’s value is what gets written; defaults never fire through this path. Computed columns are skipped — SQLite computes them itself, so any value the struct holds for DoubleQty / QtyPlusOne is ignored.

db |> insert(Item(Id = 1, Name = "thing", Active = true, Quantity = 7,
                  Tag = "t", CreatedAt = "",
                  DoubleQty = 9999, QtyPlusOne = 9999))
// INSERT INTO "Items" ("Id","Name","Active","Quantity","Tag","CreatedAt")
//   VALUES (?,?,?,?,?,?)

7.9.26.5. Defaults fire when the column is omitted

The macro INSERT always names every non-computed column, so the DEFAULT clause only fires from raw SQL or column-list forms that omit the column.

db |> exec("INSERT INTO Items (Id, Name) VALUES (2, 'defaulted')")
// -> "Active" DEFAULT 1, "Quantity" DEFAULT 100,
//    "Tag" DEFAULT 'unknown', "CreatedAt" DEFAULT CURRENT_TIMESTAMP all fire

7.9.26.6. UPDATE re-derives computed columns

Mutating Quantity is enough — the SET clause omits any @sql_computed column (typo at the macro level produces a do_update column "X" is @sql_computed error). DoubleQty / QtyPlusOne update behind the scenes when SQLite re-evaluates the expression.

db |> _sql_update(type<Item>, _.Id == 1, (Quantity = 11))
// UPDATE "Items" SET "Quantity" = ? WHERE "Id" = ?

7.9.26.7. Validation rules

[sql_table] rejects each of the following at compile time:

  • @sql_primary_key + @sql_computed

  • @sql_computed + field initializer

  • @sql_computed + @sql_default_fn

  • field initializer + @sql_default_fn

  • @sql_default_fn value outside the whitelist

7.9.26.8. Minimum SQLite version

Generated columns require SQLite 3.31+ (2019-10-10).

See also

Full source: tutorials/sql/25-defaults_computed.das

Previous tutorial: SQL-24 — Indexes

Next tutorial: SQL-26 — Custom type adapters