dasSQLITE chunk 14b: typed ALTER macros + module rename#2592
Merged
dasSQLITE chunk 14b: typed ALTER macros + module rename#2592
Conversation
Migrations as designed are SQLite-specific (the __schema_version audit
table, BEGIN IMMEDIATE, unixepoch(), busy-timeout-based concurrency).
Sitting under the daslib/sql_* namespace would lie about provider
neutrality. When the eventual dasSQL abstraction lands, an abstract
migration runner would naturally take the daslib/sql_migrate name; this
provider-specific piece moves to daslib/sqlite_migrate. Doing the
rename now keeps the public surface honest and avoids a churn migration
when the abstraction lands.
Mechanical search-replace across 40 sites:
- file move + 'module sql_migrate' -> 'module sqlite_migrate'
- internal qmacro 'sql_migrate::_add_migration_entry' -> 'sqlite_migrate::'
- internal init thunk register'sql'migrations -> register'sqlite'migrations
- 'require sqlite/sql_migrate' (23 tests + tutorial 43 + skills/sql.md
+ sql_43_migrations.rst) -> 'require sqlite/sqlite_migrate'
- 'require daslib/sql_migrate' (mockups + design docs) -> 'daslib/sqlite_migrate'
- prose references in API_REWORK / API_MIGRATION / API_MISSING /
TUTORIALS / tutorials/sql/{39,41,42}-*.das + matching RST files
- modules/dasSQLITE/.das_module sqlite_paths array
- tests/aot/CMakeLists.txt AOT module list
Annotation [sql_migration] is unchanged: SQL-level conceptual noun, an
abstract migration layer landing later could reuse it.
dastest tests/dasSQLITE: 728/728 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the typed-ALTER ergonomics layer designed in API_MIGRATION.md
Scenario 2 on top of the 14a migrations spine. New macros under
daslib/sqlite_boost (transitively visible via daslib/sqlite_migrate's
existing public re-export):
db |> add_column(type<T>, "Field" [, defaultLit])
db |> create_index(type<T>, "Field" or ("A","B") [, "name"])
db |> create_unique_index(type<T>, ...same shape...)
db |> drop_index_if_exists("name")
What the macros buy:
- compile-time field-name + struct-shape checks (no more "user's DB
panics on startup" surprises),
- type-derived NOT NULL: macro refuses to ADD a non-nullable column
without a literal default; Option<T> wrapping = nullable,
- @sql_column rename + @sql_json / @sql_blob storage are honored,
same conventions sql_table / sql_index already use,
- compile-time rejection for shapes that need a table rebuild
(PK / UNIQUE inline / generated / sql_default_fn).
Field selectors are string literals, not the .Field syntax used
inside _sql blocks: gen2's parser only synthesizes _ as a placeholder
inside that block scope. Plain call sites pass explicit string field
names -- same convention sql_index(fields="A") already uses.
create_unique_index is a separate macro rather than a unique=true
named arg because gen2's named-arg form [name=val] produces an
ExprNamedCall that call_macro does not intercept; positional
separation is cleaner and fully discoverable.
Implementation notes:
- AddColumnMacro emits qmacro(_::exec(db, build_string)) with a body
that writes prefix + sql_storage_type_for(type<T>) + suffix --
runtime SQL-type derivation via the existing _::sql_bind adapter
rail.
- CreateIndex(Unique)Macro: pure macro-time DDL build via
derive_index_name (extracted from SqlIndexMacro for reuse).
- drop_index_if_exists: 5-line runtime function, IF EXISTS native.
- is_option_field_type relaxed: now matches both the typeMacro
pre-instantiation form (what sql_table sees during structure_macro
apply) AND the post-template-instantiation tStructure form (what
call_macros see at infer time). The latter has _module pointing to
the callee (downstream of the template instantiation site), so
module-name matching was unsound -- switch to Option<...> name
shape match.
Tests (16 new, AOT-clean): 8 positives + 1 runtime-error (dup column
runtime panic rolls back via alpha-shape) + 7 failed_* compile-error
negatives. tests/dasSQLITE: 748/748 pass, no GC leaks.
Tutorial 43 + RST refreshed:
- migrations 002 / 003 rewritten to use the typed forms (raw-SQL
equivalent kept as a sidebar comment),
- new "Typed ALTER" section between Inspection and Adoption,
- migrations 004 / 005 added showing index + idempotent rebuild.
API_MIGRATION.md, API_REWORK.md, TUTORIALS.md updated to mark 14b as
shipped. 14c (struct rebuild) remains pending.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Two commits, in order:
1.
dasSQLITE: rename daslib/sql_migrate -> daslib/sqlite_migrateMigrations as currently designed are SQLite-specific (the
__schema_versionaudit table,BEGIN IMMEDIATE,unixepoch(), busy-timeout-based concurrent-runner contract). Sitting under thedaslib/sql_*namespace would lie about provider neutrality. When the dasSQL abstraction lands, an abstract migration runner naturally takes thedaslib/sql_migratename; this provider-specific piece moves todaslib/sqlite_migrate. Doing the rename now keeps the public surface honest and avoids a churn migration when the abstraction lands.Mechanical search-replace across 40 sites: file move +
module sql_migrate->module sqlite_migrate, internal qmacrosql_migrate::_add_migration_entry->sqlite_migrate::, internal init thunkregister'sql'migrations->register'sqlite'migrations, all 23 test-siderequire sqlite/sql_migrate->require sqlite/sqlite_migrate, mockupdaslib/sql_migrate->daslib/sqlite_migrate, prose references in API_REWORK / API_MIGRATION / API_MISSING / TUTORIALS / tutorials/sql/{39,41,42}-*.das + matching RST files,modules/dasSQLITE/.das_modulesqlite_pathsarray,tests/aot/CMakeLists.txtAOT module list.The annotation
[sql_migration]is unchanged (SQL-level conceptual noun; an abstract migration layer landing later could reuse it).2.
dasSQLITE chunk 14b: typed ALTER macrosAdds the typed-ALTER ergonomics layer designed in
API_MIGRATION.mdScenario 2 on top of the 14a migrations spine. New macros underdaslib/sqlite_boost(transitively visible viadaslib/sqlite_migrate's existingpublicre-export):What the macros buy:
Option<T>wrapping = nullable,@sql_columnrename +@sql_json/@sql_blobstorage are honored, same conventions[sql_table]/[sql_index]already use,@sql_default_fn).Field selectors are string literals, not the
.Fieldsyntax used inside_sql {...}blocks — gen2's parser only synthesizes_as a placeholder inside that block scope. Plain call sites pass explicit string field names, same convention[sql_index(fields="A")]already uses.create_unique_indexis a separate macro rather than aunique=truenamed arg because gen2's named-arg form[name=val]produces anExprNamedCallthat[call_macro]doesn't intercept; positional separation is cleaner.Implementation notes:
AddColumnMacroemitsqmacro(_::exec($e(db), build_string $(w) { ... }))writingprefix + sql_storage_type_for(type<T>) + suffix— runtime SQL-type derivation via the existing_::sql_bindadapter rail.CreateIndex(Unique)Macro: pure macro-time DDL build viaderive_index_name(extracted fromSqlIndexMacrofor reuse).drop_index_if_exists: 5-line runtime function, IF EXISTS native.is_option_field_typerelaxed: now matches both the typeMacro pre-instantiation form (what[sql_table]sees during structure_macroapply()) AND the post-template-instantiationtStructureform (what call_macros see at infer time). The latter has_module=<callee>(downstream of the template instantiation site), so module-name matching was unsound — switched to "Option<...>" name-shape match.Tests
16 new under
tests/dasSQLITE/:migrate_14_*throughmigrate_21_*):Option<T>-> nullable;int + default=0-> NOT NULL DEFAULT + backfill; embedded single-quote escaping in DEFAULT;@sql_columnrename propagation;@sql_json-> TEXT;@sql_blobstruct -> BLOB; auto-named single-col index; UNIQUE composite + named index +drop_index_if_existsidempotence.migrate_22_add_column_dup_runtime): re-adding the same column at runtime panics inside the migration; α-shape transaction rolls back the whole call (audit table empty, table doesn't exist).failed_*compile-error negatives: unknown field, no[sql_table], PK rejected, UNIQUE rejected, non-literal default, unknown index field, empty fields list.tests/dasSQLITE: 748/748 pass interpreted and AOT, no GC leaks, lint clean, Sphinx clean (no warnings or errors).Tutorial
tutorials/sql/43-migrations.das+doc/source/reference/tutorials/sql_43_migrations.rstrefreshed:create_index+ idempotent rebuild viadrop_index_if_exists.API_MIGRATION.md,API_REWORK.md,TUTORIALS.mdupdated to mark 14b as shipped. Chunk 14c (struct rebuild) remains pending.🤖 Generated with Claude Code