7.9.40. SQL-39 — schema_from: struct mirrors the DB
[sql_table(schema_from = "path.db")] opens the .db at compile
time, reads pragma_table_info, and populates the struct’s
fields from the actual schema. The struct mirrors the database —
which means schema drift becomes a compile error at the exact
lines that need updating. No reflection, no migration metadata,
no model snapshots; the type system carries the contract.
7.9.40.1. Empty body
require daslib/sql
require sqlite/sqlite_boost
[sql_table(name = "Logs",
schema_from = "tests/dasSQLITE/test_data/schema_from_v2.db")]
struct LogEntry {}
The macro reads schema_from_v2.db’s Logs table at compile
time and synthesizes the struct as if you’d written:
[sql_table(name = "Logs")]
struct LogEntry {
@sql_primary_key Id : int64
Severity : int64
Message : string
}
7.9.40.2. Affinity to daslang type
SQLite affinity |
Declared types |
daslang type |
|---|---|---|
INTEGER |
INT/INTEGER/BIGINT/SMALLINT/… |
|
REAL |
REAL/DOUBLE/FLOAT |
|
TEXT |
TEXT/CHAR/VARCHAR/CLOB |
|
BLOB |
BLOB or no declared type |
|
NUMERIC |
NUMERIC/DECIMAL/BOOLEAN/DATE/DATETIME |
|
NOT NULL columns synthesize as T; otherwise Option<T>.
INTEGER PRIMARY KEY columns get @sql_primary_key synthesized.
NUMERIC affinity is the rough edge — BOOLEAN/DATE columns stored
under NUMERIC all bind as REAL/double. Partial-body fields can
refine within the same storage SqlType (e.g. int instead of
synthesized int64, or float instead of double), and
@sql_json / @sql_blob adapters are accepted because they
still bind to the column’s underlying SqlType. You CAN’T swap a
NUMERIC column to bool or a date type at the field level —
that would need a custom adapter that binds back to REAL.
7.9.40.3. Partial body — hand-declared fields are CONTRACTS
A hand-declared field is an assertion that the schema looks a
particular way, not an override of the schema. Same-affinity
narrowing is allowed (int instead of synthesized int64);
@sql_json / @sql_blob overrides are accepted because they
route through the existing custom-types adapter rail.
// Same-affinity narrowing: int instead of int64 for the PK.
[sql_table(name = "Logs",
schema_from = "tests/dasSQLITE/test_data/schema_from_v2.db")]
struct LogRefined {
@sql_primary_key Id : int // narrowed; Severity + Message synthesized
}
// Annotation override: @sql_json on a TEXT column tells the macro
// to bind/extract via JSON encoding for a structured payload.
struct Note {
title : string
rank : int
}
[sql_table(name = "Items",
schema_from = "items.db")]
struct Item {
@sql_json Meta : Option<Note> // TEXT column on disk; JSON-encoded daslang side
}
What you cannot do via partial body:
Declare
stringagainst an INTEGER column — type mismatch.Declare
T(non-optional) against a nullable column — nullability mismatch.Declare
Option<T>against a NOT NULL column — nullability mismatch.Declare
@sql_primary_keyon a non-PK column — PK mismatch.Declare a field that doesn’t exist in the .db schema.
Each fails at compile time with a message naming the field, the column, and the source path.
7.9.40.4. Path resolution
Absolute path — as-is.
Relative path — resolved under daslang’s project root (
get_das_root()).
For .db files alongside the source, pass an absolute path or
arrange the project so the relative form lands under
get_das_root().
7.9.40.5. check_schema — runtime startup defense
The companion runtime helper validates an open DB matches the
[sql_table] struct. Useful even without schema_from —
it’s the recommended startup-defense pattern for any code that
opens a DB it didn’t just create.
with_sqlite("logs.db") <| $(db) {
db |> check_schema(type<LogEntry>) // panics on mismatch
// ... or for the Result form:
// let r = db |> try_check_schema(type<LogEntry>)
// if (r |> is_some) { panic(r |> unwrap) }
}
Diagnostics name the column and the divergence (count, name, SqlType, NOT NULL, PRIMARY KEY).
7.9.40.6. When NOT to use schema_from
schema_from is for code-on-current-schema: the DB’s shape
today is what the script reads/writes. ETL between two DBs,
archival readers, admin tooling — all good fits.
For “the DB grows over time, run versioned schema migrations at
startup”, a future daslib/sqlite_migrate module ships
[sql_migration(version=N)] + a runtime runner. The two are
orthogonal: schema_from gives compile-time contract checks
against a known schema; sqlite_migrate evolves the schema over
time. See SQL-42 — multi-version ETL with schema_from for the
multi-version ETL pattern.
7.9.40.7. AOT semantic-hash interaction
The macro’s output is part of the struct’s semantic hash. If the .db schema changes between AOT-gen and runtime, the AOT link fails and the runtime falls back to the interpreter — no silent miscompile.
See also
Full source: tutorials/sql/39-schema_from.das
Next tutorial: SQL-40 — FTS5 full-text search
Multi-version ETL: SQL-42 — multi-version ETL with schema_from