TML-2859: SQLite migration planner builds CREATE TABLE as typed DDL through the adapter#768
TML-2859: SQLite migration planner builds CREATE TABLE as typed DDL through the adapter#768wmadden-electric wants to merge 9 commits into
Conversation
…ice 5)
Slice 5 / Phase 1 of the marker/ledger-via-typed-query-AST project. The
first SQLite slice in the planner-adoption arc — proves the planner-adoption
pattern works for SQLite by mirroring slice 4 (TML-2754, Postgres).
- Spec: SQLite CreateTableCall.toOp() builds the slice-1 CreateTable DDL-AST
node via the contract-free createTable(...) constructor and lowers through
SqlControlAdapter.lower(), replacing renderCreateTableSql string
concatenation on the planner's live path. Substrate from slice 4 (the
constraint node, contract-free constructor, SQLite adapter
constraint-rendering with 4 byte-parity tests) is fully in place; this
slice does the SQLite adapter byte-parity reconciliation + planner-side
migration.
- Plan: three INVEST-shippable dispatches. D1 plumbs Lowerer through the
SQLite planner stack (mechanical fan-out, no behaviour change); D2
migrates SqliteCreateTableCall.toOp(lowerer) with a byte-parity test that
drives toOp(lowerer) end-to-end; D3 adds SqliteMigration.createTable({...})
authoring method and drops the free createTable re-export from
@prisma-next/sqlite/migration (mirroring slice 4's PG-facade change).
- Project plan.md: fills in slice 5's Linear reference (was TBD).
- Initial trace events (spec-authored, plan-authored).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…fan-out)
Add `import type { Lowerer }` to op-factory-call.ts, render-ops.ts, planner.ts,
and planner-produced-sqlite-migration.ts. Change the abstract
`SqliteOpFactoryCallNode.toOp(): Op` signature to `toOp(lowerer?: Lowerer): Op`
and update all nine concrete `*Call.toOp` signatures to match (each body gains
an ignored `_lowerer?` param — no body change). Update `renderOps` to accept
and forward `lowerer?: Lowerer` via the same `blindCast` pattern used on the
Postgres path. Add `readonly #lowerer: Lowerer` to `SqliteMigrationPlanner` and
`TypeScriptRenderableSqliteMigration`, threading the value from
`createSqliteMigrationPlanner(lowerer: Lowerer)` through both
`TypeScriptRenderableSqliteMigration` constructor call sites. Update both
`createPlanner(adapter)` sites in `control-target.ts` to pass `adapter`. Update
all ten test call sites of `createSqliteMigrationPlanner()` — target-sqlite tests
use an inline `stubLowerer: Lowerer`; adapter-sqlite tests use `createSqliteAdapter()`
or `new SqliteControlAdapter()` (already in scope). Zero bare
`createSqliteMigrationPlanner()` calls remain across the repo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
D2 of slice 5 halted on a falsified-assumption stop-condition: the spec assumed the SQLite adapter substrate was byte-parity-ready, but grounding showed three gaps — the SQLite adapter renderer doesn't quote identifiers (the planner's `renderCreateTableSql` does, so adapter-output and planner-output cannot be byte-identical), the adapter renderer's CREATE TABLE indentation differs from the planner's (4-space vs 2-space), and `SqliteCreateTableCall` holds pre-rendered `defaultSql` strings rather than structured `DdlColumn[]` the way PG's `PostgresCreateTableCall` does after slice 4. Operator decision: expand slice 5 to fix the SQLite adapter renderer + replicate slice 4's `*Call holds DdlColumn[]` refactor on the SQLite side. Plan goes from 3 dispatches to 4: D1 (lowerer plumbing — done in `de813cb18`) + D2 (substrate fix: renderer convention) + D3 (consumer refactor + byte-parity proof) + D4 (authoring API, unchanged from original D3). Captures the in-flight D1 dispatch brief at `slices/sqlite-create-table-adoption/dispatches/01-lowerer-plumbing.md` and a draft D2 brief at `slices/sqlite-create-table-adoption/dispatches/02-create-table-lower-migration.md` (written before the stop-condition surfaced; D2 will be re-briefed after this commit lands and the spec amendment takes effect). Trace events appended for `spec-amended` / `plan-amended` / `falsified-assumption`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…able-adoption Signed-off-by: Will Madden <madden@prisma.io>
The SQLite adapter DDL renderer (`packages/3-targets/6-adapters/sqlite/src/core/ddl-renderer.ts`) was emitting unquoted identifiers and using 4-space/2-space indentation that diverged from both the Postgres adapter renderer and the SQLite planner's `renderCreateTableSql`. This change adds `quoteIdentifier` (from `@prisma-next/target-sqlite/sql-utils`) and `quoteQualifiedIdentifier` to the SQLite renderer, applies them to every identifier reference (table name, column names, constraint names, FK column lists, FK ref-table), and switches indentation to the 2-space/0-space-close form used by the Postgres renderer. Expected-SQL literals in `ddl-create-table-lowering.test.ts` and `ddl-table-constraints-lowering.test.ts` are updated to match; a new test in the constraints file pins mixed-case column names and SQL reserved-word table/constraint names to demonstrate the behaviour the bare-string form could not provide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
… (byte-parity)
Mirror slice 4's PG refactor onto SQLite.
`SqliteCreateTableCall` field shape changes from `spec: SqliteTableSpec` (a
flat struct of pre-rendered SQL fragments) to `columns: readonly DdlColumn[]
+ constraints?: readonly DdlTableConstraint[]` (structured AST nodes).
Constructor takes the structured shape directly. `toOp(lowerer)` builds a
typed DDL node via the contract-free `createTable({...})` constructor and
lowers it through `lowerer.lower(...)` — replacing the call to the free
`createTable(tableName, spec)` Op-builder on this path. The free Op-builder
and its `renderCreateTableSql` helper stay in `operations/tables.ts` for
`recreateTable`, which is Phase 2.
The upstream construction site in `issue-planner.ts` now builds the
structured shape: `StorageColumn` → `DdlColumn` instances via a new
`sqliteDefaultToDdlColumnDefault` helper (analogous to PG's
`postgresDefaultToDdlColumnDefault`); table constraints map to
`PrimaryKeyConstraint` / `ForeignKeyConstraint` / `UniqueConstraint`.
`renderTypeScript()` emits `this.createTable({ table, columns: [col(...),
...], constraints: [...] })` — the user-facing authoring form that
`SqliteMigration.createTable` (landing in the next dispatch) will consume.
Byte-parity proof at `adapter-sqlite/test/migrations/create-table-call-byte-parity.test.ts`:
the new test drives `SqliteCreateTableCall.toOp(lowerer)` end-to-end and
asserts byte-identity vs `renderCreateTableSql(tableName, spec)` across
five representative shapes (simple, composite PK, FK with referential
actions, table-level unique, autoincrement-inline-PK).
Roundtrip-test scope:
`render-typescript.roundtrip.test.ts` dropped `CreateTableCall` from its
operation list. The roundtrip evaluates rendered TS by calling `toOp` on
each construction, and CreateTableCall now needs both a Lowerer AND the
`this.createTable` method on the base class to round-trip — the method
doesn't land until the next dispatch. The remaining operations in the
test (AddColumn, CreateIndex, RawSql, …) still round-trip cleanly because
their toOp doesn't depend on either. Restore CreateTableCall coverage
once the migration method is available.
Includes the dispatch briefs for D2 and D3 as project-context artefacts;
the stale D3 brief from before the spec amendment is removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…-export drop
Adds `SqliteMigration.createTable({ table, columns, constraints? })` as a protected
instance method, mirroring `PostgresMigration.createTable`. The method builds a
`CreateTableCall` and lowers it through a `SqlControlAdapter<'sqlite'>` stored in the
constructor (materialized from `stack?.adapter`). A companion error function
`errorSqliteMigrationStackMissing` surfaces the misuse when the adapter is absent.
Drops the free `createTable` re-export from the target migration facade
(`packages/3-targets/3-targets/sqlite/src/exports/migration.ts`) and the extension
facade (`packages/3-extensions/sqlite`). The function stays in `operations/tables.ts`
for internal use by `recreateTable`. Adds `col`, `fn`, `lit`, `primaryKey`, `foreignKey`,
and `unique` from `@prisma-next/sql-relational-core/contract-free` to both facades
so authored migration files can import all column-builder helpers from the single
`@prisma-next/sqlite/migration` entrypoint.
Restores `CreateTableCall` coverage in the SQLite adapter roundtrip test now that
the base class method exists. Updates the byte-parity test to use the DDL renderer
directly as the oracle (instead of the now-private free `createTable` op factory).
Records the breaking API change in the 0.12→0.13 upgrade instructions for both user
and extension-author skill packages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
… byte-parity oracle, autoincrement guard Fix four reviewer findings from the slice-5 DoD pass. The literal-default renderer now emits 0/1 for booleans, a single-quoted ISO string for Date, and a bare integer string for bigint (defensive), matching the pre-slice renderDefaultLiteral output exactly. The byte-parity test is rewritten with a genuine cross-implementation oracle: each case independently provides a SqliteTableSpec for the pre-slice createTable path and DdlColumn[] for the new path, covering all ColumnDefaultLiteralInputValue kinds plus function defaults. sqliteDefaultToDdlColumnDefault now short-circuits autoincrement() to undefined at the helper boundary (mirroring the Postgres analogue) instead of forwarding a FunctionColumnDefault the renderer silently discards. Both the tableToDdlParts and renderColumn autoincrement sites carry cross-linking comments describing the type-smuggling convention, referencing TML-2866 for the structural fix. A tsconfig.test.json with rootDir widened to the monorepo packages root is added so the cross-package direct-source import in the parity test typechecks cleanly without relaxing the production tsconfig's rootDir. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
Trace records D5 (round-2 fixes) dispatch lifecycle: dispatch-start, round-start, brief-issued, round-end (satisfied), dispatch-end. D4 and D5 brief artifacts capture the dispatched scope so the slice's audit trail is complete. Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 11 minutes and 28 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (9)
📒 Files selected for processing (27)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
size-limit report 📦
|
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/extension-supabase
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/emitter
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
At a glance
The SQLite migration planner's
CREATE TABLEno longer concatenates raw SQL — it builds a typed DDL-AST node and lowers it through the adapter at plan time, the same shape Postgres adopted in #751.The user-facing authoring form on the SQLite side becomes
this.createTable({ ... })onSqliteMigration— same shape Postgres already has.What changed
SqliteCreateTableCallrefactored (packages/3-targets/3-targets/sqlite/src/core/migrations/op-factory-call.ts). The class held a flatSqliteTableSpec(stringdefaultSqlfragments); now it holdsreadonly DdlColumn[]+readonly DdlTableConstraint[]?, mirroringPostgresCreateTableCall.toOp(lowerer)builds the contract-freecreateTable({...})node and lowers vialowerer.lower(node, { contract: {} }). The planner's upstream construction site atissue-planner.tsbuilds the structured node list from the same source data, with a newsqliteDefaultToDdlColumnDefaulthelper mirroring the Postgres analogue (autoincrement()short-circuits toundefined; literal defaults wrap inLiteralColumnDefault; expression defaults wrap inFunctionColumnDefault).SqliteMigrationauthoring API. Aprotected createTable({ table, columns, constraints? })method on the base class instantiates the call and lowers via the heldcontrolAdapter. The freecreateTable(tableName, spec)re-export is dropped from@prisma-next/sqlite/migration; the structural constructorscol,lit,fn,primaryKey,foreignKey,uniqueare re-exported instead. The user-facing breaking change is recorded via the upgrade-instructions skill.packages/3-targets/6-adapters/sqlite/src/core/ddl-renderer.ts). The SQLite DDL renderer didn't quote identifiers and used a different indent than the planner's canonical format. Fixed to quote identifiers and use the 2-space indent the planner emits, so the lowered SQL is byte-identical to the pre-slice output.quoteIdentifier/quoteQualifiedIdentifierhelpers added.The substrate-fix decision — adapter renderer convention
The first dispatch attempted the call-class refactor on top of the existing SQLite renderer. The byte-parity test failed immediately: the renderer didn't quote identifiers, used the wrong indent, and pre-rendered literal defaults inline rather than as a structured node. Three options surfaced:
Picked (1) — the planner's format is the canonical one (Postgres lowered to it in slice 4) and the renderer was the outlier. The slice's spec + plan amended to add the substrate fix as dispatch 2, the call-class refactor as dispatch 3, and the authoring API as dispatch 4.
Byte-parity — no behaviour change
A cross-implementation byte-parity test (
packages/3-targets/6-adapters/sqlite/test/migrations/create-table-call-byte-parity.test.ts) drivesSqliteCreateTableCall.toOp(lowerer)end-to-end with a realSqliteControlAdapter()and compares the lowered SQL to the pre-slicerenderCreateTableSqloutput for 12 representative shapes (simple, composite PK, FK with actions, table-level unique, autoincrement, plus 7 literal-default kinds). The oracle importscreateTabledirectly from the internaloperations/tables.tsmodule — the freecreateTableandrenderCreateTableSqlstay on disk becauserecreateTable(Phase 2) still calls them; only the facade re-export was dropped.The renderer's
defaultVisitor.literalwas expanded in D5 to type-branch correctly onboolean(→0/1),Date(→ ISO single-quoted), andbigint(→String(value)) for parity withrenderDefaultLiteral. This expansion is a transitional state that TML-2867 deletes by routing the value through the column's codec — the type-branching becomes dead once the codec produces the canonical wire form.Scope — deliberately SQLite
CreateTableonlyOther SQLite ops (
AddColumn/DropColumn/CreateIndex/DropIndex/DropTable/RecreateTable) stay on the existing string-build path. They're tracked as Phase 2 of the parent project (projects/migrate-marker-ledger-to-typed-query-ast-commands/).recreateTablestill calls the freecreateTable+renderCreateTableSqlfromoperations/tables.ts— preserved deliberately.A handful of inline-autoincrement smuggling sites in the planner (
tableToDdlPartsencodesPRIMARY KEY AUTOINCREMENTintoDdlColumn.type, the renderer substring-detects to special-case it) are cross-linked with comments naming the convention; the structural fix is tracked as TML-2866.Verification
git grep "createTable(this.tableName, this.spec)"→ zero matches. Byte-parity test passes for all 12 shapes.renderTypeScript()roundtrip test restored (executes the rendered TS scaffold throughtsxand assertsops.jsonequalsrenderOps(calls)).target-sqlite112 /adapter-sqlite194 /sqlitefacade 46 pass.pnpm fixtures:checkgreen;pnpm lint:depsgreen.Alternatives considered
codec.encode(value)calls directly. Extracted to TML-2867 because the change is cross-target (PG already shipped with the same type-branching shape) and async-propagation touches the frameworkMigrationPlanWithAuthoringSurfaceinterface — own slice's worth of work that deserves its own framing.projects/.../README.md) lands the planner-adoption pattern across all three targets (PG/SQLite/Mongo) before fanning out to per-target op vocabulary. Slice 5 is the SQLite pioneer.Refs: TML-2859. Related follow-ups: TML-2860 (slice-4 adapter-sqlite test debt), TML-2862 (pre-commit check for transient project refs), TML-2866 (DdlColumn.type smuggling structural fix), TML-2867 (codec-routed DDL default literals across PG + SQLite).