Skip to content

dasSQLITE: schema_from + check_schema — compile-time schema introspection#2575

Merged
borisbat merged 4 commits into
masterfrom
dassqlite-schema-from
May 5, 2026
Merged

dasSQLITE: schema_from + check_schema — compile-time schema introspection#2575
borisbat merged 4 commits into
masterfrom
dassqlite-schema-from

Conversation

@borisbat
Copy link
Copy Markdown
Collaborator

@borisbat borisbat commented May 4, 2026

Summary

  • [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 → SqlError — runtime validator over (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.
  • 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; tut 02 + test_78 prove the collision is now handled).
  • Tutorials: 39-schema_from (basics + affinity table + partial-body contracts), 42-schema_evolution (multi-version ETL via typeinfo has_field + _each_sql streaming), and a check_schema section folded into 02-insert_data.
  • C++ shim: sqlite3_open_v2_no_vfs (READONLY-no-create open with NULL vfs; daslang string can't represent C NULL).

Affinity → daslang type

SQLite affinity Declared types daslang type
INTEGER INT/INTEGER/BIGINT/etc. int64
REAL REAL/DOUBLE/FLOAT double
TEXT TEXT/CHAR/VARCHAR/CLOB string
BLOB BLOB or no declared type array<uint8>
NUMERIC NUMERIC/DECIMAL/BOOLEAN/DATE/DATETIME double

NOT NULLT; otherwise → Option<T>. INTEGER PRIMARY KEY columns get @sql_primary_key synthesized. SQLite's INTEGER-PK-reports-notnull=0 quirk handled at both macro and runtime sides.

Tests

  • 4 compile-pass 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-version has_field branching + _each_sql), test_98_check_schema (7-case match/mismatch matrix incl. panic propagation).
  • 7 failed_* compile-error tests: field-not-in-DB / table-not-in-file / file-missing / type-mismatch / nullable-strict / nullable-loose / pk-disagreement.
  • 5 small (8 KB each) .db fixtures under tests/dasSQLITE/test_data/ plus companion .sql and a _gen_fixtures.das builder for regeneration.

Test plan

  • bin/Release/daslang.exe dastest/dastest.das -- --test tests/dasSQLITE/673/673 pass
  • bin/Release/test_aot.exe -use-aot dastest/dastest.das -- --use-aot --test tests/dasSQLITE/673/673 pass
  • MCP format_file clean on every changed .das
  • MCP lint clean on sqlite_boost.das + tutorials + tests

🤖 Generated with Claude Code

…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).
Copilot AI review requested due to automatic review settings May 4, 2026 21:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_from support to [sql_table] in sqlite_boost.das, synthesizing struct fields from PRAGMA table_info at 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_from and 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.

Comment thread modules/dasSQLITE/daslib/sqlite_boost.das Outdated
Comment thread modules/dasSQLITE/daslib/sqlite_boost.das Outdated
Comment thread modules/dasSQLITE/daslib/sqlite_boost.das Outdated
Comment thread tutorials/sql/02-insert_data.das
Comment thread doc/source/reference/tutorials/sql_02_insert_data.rst Outdated
Comment thread tutorials/sql/39-schema_from.das Outdated
Comment thread doc/source/reference/tutorials/sql_39_schema_from.rst Outdated
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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread modules/dasSQLITE/daslib/sqlite_boost.das Outdated
Comment thread doc/source/reference/tutorials/sql_42_schema_evolution.rst Outdated
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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread modules/dasSQLITE/daslib/sqlite_boost.das
Comment thread tests/dasSQLITE/test_96_schema_from_partial_body.das
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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@borisbat borisbat merged commit fa81fa1 into master May 5, 2026
34 checks passed
@borisbat borisbat deleted the dassqlite-schema-from branch May 14, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants