Repository navigation
Declared tables: require an explicit name key, stop deriving it from ddl - #968
Merged
Merged
Conversation
A table's name is declared, never derived from `ddl`. These suites pin the three observable rules across all three SDKs, at the binding/integration and e2e tiers: - a `[[table]]` entry's declared `name` is what the table registers as - an entry without `name` is a load error naming the key - a `name` the entry's `ddl` never creates is a load error carrying the config-entry prefix (`table 'messages'`), caught against SQLite's own catalog before any ingestion Quoted, schema-qualified and `IF NOT EXISTS` DDL forms are covered too: those are SQL delimiter forms, so the catalog records the bare name and the declared `name` matches without dirsql reading the DDL text. RED: all four Rust integration cases and all three Rust e2e cases fail today (`name` is an unknown key; a nameless entry loads fine). Refs #962 skip-changelog: test-only commit; the behavior change ships with the implementation commit Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hvg3vjPNZPa6EyqvFoNb9X
A table's SQL name was derived by tokenizing the `CREATE TABLE` head of `ddl` (`db::parse_table_name`) -- bespoke SQL-as-text parsing that had grown cases for quoted identifiers, `schema.table`, `IF NOT EXISTS` and leading comments. Never reinvent what SQLite expresses natively: the name is now declared, and SQLite's own catalog settles whether the DDL produced it. - `[[table]]` gains a required `name` key; `Table::new` / `try_new` / `strict` take it first; Python `Table(name=...)`; TypeScript `TableDef.name`. - Every call site that keyed on the parsed name reads `table.name` instead: the build path, `compile_matcher`, `compute_glob_config_hash`, and duplicate-table detection across composed configs. - `Db::create_table` takes `(name, ddl)`. `validate_identifier` stays -- `name` is spliced into `format!()`-built INSERT/DELETE SQL, so it remains the injection guard. - After the DDL runs, `table_exists` asks `pragma_table_list` whether `name` is in the catalog. No row is `DirSqlError::TableNotCreated`, raised before any file is ingested and carrying the config entry's name. - `db::parse_table_name` is deleted, with the `strip_keyword_ci` / `parse_identifier` / `skip_ws_comments` tokenizer behind it, and so is the TypeScript `parseTableName` export that wrapped it. Quoted, schema-qualified and `IF NOT EXISTS` DDL keep working without any of that: SQLite records the bare identifier, so a plain `name` matches. Manually exercised against the release binary over a real temp dir: the happy path returns rows; a nameless entry exits 1 with `Missing required field 'name' in [[table]] entry`; `name = "messages"` over a `ddl` creating `notes` exits 1 with `table 'messages': its `ddl` ran but created no table called 'messages'.`; and all three of `CREATE TABLE "messages"`, `CREATE TABLE main.messages` and `CREATE TABLE IF NOT EXISTS messages` load and query under `name = "messages"`. GREEN: the red suites from the previous commit now pass in all three SDKs. Fixes #962 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hvg3vjPNZPa6EyqvFoNb9X
thekevinbot
marked this pull request as ready for review
August 20, 2026 18:24
thekevinscott
enabled auto-merge
August 20, 2026 18:40
`mutants.out/` was already ignored, but cargo-mutants rotates the previous run's directory to `mutants.out.old/` on every re-run — so the second local run of the mutation gate leaves an untracked directory behind. Same rule, both forms. skip-changelog: repo tooling only; no packaged code changes Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hvg3vjPNZPa6EyqvFoNb9X
8 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #962
A
[[table]]entry gains a requiredname. dirsql stops tokenizing theCREATE TABLEhead ofddlto learn the table name — that was bespokeSQL-as-text parsing, and SQLite's own catalog answers the question natively.
What changed
nameis required on[[table]](missing →Missing required field 'name' in [[table]] entry; blank →EmptyField). Duplicate-table detection across composed configs comparesnamekeys.table.name: the build path,compile_matcher,compute_glob_config_hash, and the config merge.db::parse_table_nameis deleted, along with thestrip_keyword_ci/parse_identifier/skip_ws_commentstokenizer behind it and its ~165 lines of unit tests.Db::create_tablenow takes(name, ddl).validate_identifierstays —nameis spliced intoformat!()-built INSERT/DELETE SQL, so it remains the injection guard.create_table,table_existsaskspragma_table_listwhethernameis inmain. No row →DirSqlError::TableNotCreated, raised before any file is ingested and carrying the config-entry prefix.Table::new(name, ddl, glob, on_file)(andtry_new/strict),Table(name=..., …),TableDef.name.PARITY.mdupdated.parseTableName(ddl)is removed — it wrapped the core parser that no longer exists. Its napi export and colocated tests go with it.Quoted (
CREATE TABLE "messages"), schema-qualified (main.messages) andIF NOT EXISTSDDL keep working with a plainname = "messages": SQLite records the bare identifier in its catalog, so nothing has to interpret the DDL text.Parity: restoring/holding, not drifting — all three SDKs gain the
nameargument in this PR, so no follow-up issues are needed.Manual verification
Against the release binary over a real temp dir (
data/a.jsonholding[{"id":"one"},{"id":"two"}]):Red/green
2c9c3f3pushed the failing suites alone; Rust CI run 32398345357 went red on exactly their assertions (unknown field \name`, and a nameless entry loading fine), not on anything incidental.706ed09` turns them green.E2E Verification
packages/python/e2e-attestation.jsonrefreshed ifpackages/pythonchangedpackages/ts/e2e-attestation.jsonrefreshed ifpackages/tschangedcargo test --manifest-path packages/rust/Cargo.toml --features cli --lib --testscd packages/python && uv run python -m pytest tests/e2e/ -qcd packages/ts && pnpm test:e2eChangelog / Migrations
packages/<pkg>/changelog.d/for each changed packagepackages/<pkg>/migrations.d/— breaking config + API change, all three packages