dasSQLITE: schema_from + check_schema — compile-time schema introspection#2575
Conversation
…tion [sql_table(schema_from="path.db")] opens the .db at compile time, reads pragma_table_info, and synthesizes the struct's fields from the actual schema. Hand-declared fields stay as contract assertions: name/PK/nullability checked at macro time, SqlType checked at typecheck via concept_assert emitted into _sql_table_name. Schema drift becomes a compile error at the exact lines that need updating — the macro-advantage analog to EF Core's runtime model snapshot, at zero runtime cost. check_schema(db, type<T>) / try_check_schema returning SqlError validates an open DB matches a struct on (name, SqlType, NOT NULL, PRIMARY KEY). Works on any [sql_table] struct, not just schema_from-derived ones — recommended startup-defense pattern for code that opens a DB it didn't just create. Tutorials: 39-schema_from (basics) + 42-schema_evolution (multi-version ETL via typeinfo has_field) + check_schema fold-in to 02-insert_data. Bonus fix: [sql_function] ExprAddr target switched to "__::name" so the typer's findMatchingFunctions scopes resolution to user's module — funcType-only disambiguation ties on same-signature imports (now that sqlite_boost requires daslib/fio, fio_core::normalize and a user def normalize(s : string) collide on (string)->string). Test data: tests/dasSQLITE/test_data/ ships 5 small (8 KB) .db fixtures plus companion .sql + a _gen_fixtures.das builder for regeneration. Tests: 4 compile-pass tests (test_95..98), 7 failed_* compile-error tests covering each contract violation. 673/673 pass interp + AOT. C++ shim: sqlite3_open_v2_no_vfs (READONLY-no-create open with NULL vfs; daslang's string parameter cannot represent C NULL).
There was a problem hiding this comment.
Pull request overview
This PR extends dasSQLITE with compile-time SQLite schema introspection ([sql_table(schema_from="...")]) and a runtime validator (check_schema / try_check_schema) to detect schema drift early. It also includes a small [sql_function] resolution fix, adds tutorials documenting the new workflow, and introduces fixtures/tests to cover success and failure modes.
Changes:
- Add
schema_fromsupport to[sql_table]insqlite_boost.das, synthesizing struct fields fromPRAGMA table_infoat compile time and emitting contract checks. - Add runtime schema validation helpers (
check_schema,try_check_schema) and tests/fixtures validating match and mismatch scenarios. - Add tutorials + Sphinx docs for
schema_fromand multi-version ETL patterns, plus a C++ shim (sqlite3_open_v2_no_vfs) for READONLY open semantics.
Reviewed changes
Copilot reviewed 28 out of 33 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tutorials/sql/42-schema_evolution.das | New tutorial demonstrating multi-version ETL using schema_from + typeinfo has_field + _each_sql. |
| tutorials/sql/39-schema_from.das | New tutorial introducing schema_from basics, affinity mapping, and check_schema. |
| tutorials/sql/02-insert_data.das | Adds check_schema startup-defense guidance to an existing tutorial. |
| tests/dasSQLITE/test_data/schema_from_v2.sql | Fixture DDL (v2 Logs table) for schema_from tests/tutorials. |
| tests/dasSQLITE/test_data/schema_from_v1.sql | Fixture DDL (v1 Logs table) for schema_from tests/tutorials. |
| tests/dasSQLITE/test_data/schema_from_nullable.sql | Fixture DDL for nullable column handling tests. |
| tests/dasSQLITE/test_data/schema_from_blob.sql | Fixture DDL for BLOB affinity handling tests. |
| tests/dasSQLITE/test_data/schema_from_all_affinities.sql | Fixture DDL covering all SQLite affinity classes. |
| tests/dasSQLITE/test_data/README.md | Documents what fixtures are and how to regenerate them. |
| tests/dasSQLITE/test_data/_gen_fixtures.das | Script to regenerate committed .db fixtures from SQL. |
| tests/dasSQLITE/test_98_check_schema.das | New runtime check_schema/try_check_schema tests. |
| tests/dasSQLITE/test_97_schema_from_typeinfo.das | New tests for typeinfo has_field branching on schema_from-synthesized structs. |
| tests/dasSQLITE/test_96_schema_from_partial_body.das | New tests validating partial-body contracts and narrowing behavior. |
| tests/dasSQLITE/test_95_schema_from_basic.das | New tests validating basic synthesis across affinities and nullability. |
| tests/dasSQLITE/failed_schema_from_type_mismatch.das | Compile-fail test for schema/type mismatch diagnostics. |
| tests/dasSQLITE/failed_schema_from_table_not_in_file.das | Compile-fail test for missing table in schema_from DB. |
| tests/dasSQLITE/failed_schema_from_pk_disagreement.das | Compile-fail test for PK disagreement between struct and DB. |
| tests/dasSQLITE/failed_schema_from_nullable_strict.das | Compile-fail test for non-optional field declared against nullable column. |
| tests/dasSQLITE/failed_schema_from_nullable_loose.das | Compile-fail test for optional field declared against NOT NULL/PK column. |
| tests/dasSQLITE/failed_schema_from_file_missing.das | Compile-fail test for missing schema_from DB file. |
| tests/dasSQLITE/failed_schema_from_field_not_in_db.das | Compile-fail test for extra struct field not present in DB schema. |
| modules/dasSQLITE/src/dasSQLITE.main.cpp | Adds sqlite3_open_v2_no_vfs shim and exports it to daslang. |
| modules/dasSQLITE/src/aot_sqlite.h | Declares the new shim for AOT builds. |
| modules/dasSQLITE/daslib/sqlite_boost.das | Implements schema_from, check_schema, PRAGMA helpers, and [sql_function] ExprAddr scoping fix. |
| doc/source/reference/tutorials/sql_42_schema_evolution.rst | Sphinx documentation page for the new SQL-42 tutorial. |
| doc/source/reference/tutorials/sql_39_schema_from.rst | Sphinx documentation page for the new SQL-39 tutorial. |
| doc/source/reference/tutorials/sql_02_insert_data.rst | Adds check_schema section to SQL-02 tutorial docs. |
| doc/source/reference/tutorials.rst | Adds the new SQL-39 and SQL-42 tutorials to the index. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
PR #2575 review iteration: - Option<T> contract: macro-time unwrap before sql_bind witness so partial-body Option<T> against nullable columns no longer trips the generic concept_assert (regression test in test_96). - Name-keyed check_schema: SELECTs are explicit-column not SELECT *, so struct field order is independent of DB column order. Drops the false-positive positional-mismatch path; new tests cover reordered structs and missing fields. - read_pragma_table_info honors db.schema_name so check_schema works on with_schema / ATTACH'd runners (regression test_check_schema_attached_schema). - Tutorials: "Result form" -> "non-panicking Option form"; NUMERIC affinity partial-body wording clarified - overrides refine within the same SqlType, not across. - sql_39 RST: pad title overline (CI -W tripped on title-overline-too-short). - skills/make_pr.md: route step 4 by what changed; add overline-too-short and TOC-missing to common Sphinx issues. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 34 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
PR #2575 review round 2: - try_open_sqlite + try_open_sqlite_readonly now NULL-guard the sqlite_handle before calling sqlite3_errmsg. SQLite writes NULL into *ppDb on OOM-during- open; sqlite3_errmsg(NULL) is undefined behavior. close_v2(NULL) is a documented no-op so the close stays. try_* contract is "no crash". - sql_42 schema_evolution RST: code-block paths in the migration loop now carry the full tests/dasSQLITE/test_data/ prefix, matching the .das source and lines 37/41 of the same page. Snippets now copy/paste-runnable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 34 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
PR #2575 review round 3 — three intertwined Option<T> bugs surfaced by Copilot. 1. Generic `sql_bind($Option<auto(TT)>)` fallback in sqlite_boost mirrors the existing `sql_storage_enum_for(type<$Option<auto(TT)>>)` pattern. Uses `_::sql_bind(default<TT>)` so caller-module adapters resolve; without `_::`, recursion runs in sqlite_boost's module and misses `@sql_json`/`@sql_blob`-generated overloads. 2. `emit_json_blob_adapters_for_struct` now unwraps Option<T> before adapter codegen. Runtime `sql_bind_to_stmt(Option<T>)` strips the Option BEFORE calling user `sql_bind`; registering the adapter for `Option<NoteData>` was dead code — actual call lands on `sql_bind(NoteData)`. Adapter dedup now naturally collapses bare T and Option<T> in the same struct (both key as T). 3. Drop the macro-time Option unwrap in `apply_schema_from` from the prior round; the witness is back to `_::sql_bind(default<field_type>)`, with rail #1 handling the unwrap uniformly. Tests: - test_96_schema_from_partial_body: @sql_json + Option, @sql_blob + Option via schema_from path (contract + roundtrip). - test_64_json_columns: standalone @sql_json + Option, plus dedup edge case (bare X + Option<X> in same struct). - test_65_blob_struct: standalone @sql_blob + Option. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 37 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
[sql_table(schema_from="path.db")]— opens the.dbat compile time, readspragma_table_info, and synthesizes the struct's fields from the actual schema. Hand-declared fields stay as contract assertions: name/PK/nullability checked at macro time, SqlType checked at typecheck viaconcept_assertemitted into_sql_table_name. Schema drift becomes a compile error at the exact lines that need updating — the macro-advantage analog to EF Core's runtime model snapshot, at zero runtime cost.check_schema(db, type<T>)/try_check_schema → SqlError— runtime validator over (name, SqlType, NOT NULL, PRIMARY KEY). Works on any[sql_table]struct, not justschema_from-derived ones. Recommended startup-defense pattern for code that opens a DB it didn't just create.[sql_function]ExprAddr target switched to__::nameso the typer'sfindMatchingFunctionsscopes resolution to user's module — funcType-only disambiguation ties on same-signature imports (now thatsqlite_boostrequiresdaslib/fio,fio_core::normalizeand a userdef normalize(s : string)collide on(string)→string; tut 02 + test_78 prove the collision is now handled).39-schema_from(basics + affinity table + partial-body contracts),42-schema_evolution(multi-version ETL viatypeinfo has_field+_each_sqlstreaming), and acheck_schemasection folded into02-insert_data.sqlite3_open_v2_no_vfs(READONLY-no-create open withNULLvfs; daslangstringcan't represent CNULL).Affinity → daslang type
int64doublestringarray<uint8>doubleNOT NULL→T; otherwise →Option<T>.INTEGER PRIMARY KEYcolumns get@sql_primary_keysynthesized. SQLite's INTEGER-PK-reports-notnull=0 quirk handled at both macro and runtime sides.Tests
test_95_schema_from_basic(all-affinities sweep),test_96_schema_from_partial_body(type narrowing + PK redundancy + round-trip),test_97_schema_from_typeinfo(multi-versionhas_fieldbranching +_each_sql),test_98_check_schema(7-case match/mismatch matrix incl. panic propagation).failed_*compile-error tests: field-not-in-DB / table-not-in-file / file-missing / type-mismatch / nullable-strict / nullable-loose / pk-disagreement..dbfixtures undertests/dasSQLITE/test_data/plus companion.sqland a_gen_fixtures.dasbuilder for regeneration.Test plan
bin/Release/daslang.exe dastest/dastest.das -- --test tests/dasSQLITE/→ 673/673 passbin/Release/test_aot.exe -use-aot dastest/dastest.das -- --use-aot --test tests/dasSQLITE/→ 673/673 passformat_fileclean on every changed.daslintclean onsqlite_boost.das+ tutorials + tests🤖 Generated with Claude Code