| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
TML-3496: Migrations skip an operation only when its full effect is in place (#30752) ## Linked issue Refs [TML-3496](https://linear.app/prisma-company/issue/TML-3496). Also fixes [TML-2507](https://linear.app/prisma-company/issue/TML-2507), [TML-3516](https://linear.app/prisma-company/issue/TML-3516) and [TML-3441](https://linear.app/prisma-company/issue/TML-3441). ## At a glance A hand-written migration changes a column's default from `'open'` to `'closed'`: ```ts this.setDefault({ schema: 'public', table: 'Item', column: col('state', 'text', { default: lit('closed'), codecRef: { codecId: 'pg/text@1' } }) }), ``` Running the file now writes: ```json { "id": "setDefault.Item.state", "operationClass": "widening", "precheck": [ … ], "execute": [ { "sql": "ALTER TABLE \"public\".\"Item\" ALTER COLUMN \"state\" SET DEFAULT 'closed'" } ], "postcheck": [] } ``` and `prisma db migrate` exits 0 with the column at `'closed'::text`. Before, `operationClass` defaulted to `additive`, and an additive `setDefault` carried the postcheck "the column has a default". The column already had one (`'open'`), so the runner treated the operation as applied, skipped it, and `db migrate` then failed its final schema check with `expected "closed", actual 'open'::text`. ## Decision Every runner (Postgres, SQLite, MongoDB) runs an operation's postchecks first and skips the operation when they all hold. This PR makes every postcheck follow one rule: **before an operation runs, its postchecks may all hold only when the operation's full effect is already in place.** ADR 038 is rewritten to say this and to describe what the runners actually do. 1. **`setDefault` carries no postcheck.** Checking the value would need the text Postgres stores for the default, predicted at plan time. Re-running `SET DEFAULT` is harmless, and the final schema check compares the value. 2. **`this.setDefault` has no `operationClass` option.** It takes the class from the migration's start contract, after renames earlier in the same migration: `widening` when the column had a default, `additive` otherwise. The planner already chose the class this way. 3. **Every postcheck in the three targets was audited against the rule, and each one that broke it now checks the object's structure from the catalog.** This covers name-only checks on indexes, constraints, foreign keys and policies, existence-only checks on tables and columns, rename checks that never checked the old name was gone, and a SQLite rebuild check that missed foreign key actions. Postchecks never compare text the database reprints (expressions, predicates, check and policy bodies, defaults). 4. **Three defects the audit found are fixed here**, because users hit them on the normal path: - SQLite: a model with `Int @id @default(autoincrement())` failed `db init` and `db migrate`, because introspection never reported the default. - SQLite: changing only a foreign key's `onDelete` planned a rebuild that the runner skipped. - Postgres: widening an `autoincrement()` column from `int4` to `int8` left its sequence `AS integer`, so inserts still stopped at 2147483647. ## How it fits together 1. **The rule and the runner order** are in [ADR 038](docs/architecture%20docs/adrs/ADR%20038%20-%20Operation%20idempotency%20classification%20&%20enforcement.md). It replaces the old text, which said prechecks run first. It also lists every place a postcheck is weaker or stricter than the full effect, and what catches a difference there. 2. **Postgres** postchecks are typed builders in `contract-free/checks.ts`: relation kind, table, uniqueness, access method, ordered columns, foreign key target and actions, policy command, permissive flag and roles. Renames also require the old name to be gone. Sequences are found when the operation runs, with `pg_get_serial_sequence`, so renamed tables and identity columns work. 3. **SQLite** rebuilds compare the table's stored `CREATE TABLE` text with the exact statement the rebuild writes. SQLite stores that text as written, so this check holds only after the rebuild. A new precheck refuses to rebuild a table whose columns are not exactly the ones the rebuild copies, so a rebuild can never drop a column it does not know about. When one migration both rebuilds a table and adds a column to it, the planner lets the rebuild create the column instead of planning a separate `addColumn` first. 4. **MongoDB** `createIndex` compares every option that tells two indexes apart. That list now lives once in `mongo-schema-ir` and is shared by the planner, the verifier and the postcheck. Compile-time checks make sure a new option cannot be missed. `collMod` compares the whole validator. 5. **When a precheck fails**, each runner reports the check in a typed `failedCheck` field on `MigrationRunnerFailure`. The CLI then says which check failed and what it means, for example "the database already has index "Item_state_idx"". It then points to `prisma db sign` when the database has no marker, or to `prisma db verify` when it does. ## Behavior changes & evidence - **A hand-written `setDefault` that changes a default is applied.** [columns.ts](packages/3-targets/3-targets/postgres/src/core/migrations/operations/columns.ts), [postgres-migration.ts](packages/3-targets/3-targets/postgres/src/core/migrations/postgres-migration.ts). Evidence: [runner.set-default.integration.test.ts](packages/3-targets/6-adapters/postgres/test/migrations/runner.set-default.integration.test.ts), [postgres-migration.set-default-class.test.ts](packages/3-targets/3-targets/postgres/test/postgres-migration.set-default-class.test.ts). - **A Postgres create or rename whose name is already used with another definition is no longer skipped.** It now fails at its precheck, naming the object. Evidence: [postchecks-full-definition.integration.test.ts](packages/3-targets/6-adapters/postgres/test/migrations/postchecks-full-definition.integration.test.ts) and its `.shapes` companion, which covers mixed-case names, other schemas, composite keys, enums, arrays and raw types. - **Widening an `autoincrement()` column widens its sequence**, and narrowing refuses when the sequence is already past the new maximum. Evidence: the alter-column-type sequence tests in the Postgres adapter. - **`this.alterColumnType` with `using` is refused when the column already has the target type** (`MIGRATION.ALTER_COLUMN_TYPE_USING_WITHOUT_TYPE_CHANGE`). The conversion could never be told apart from no conversion. Write a `dataTransform` instead. Evidence: [postgres-migration.alter-column-type.test.ts](packages/3-targets/3-targets/postgres/test/postgres-migration.alter-column-type.test.ts). - **SQLite `autoincrement()` ids pass `db init`.** [control-adapter.ts](packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts). Evidence: [autoincrement.sqlite.e2e.test.ts](test/integration/test/cli-journeys/autoincrement.sqlite.e2e.test.ts). - **A SQLite foreign key action change is applied, and a rebuild never drops an unknown column.** Evidence: `recreate-table.postcheck-skip.integration.test.ts`, `recreate-table.adopted-database.integration.test.ts`, `recreate-table.same-migration.integration.test.ts`. - **A failed precheck names the check and what it means**, with advice that can work: `db sign` without a marker, `db verify` with one. Evidence: the CLI runner-failure tests, and [precheck-descriptions.test.ts](test/integration/test/migration-checks/precheck-descriptions.test.ts), which reads every real precheck description from the three targets. - **A MongoDB index that differs only in options is no longer skipped.** Evidence: `runner-postcheck-full-definition.test.ts` (target-mongo), and the type tests for the option list. ## Reviewer notes - **Replaying migrations on a database with no marker that is already ahead of a migration now stops at that migration's precheck.** For example, migration A adds `x int4` and B changes it to `int8`, and the database already has `int8`. On main, A was skipped. The documented way to adopt an existing database is `db sign`, and the error now says so. - **Stricter postchecks are a risk in the other direction.** A postcheck that fails after a correct execute would break every migration that uses that operation. The "both ways" integration tests run each operation on a clean database and check its postchecks then hold, across the input shapes listed above. - **ADR 038 dropped sections describing an `idempotency` field and per-operation ledger records that were never built.** - **Upgrade entries:** an `ops.json` written before this change and not yet applied keeps the old postchecks. Each target's entry tells the reader to run `migration.ts` again, and detection tests prove each pattern matches only old output. An applied migration that widened an autoincrement column needs a one-time `ALTER SEQUENCE`, which the entry gives. - **Largest diffs:** the Postgres `checks.ts` builders and the test files. The example migrations (`prisma-8-demo`, `prisma-8-postgis-demo`, `mongo-demo`, `retail-store`) were regenerated by `pnpm fixtures:check`. ## Testing performed - Each changed package: typecheck, lint, `pnpm test --coverage` (target-postgres, adapter-postgres, target-sqlite, adapter-sqlite, target-mongo, adapter-mongo, mongo-schema-ir, the Mongo family package, cli). - `pnpm test:packages`: green apart from three tarball-install tests that a registry trust check refused, one CLI telemetry timeout and one language-server error. Each passes alone and none is touched here. - Touched integration files only, including `defaults-on-existing-tables`, the planner golden, the SQLite CLI journeys, `migration-ts-column-defaults.e2e`, and the upgrade-instruction detection tests. - `pnpm fixtures:check`, `pnpm check:upgrade-coverage`, `pnpm lint:throws`, `pnpm lint:deps`, `pnpm lint:skills`. ## Manual QA Run against the CLI and packages built from this branch, with Postgres 15 and SQLite. Every case passed. 1. **Planned default change.** Model `Item { id String @id state String @default("open") }`, then plan and migrate. Change the default to `"closed"`, then plan and migrate. Expected: `migration.ts` has no `operationClass`, `ops.json` has `"widening"` and `"postcheck": []`, exit 0, and the default is `'closed'::text`. 2. **Hand-written default change.** Same as 1, but write `this.setDefault(...)` by hand in a `migration new` scaffold and run the file. Expected: the same result. 3. **Index name already taken.** Create `CREATE INDEX "Item_state_idx" ON "Item" ("label")` by hand. Then migrate a contract with `@@index([state], map: "Item_state_idx")`. Expected: exit 2 at the precheck "ensure index "Item_state_idx" does not exist", with nothing applied. With main's postcheck the index was skipped and the run failed later with "Database schema does not satisfy contract". 4. **SQLite.** `User`/`Post` with `Int @id @default(autoincrement())` and `onDelete: Cascade`, applied with `db init`. Then change to `SetNull` and migrate. Expected: both exit 0, and `PRAGMA foreign_key_list("Post")` shows `SET NULL`. 5. **Postgres widen after rename.** Model `Order { id Int @id @default(autoincrement()) }`. Rename the table with `renameTable`, then change `id` to `BigInt` and migrate. Expected: exit 0, and the owned sequence `Order_id_seq` is `bigint`. ## Skill update `skills/prisma-8/references/migrations.md`: - `setDefault` takes no `operationClass` and has no postcheck. - `fn('autoincrement()')` reuses an owned sequence. - `alterColumnType` changes the owned sequence and refuses `using` without a type change. - `rawSql` states the postcheck rule. - A new pitfall: adopt a database that is ahead of the migrations with `db sign`. ## Follow-ups - [TML-3609](https://linear.app/prisma-company/issue/TML-3609): a planned `migration.ts` for the unbound namespace leaves out `schema`. - [TML-3610](https://linear.app/prisma-company/issue/TML-3610): MongoDB `createCollection` cannot be re-run after a partial failure. - [TML-3611](https://linear.app/prisma-company/issue/TML-3611): schema verification does not compare sequence width. - [TML-3612](https://linear.app/prisma-company/issue/TML-3612): a MongoDB collection's default collation looks like drift. - [TML-3613](https://linear.app/prisma-company/issue/TML-3613): check steps record what they expect, so the CLI need not read check descriptions. ## Alternatives considered - **A `setDefault` postcheck that compares the value.** It needs Postgres's stored text for the default predicted at plan time, which differs by type and expression. Rejected as fragile; the final schema check already compares the value. - **Removing name-only postchecks instead of strengthening them.** Without a postcheck, the "does not exist" precheck fails on any database that already has the object. That turns every replay into a failure. - **Keeping `operationClass` as an option on `this.setDefault`.** Leaving it out changed what ran, and nothing told the author so. The start contract already holds the answer. - **Replacing postchecks with "introspect the object and run the verifier's comparison".** That would remove the remaining copies of comparison rules, but it is a runner redesign. Not done here. - **Checking sequence width in schema verification.** This needs sequences in the schema IR. Filed as TML-3611. ## Checklist - [x] All commits are signed off (`git commit -s`) per the [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). - [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is scoped to one logical concern. - [x] Tests are updated. - [x] The PR title is in `TML-NNNN: <sentence-case title>` form. - [x] The **Skill update** section above is filled in. Agent: earendil-64 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Migration runs now skip operations when checks confirm their full intended effects are already present, then verify the final schema. * PostgreSQL autoincrement sequences adjust with column type changes, with checks to prevent unsafe narrowing. * Migration failures can identify the failed check and provide more specific recovery guidance. * **Bug Fixes** * Migration checks now compare database object definitions—not just names—across PostgreSQL, SQLite, and MongoDB, helping detect conflicting or incomplete changes. * **Documentation** * Added upgrade guidance for affected migrations and updated migration behavior references. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 1 天前 | |
| 2 天前 | ||
TML-2527: remove in-repo CipherStash extension (#650) ## At a glance This is a **removal** PR. `@prisma-next/extension-cipherstash` was an in-repo incubation of CipherStash's encrypted-field support. That integration now lives in CipherStash's own repository (published as the partner package `@cipherstash/prisma-next`), so the in-repo copy is redundant. ``` packages/3-extensions/cipherstash/ ─┐ examples/cipherstash-integration/ ├─ deleted (now external) test/integration/.../authoring/parity/cipherstash-* ─┘ projects/cipherstash-integration/ ─ closed ``` The bulk of the diff is those deletions. The parts worth a reviewer's eyes are the handful of files that *referenced* cipherstash and now point at a live in-repo extension instead. ## Decision Remove the in-repo CipherStash extension, its example app, and its authoring-parity fixtures, and close the on-disk `projects/cipherstash-integration/` planning project. Where docs and tests used cipherstash as their **example** extension, repoint them to `pgvector` / `paradedb`, which remain in-repo. The decision to wind the in-repo work down is already reflected in Linear — the substantive tickets were cancelled on 2026-05-27. ## Why CipherStash was always an exemplar: a real third-party extension we incubated in-repo to prove the extension surface. Now that it ships from CipherStash's own repository, keeping a second copy here means dual maintenance and a misleading "first-party" signal for an extension that is, in fact, external. Deleting it leaves `pgvector` and `paradedb` — both genuinely in-repo — as the example extensions the docs and tests lean on. ## What's adjusted (not just deleted) - **`test/integration/package.json`** — dropped the `@prisma-next/extension-cipherstash` dependency; lockfile regenerated via `pnpm install`. - **`test/integration/test/cli.migration-plan-ref-aware.e2e.test.ts`** — migrated from the cipherstash control pack to pgvector; still exercises extension-space seeding. - **`scripts/check-upgrade-coverage.test.mjs`** — two fixture paths repointed to `pgvector`. - **`packages/3-targets/3-targets/postgres/test/snapshot-read-shapes.test.ts`** — removed now-dead cipherstash path-exclusion filters. - **`packages/3-extensions/pgvector/src/contract.ts`** — dropped a comment referencing a deleted cipherstash file. - **Durable docs + skills** — repointed to `pgvector` / `paradedb` as the live extension example (subsystems 6 & 7, contract-spaces-overhead, the migration domain README, and several skills). ## What's deliberately kept - **ADRs 207 / 212 / 213 / 214 / 215** — immutable historical records; cipherstash stays as the design exemplar that motivated them. - **Root `README.md`'s `@cipherstash/prisma-next` link** — the extension lives there now. - **`'cipherstash'` sample space-id string literals** in framework/migration tests — they're sample names, not package references. - **`docs/reference/framework-gaps.md`** — references only the external codebase, not deleted paths. - Historical cipherstash mentions inside other transient `projects/*` dirs. ## Verification `pnpm build` (65/65) · `pnpm lint:deps` (0 violations) — green. `pnpm test:packages` / `pnpm test:integration` green except pre-existing local-infra flakiness (missing CLI binary + DB connection under parallel load) unrelated to this deletion; the modified e2e passes in isolation. CI is the gate. ## Alternatives considered - **Keep the in-repo extension as a maintained exemplar.** Rejected: it now lives in CipherStash's repo, so this would be dual maintenance and would misrepresent an external extension as first-party. - **Delete only the package, keep the example app and fixtures.** Rejected: they depend on the package; orphaning them leaves dead, non-building code. - **Leave cipherstash as the example extension in docs.** Rejected: the docs should point at extensions that actually live here — `pgvector` / `paradedb` — so a reader can follow the example to real in-repo code. Closes TML-2527. Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Will Madden <madden@prisma.io> | 4 个月前 | |
feat: an application depends on one Prisma package (ADR 242) (#29864) ## What changes for someone using Prisma Today, an application that talks to Postgres installs a long list of our packages: ```jsonc { "dependencies": { "@prisma-next/postgres": "...", "@prisma-next/sql-runtime": "...", "@prisma-next/sql-orm-client": "...", "@prisma-next/target-postgres": "...", "@prisma-next/adapter-postgres": "...", "@prisma-next/sql-contract": "..." // ...a dozen more } } ``` After this PR, it installs one: ```jsonc { "dependencies": { "@prisma/orm-postgres": "0.16.0" } } ``` Everything else arrives as that package's own dependencies. Three of our example apps are converted in this PR to prove it — one per database — and each has exactly one Prisma package in its `dependencies`. This implements [ADR 242](https://github.com/prisma/prisma/pull/29852), which is already merged. ## What gets published 17 packages, all under the `@prisma` scope: - **3 database packages** — `@prisma/orm-postgres`, `orm-sqlite`, `orm-mongo`. An application installs exactly one. We call these *facades*: each is a small package that wires its database together and re-exports everything an application needs. - **6 extension packs** — PostGIS, pgvector, ParadeDB, Supabase, arktype-json, middleware-cache. Optional, installed alongside a database package. - **7 platform packages** — the framework, the toolchain, one per database family, one per database target. Applications never install these directly; they arrive as dependencies. Extension authors do install them. - **the `prisma` command**, as a bin-only package. Every other workspace package — around 50 of them — stops being published. They still exist in the repo as the unit we organise code in; they just stop having a life on the registry. **This PR does not make that switch yet.** It builds and proves the new surface while leaving today's publish list exactly as it is. Flipping it is a separate change. ## The problem this design has to avoid A published package can't depend on packages that won't exist on the registry. So each published package *contains a compiled copy* of the internal packages it covers. That creates a trap. If one application ends up with the same code twice — once inside a published package, once as its own package — then classes, registries, and anything compared by reference exist twice too. An `instanceof` check quietly returns false. Nothing crashes, nothing fails to compile, and both copies behave identically in isolation. You find out much later, somewhere unrelated. So the rule the whole design follows is: **every piece of internal code is published from exactly one package.** Concretely, that means: - Each published package is built in one pass, so code shared between its own entry points exists once. Verified from the build's source maps: no module appears in more than one chunk, in any published package. - When one published package needs code from another, it imports it as a real dependency rather than compiling in a second copy. - A facade re-exports from the platform packages; it never carries its own copy. `@prisma/orm-postgres/orm-client` and `@prisma/orm-family-sql/orm-client` are two names for the same object, and there's a test that asserts exactly that from installed tarballs. - One table in `packages/0-shared/publish-surface` maps every internal package to where it's published. The build, the code generator, and the lint checks all read it, so there's one answer to "where does this live" rather than three that can drift. ## Generated code follows the application Prisma writes imports into your project — contract types and migration files. Those imports have to name packages your project actually depends on, or they won't resolve. So the generator now reads the `package.json` next to the config it's generating for. A project that depends on `@prisma/orm-postgres` gets imports from that package. A project on today's names keeps today's names. Nothing to configure, because the manifest already says which it is. Contract hashes are unaffected, and that isn't an assumption — hashes are computed from a structure that import text never enters, and there's a test asserting the hash is identical across naming schemes *while* the emitted imports demonstrably differ. ## What stops the trap coming back Two checks, because the failure is silent and won't show up in a test suite: - Every example app and test project must use one naming scheme, not a mix. `lint-single-import-root` scans them and fails the build if any project imports from both, since that's the situation that loads code twice. - `lint-consumer-internal-imports` counts how many internal-package imports remain in those projects and compares against a committed number. It fails if the number goes up (someone added one) and also if it goes down without the number being updated (so improvements get locked in). Target is zero. The build itself also refuses to proceed if the published-package map would put one module in two places, or if a published package's `package.json` no longer matches what its code actually needs. ## Reading this PR It's large — 257 files — because it's a migration. The commits are grouped and meant to be read in order: 1. **Platform packages** — the build mechanism, and the seven platform packages it produces. 2. **Database packages, extension packs, the `prisma` command** — completes the set of 17. 3. **Generated imports become configurable** — one place decides which names get written, with today's names still the default. 4. **Database-family symmetry, publishing the map, the identity checks.** 5. **One package per application** — the three converted examples, the re-exports they proved necessary, and the counting check. 6. **Migration files follow the project too.** One thing worth knowing while reading: re-exporting a package republishes all of its sub-paths, not just the one that was needed. This PR adds 115 published sub-paths across the three database packages. Two candidates were dropped for exactly that reason — see below. ## Alternatives considered **Let an application install platform packages alongside its facade.** Nothing would need re-exporting and the facades would stay thinner. Rejected: an application would again juggle several Prisma dependencies whose correct combination it maintains by hand, and getting it wrong — upgrading one and not the other — produces the silent two-copies failure above. Re-exporting costs a generated line and nothing at runtime. **Re-export everything an application might plausibly want.** Rejected in review: because re-exporting brings a package's entire sub-path surface, generosity is expensive and hard to undo. Migration tooling (54 sub-paths) was dropped because its only users are extension packs, which install platform packages anyway; the SQL driver re-export was dropped because nothing imported it at all. What remains is what a converted example actually needed. **Flip the publish list in this same PR.** Rejected: it would mix "does the new surface work" with "is it safe to stop publishing 50 packages" in one review. The switch is mechanical once this lands, and gets its own change. ## Verification `build`, `typecheck` (156 tasks), `test:packages` (1077 files / 14087 tests), `test:e2e`, `lint`, `lint:deps`, `lint:docs`, `lint:manifests`, `check:publish-deps`, `check:clean-tree`, `lint:casts` and `lint:throws` (no new instances), `test:scripts`, coverage, the tarball-install suites, and regenerating every committed artifact leaves the tree unchanged. Known-unstable and unrelated to this change: the `relation-mode-gh-*` port suites (TML-3140), and several test timeouts that are too tight under load. ## Follow-ups TML-3124 switch the publish list · TML-3127 build cache can validate a stale published package on CI · TML-3140 unstable port suites · TML-3141 a test-helper sub-path reaches a package that is never published. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added consolidated public ORM packages for PostgreSQL, MongoDB, SQLite, framework tooling, database targets, and extensions. - Generated contracts, migrations, and scaffolds now adapt imports to the consuming project’s package surface. - Added facade-provided `prisma-next` CLI access and consolidated migration entrypoints. - **Documentation** - Updated installation, package naming, public entrypoint, and migration scaffolding guidance. - **Tests** - Added coverage for package installation, exports, CLI behavior, module identity, and import compatibility. - **Chores** - Added checks preventing incompatible internal and public package imports. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 2 个月前 | |
TML-3496: Migrations skip an operation only when its full effect is in place (#30752) ## Linked issue Refs [TML-3496](https://linear.app/prisma-company/issue/TML-3496). Also fixes [TML-2507](https://linear.app/prisma-company/issue/TML-2507), [TML-3516](https://linear.app/prisma-company/issue/TML-3516) and [TML-3441](https://linear.app/prisma-company/issue/TML-3441). ## At a glance A hand-written migration changes a column's default from `'open'` to `'closed'`: ```ts this.setDefault({ schema: 'public', table: 'Item', column: col('state', 'text', { default: lit('closed'), codecRef: { codecId: 'pg/text@1' } }) }), ``` Running the file now writes: ```json { "id": "setDefault.Item.state", "operationClass": "widening", "precheck": [ … ], "execute": [ { "sql": "ALTER TABLE \"public\".\"Item\" ALTER COLUMN \"state\" SET DEFAULT 'closed'" } ], "postcheck": [] } ``` and `prisma db migrate` exits 0 with the column at `'closed'::text`. Before, `operationClass` defaulted to `additive`, and an additive `setDefault` carried the postcheck "the column has a default". The column already had one (`'open'`), so the runner treated the operation as applied, skipped it, and `db migrate` then failed its final schema check with `expected "closed", actual 'open'::text`. ## Decision Every runner (Postgres, SQLite, MongoDB) runs an operation's postchecks first and skips the operation when they all hold. This PR makes every postcheck follow one rule: **before an operation runs, its postchecks may all hold only when the operation's full effect is already in place.** ADR 038 is rewritten to say this and to describe what the runners actually do. 1. **`setDefault` carries no postcheck.** Checking the value would need the text Postgres stores for the default, predicted at plan time. Re-running `SET DEFAULT` is harmless, and the final schema check compares the value. 2. **`this.setDefault` has no `operationClass` option.** It takes the class from the migration's start contract, after renames earlier in the same migration: `widening` when the column had a default, `additive` otherwise. The planner already chose the class this way. 3. **Every postcheck in the three targets was audited against the rule, and each one that broke it now checks the object's structure from the catalog.** This covers name-only checks on indexes, constraints, foreign keys and policies, existence-only checks on tables and columns, rename checks that never checked the old name was gone, and a SQLite rebuild check that missed foreign key actions. Postchecks never compare text the database reprints (expressions, predicates, check and policy bodies, defaults). 4. **Three defects the audit found are fixed here**, because users hit them on the normal path: - SQLite: a model with `Int @id @default(autoincrement())` failed `db init` and `db migrate`, because introspection never reported the default. - SQLite: changing only a foreign key's `onDelete` planned a rebuild that the runner skipped. - Postgres: widening an `autoincrement()` column from `int4` to `int8` left its sequence `AS integer`, so inserts still stopped at 2147483647. ## How it fits together 1. **The rule and the runner order** are in [ADR 038](docs/architecture%20docs/adrs/ADR%20038%20-%20Operation%20idempotency%20classification%20&%20enforcement.md). It replaces the old text, which said prechecks run first. It also lists every place a postcheck is weaker or stricter than the full effect, and what catches a difference there. 2. **Postgres** postchecks are typed builders in `contract-free/checks.ts`: relation kind, table, uniqueness, access method, ordered columns, foreign key target and actions, policy command, permissive flag and roles. Renames also require the old name to be gone. Sequences are found when the operation runs, with `pg_get_serial_sequence`, so renamed tables and identity columns work. 3. **SQLite** rebuilds compare the table's stored `CREATE TABLE` text with the exact statement the rebuild writes. SQLite stores that text as written, so this check holds only after the rebuild. A new precheck refuses to rebuild a table whose columns are not exactly the ones the rebuild copies, so a rebuild can never drop a column it does not know about. When one migration both rebuilds a table and adds a column to it, the planner lets the rebuild create the column instead of planning a separate `addColumn` first. 4. **MongoDB** `createIndex` compares every option that tells two indexes apart. That list now lives once in `mongo-schema-ir` and is shared by the planner, the verifier and the postcheck. Compile-time checks make sure a new option cannot be missed. `collMod` compares the whole validator. 5. **When a precheck fails**, each runner reports the check in a typed `failedCheck` field on `MigrationRunnerFailure`. The CLI then says which check failed and what it means, for example "the database already has index "Item_state_idx"". It then points to `prisma db sign` when the database has no marker, or to `prisma db verify` when it does. ## Behavior changes & evidence - **A hand-written `setDefault` that changes a default is applied.** [columns.ts](packages/3-targets/3-targets/postgres/src/core/migrations/operations/columns.ts), [postgres-migration.ts](packages/3-targets/3-targets/postgres/src/core/migrations/postgres-migration.ts). Evidence: [runner.set-default.integration.test.ts](packages/3-targets/6-adapters/postgres/test/migrations/runner.set-default.integration.test.ts), [postgres-migration.set-default-class.test.ts](packages/3-targets/3-targets/postgres/test/postgres-migration.set-default-class.test.ts). - **A Postgres create or rename whose name is already used with another definition is no longer skipped.** It now fails at its precheck, naming the object. Evidence: [postchecks-full-definition.integration.test.ts](packages/3-targets/6-adapters/postgres/test/migrations/postchecks-full-definition.integration.test.ts) and its `.shapes` companion, which covers mixed-case names, other schemas, composite keys, enums, arrays and raw types. - **Widening an `autoincrement()` column widens its sequence**, and narrowing refuses when the sequence is already past the new maximum. Evidence: the alter-column-type sequence tests in the Postgres adapter. - **`this.alterColumnType` with `using` is refused when the column already has the target type** (`MIGRATION.ALTER_COLUMN_TYPE_USING_WITHOUT_TYPE_CHANGE`). The conversion could never be told apart from no conversion. Write a `dataTransform` instead. Evidence: [postgres-migration.alter-column-type.test.ts](packages/3-targets/3-targets/postgres/test/postgres-migration.alter-column-type.test.ts). - **SQLite `autoincrement()` ids pass `db init`.** [control-adapter.ts](packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts). Evidence: [autoincrement.sqlite.e2e.test.ts](test/integration/test/cli-journeys/autoincrement.sqlite.e2e.test.ts). - **A SQLite foreign key action change is applied, and a rebuild never drops an unknown column.** Evidence: `recreate-table.postcheck-skip.integration.test.ts`, `recreate-table.adopted-database.integration.test.ts`, `recreate-table.same-migration.integration.test.ts`. - **A failed precheck names the check and what it means**, with advice that can work: `db sign` without a marker, `db verify` with one. Evidence: the CLI runner-failure tests, and [precheck-descriptions.test.ts](test/integration/test/migration-checks/precheck-descriptions.test.ts), which reads every real precheck description from the three targets. - **A MongoDB index that differs only in options is no longer skipped.** Evidence: `runner-postcheck-full-definition.test.ts` (target-mongo), and the type tests for the option list. ## Reviewer notes - **Replaying migrations on a database with no marker that is already ahead of a migration now stops at that migration's precheck.** For example, migration A adds `x int4` and B changes it to `int8`, and the database already has `int8`. On main, A was skipped. The documented way to adopt an existing database is `db sign`, and the error now says so. - **Stricter postchecks are a risk in the other direction.** A postcheck that fails after a correct execute would break every migration that uses that operation. The "both ways" integration tests run each operation on a clean database and check its postchecks then hold, across the input shapes listed above. - **ADR 038 dropped sections describing an `idempotency` field and per-operation ledger records that were never built.** - **Upgrade entries:** an `ops.json` written before this change and not yet applied keeps the old postchecks. Each target's entry tells the reader to run `migration.ts` again, and detection tests prove each pattern matches only old output. An applied migration that widened an autoincrement column needs a one-time `ALTER SEQUENCE`, which the entry gives. - **Largest diffs:** the Postgres `checks.ts` builders and the test files. The example migrations (`prisma-8-demo`, `prisma-8-postgis-demo`, `mongo-demo`, `retail-store`) were regenerated by `pnpm fixtures:check`. ## Testing performed - Each changed package: typecheck, lint, `pnpm test --coverage` (target-postgres, adapter-postgres, target-sqlite, adapter-sqlite, target-mongo, adapter-mongo, mongo-schema-ir, the Mongo family package, cli). - `pnpm test:packages`: green apart from three tarball-install tests that a registry trust check refused, one CLI telemetry timeout and one language-server error. Each passes alone and none is touched here. - Touched integration files only, including `defaults-on-existing-tables`, the planner golden, the SQLite CLI journeys, `migration-ts-column-defaults.e2e`, and the upgrade-instruction detection tests. - `pnpm fixtures:check`, `pnpm check:upgrade-coverage`, `pnpm lint:throws`, `pnpm lint:deps`, `pnpm lint:skills`. ## Manual QA Run against the CLI and packages built from this branch, with Postgres 15 and SQLite. Every case passed. 1. **Planned default change.** Model `Item { id String @id state String @default("open") }`, then plan and migrate. Change the default to `"closed"`, then plan and migrate. Expected: `migration.ts` has no `operationClass`, `ops.json` has `"widening"` and `"postcheck": []`, exit 0, and the default is `'closed'::text`. 2. **Hand-written default change.** Same as 1, but write `this.setDefault(...)` by hand in a `migration new` scaffold and run the file. Expected: the same result. 3. **Index name already taken.** Create `CREATE INDEX "Item_state_idx" ON "Item" ("label")` by hand. Then migrate a contract with `@@index([state], map: "Item_state_idx")`. Expected: exit 2 at the precheck "ensure index "Item_state_idx" does not exist", with nothing applied. With main's postcheck the index was skipped and the run failed later with "Database schema does not satisfy contract". 4. **SQLite.** `User`/`Post` with `Int @id @default(autoincrement())` and `onDelete: Cascade`, applied with `db init`. Then change to `SetNull` and migrate. Expected: both exit 0, and `PRAGMA foreign_key_list("Post")` shows `SET NULL`. 5. **Postgres widen after rename.** Model `Order { id Int @id @default(autoincrement()) }`. Rename the table with `renameTable`, then change `id` to `BigInt` and migrate. Expected: exit 0, and the owned sequence `Order_id_seq` is `bigint`. ## Skill update `skills/prisma-8/references/migrations.md`: - `setDefault` takes no `operationClass` and has no postcheck. - `fn('autoincrement()')` reuses an owned sequence. - `alterColumnType` changes the owned sequence and refuses `using` without a type change. - `rawSql` states the postcheck rule. - A new pitfall: adopt a database that is ahead of the migrations with `db sign`. ## Follow-ups - [TML-3609](https://linear.app/prisma-company/issue/TML-3609): a planned `migration.ts` for the unbound namespace leaves out `schema`. - [TML-3610](https://linear.app/prisma-company/issue/TML-3610): MongoDB `createCollection` cannot be re-run after a partial failure. - [TML-3611](https://linear.app/prisma-company/issue/TML-3611): schema verification does not compare sequence width. - [TML-3612](https://linear.app/prisma-company/issue/TML-3612): a MongoDB collection's default collation looks like drift. - [TML-3613](https://linear.app/prisma-company/issue/TML-3613): check steps record what they expect, so the CLI need not read check descriptions. ## Alternatives considered - **A `setDefault` postcheck that compares the value.** It needs Postgres's stored text for the default predicted at plan time, which differs by type and expression. Rejected as fragile; the final schema check already compares the value. - **Removing name-only postchecks instead of strengthening them.** Without a postcheck, the "does not exist" precheck fails on any database that already has the object. That turns every replay into a failure. - **Keeping `operationClass` as an option on `this.setDefault`.** Leaving it out changed what ran, and nothing told the author so. The start contract already holds the answer. - **Replacing postchecks with "introspect the object and run the verifier's comparison".** That would remove the remaining copies of comparison rules, but it is a runner redesign. Not done here. - **Checking sequence width in schema verification.** This needs sequences in the schema IR. Filed as TML-3611. ## Checklist - [x] All commits are signed off (`git commit -s`) per the [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). - [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is scoped to one logical concern. - [x] Tests are updated. - [x] The PR title is in `TML-NNNN: <sentence-case title>` form. - [x] The **Skill update** section above is filled in. Agent: earendil-64 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Migration runs now skip operations when checks confirm their full intended effects are already present, then verify the final schema. * PostgreSQL autoincrement sequences adjust with column type changes, with checks to prevent unsafe narrowing. * Migration failures can identify the failed check and provide more specific recovery guidance. * **Bug Fixes** * Migration checks now compare database object definitions—not just names—across PostgreSQL, SQLite, and MongoDB, helping detect conflicting or incomplete changes. * **Documentation** * Added upgrade guidance for affected migrations and updated migration behavior references. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 1 天前 | |
TML-3496: Migrations skip an operation only when its full effect is in place (#30752) ## Linked issue Refs [TML-3496](https://linear.app/prisma-company/issue/TML-3496). Also fixes [TML-2507](https://linear.app/prisma-company/issue/TML-2507), [TML-3516](https://linear.app/prisma-company/issue/TML-3516) and [TML-3441](https://linear.app/prisma-company/issue/TML-3441). ## At a glance A hand-written migration changes a column's default from `'open'` to `'closed'`: ```ts this.setDefault({ schema: 'public', table: 'Item', column: col('state', 'text', { default: lit('closed'), codecRef: { codecId: 'pg/text@1' } }) }), ``` Running the file now writes: ```json { "id": "setDefault.Item.state", "operationClass": "widening", "precheck": [ … ], "execute": [ { "sql": "ALTER TABLE \"public\".\"Item\" ALTER COLUMN \"state\" SET DEFAULT 'closed'" } ], "postcheck": [] } ``` and `prisma db migrate` exits 0 with the column at `'closed'::text`. Before, `operationClass` defaulted to `additive`, and an additive `setDefault` carried the postcheck "the column has a default". The column already had one (`'open'`), so the runner treated the operation as applied, skipped it, and `db migrate` then failed its final schema check with `expected "closed", actual 'open'::text`. ## Decision Every runner (Postgres, SQLite, MongoDB) runs an operation's postchecks first and skips the operation when they all hold. This PR makes every postcheck follow one rule: **before an operation runs, its postchecks may all hold only when the operation's full effect is already in place.** ADR 038 is rewritten to say this and to describe what the runners actually do. 1. **`setDefault` carries no postcheck.** Checking the value would need the text Postgres stores for the default, predicted at plan time. Re-running `SET DEFAULT` is harmless, and the final schema check compares the value. 2. **`this.setDefault` has no `operationClass` option.** It takes the class from the migration's start contract, after renames earlier in the same migration: `widening` when the column had a default, `additive` otherwise. The planner already chose the class this way. 3. **Every postcheck in the three targets was audited against the rule, and each one that broke it now checks the object's structure from the catalog.** This covers name-only checks on indexes, constraints, foreign keys and policies, existence-only checks on tables and columns, rename checks that never checked the old name was gone, and a SQLite rebuild check that missed foreign key actions. Postchecks never compare text the database reprints (expressions, predicates, check and policy bodies, defaults). 4. **Three defects the audit found are fixed here**, because users hit them on the normal path: - SQLite: a model with `Int @id @default(autoincrement())` failed `db init` and `db migrate`, because introspection never reported the default. - SQLite: changing only a foreign key's `onDelete` planned a rebuild that the runner skipped. - Postgres: widening an `autoincrement()` column from `int4` to `int8` left its sequence `AS integer`, so inserts still stopped at 2147483647. ## How it fits together 1. **The rule and the runner order** are in [ADR 038](docs/architecture%20docs/adrs/ADR%20038%20-%20Operation%20idempotency%20classification%20&%20enforcement.md). It replaces the old text, which said prechecks run first. It also lists every place a postcheck is weaker or stricter than the full effect, and what catches a difference there. 2. **Postgres** postchecks are typed builders in `contract-free/checks.ts`: relation kind, table, uniqueness, access method, ordered columns, foreign key target and actions, policy command, permissive flag and roles. Renames also require the old name to be gone. Sequences are found when the operation runs, with `pg_get_serial_sequence`, so renamed tables and identity columns work. 3. **SQLite** rebuilds compare the table's stored `CREATE TABLE` text with the exact statement the rebuild writes. SQLite stores that text as written, so this check holds only after the rebuild. A new precheck refuses to rebuild a table whose columns are not exactly the ones the rebuild copies, so a rebuild can never drop a column it does not know about. When one migration both rebuilds a table and adds a column to it, the planner lets the rebuild create the column instead of planning a separate `addColumn` first. 4. **MongoDB** `createIndex` compares every option that tells two indexes apart. That list now lives once in `mongo-schema-ir` and is shared by the planner, the verifier and the postcheck. Compile-time checks make sure a new option cannot be missed. `collMod` compares the whole validator. 5. **When a precheck fails**, each runner reports the check in a typed `failedCheck` field on `MigrationRunnerFailure`. The CLI then says which check failed and what it means, for example "the database already has index "Item_state_idx"". It then points to `prisma db sign` when the database has no marker, or to `prisma db verify` when it does. ## Behavior changes & evidence - **A hand-written `setDefault` that changes a default is applied.** [columns.ts](packages/3-targets/3-targets/postgres/src/core/migrations/operations/columns.ts), [postgres-migration.ts](packages/3-targets/3-targets/postgres/src/core/migrations/postgres-migration.ts). Evidence: [runner.set-default.integration.test.ts](packages/3-targets/6-adapters/postgres/test/migrations/runner.set-default.integration.test.ts), [postgres-migration.set-default-class.test.ts](packages/3-targets/3-targets/postgres/test/postgres-migration.set-default-class.test.ts). - **A Postgres create or rename whose name is already used with another definition is no longer skipped.** It now fails at its precheck, naming the object. Evidence: [postchecks-full-definition.integration.test.ts](packages/3-targets/6-adapters/postgres/test/migrations/postchecks-full-definition.integration.test.ts) and its `.shapes` companion, which covers mixed-case names, other schemas, composite keys, enums, arrays and raw types. - **Widening an `autoincrement()` column widens its sequence**, and narrowing refuses when the sequence is already past the new maximum. Evidence: the alter-column-type sequence tests in the Postgres adapter. - **`this.alterColumnType` with `using` is refused when the column already has the target type** (`MIGRATION.ALTER_COLUMN_TYPE_USING_WITHOUT_TYPE_CHANGE`). The conversion could never be told apart from no conversion. Write a `dataTransform` instead. Evidence: [postgres-migration.alter-column-type.test.ts](packages/3-targets/3-targets/postgres/test/postgres-migration.alter-column-type.test.ts). - **SQLite `autoincrement()` ids pass `db init`.** [control-adapter.ts](packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts). Evidence: [autoincrement.sqlite.e2e.test.ts](test/integration/test/cli-journeys/autoincrement.sqlite.e2e.test.ts). - **A SQLite foreign key action change is applied, and a rebuild never drops an unknown column.** Evidence: `recreate-table.postcheck-skip.integration.test.ts`, `recreate-table.adopted-database.integration.test.ts`, `recreate-table.same-migration.integration.test.ts`. - **A failed precheck names the check and what it means**, with advice that can work: `db sign` without a marker, `db verify` with one. Evidence: the CLI runner-failure tests, and [precheck-descriptions.test.ts](test/integration/test/migration-checks/precheck-descriptions.test.ts), which reads every real precheck description from the three targets. - **A MongoDB index that differs only in options is no longer skipped.** Evidence: `runner-postcheck-full-definition.test.ts` (target-mongo), and the type tests for the option list. ## Reviewer notes - **Replaying migrations on a database with no marker that is already ahead of a migration now stops at that migration's precheck.** For example, migration A adds `x int4` and B changes it to `int8`, and the database already has `int8`. On main, A was skipped. The documented way to adopt an existing database is `db sign`, and the error now says so. - **Stricter postchecks are a risk in the other direction.** A postcheck that fails after a correct execute would break every migration that uses that operation. The "both ways" integration tests run each operation on a clean database and check its postchecks then hold, across the input shapes listed above. - **ADR 038 dropped sections describing an `idempotency` field and per-operation ledger records that were never built.** - **Upgrade entries:** an `ops.json` written before this change and not yet applied keeps the old postchecks. Each target's entry tells the reader to run `migration.ts` again, and detection tests prove each pattern matches only old output. An applied migration that widened an autoincrement column needs a one-time `ALTER SEQUENCE`, which the entry gives. - **Largest diffs:** the Postgres `checks.ts` builders and the test files. The example migrations (`prisma-8-demo`, `prisma-8-postgis-demo`, `mongo-demo`, `retail-store`) were regenerated by `pnpm fixtures:check`. ## Testing performed - Each changed package: typecheck, lint, `pnpm test --coverage` (target-postgres, adapter-postgres, target-sqlite, adapter-sqlite, target-mongo, adapter-mongo, mongo-schema-ir, the Mongo family package, cli). - `pnpm test:packages`: green apart from three tarball-install tests that a registry trust check refused, one CLI telemetry timeout and one language-server error. Each passes alone and none is touched here. - Touched integration files only, including `defaults-on-existing-tables`, the planner golden, the SQLite CLI journeys, `migration-ts-column-defaults.e2e`, and the upgrade-instruction detection tests. - `pnpm fixtures:check`, `pnpm check:upgrade-coverage`, `pnpm lint:throws`, `pnpm lint:deps`, `pnpm lint:skills`. ## Manual QA Run against the CLI and packages built from this branch, with Postgres 15 and SQLite. Every case passed. 1. **Planned default change.** Model `Item { id String @id state String @default("open") }`, then plan and migrate. Change the default to `"closed"`, then plan and migrate. Expected: `migration.ts` has no `operationClass`, `ops.json` has `"widening"` and `"postcheck": []`, exit 0, and the default is `'closed'::text`. 2. **Hand-written default change.** Same as 1, but write `this.setDefault(...)` by hand in a `migration new` scaffold and run the file. Expected: the same result. 3. **Index name already taken.** Create `CREATE INDEX "Item_state_idx" ON "Item" ("label")` by hand. Then migrate a contract with `@@index([state], map: "Item_state_idx")`. Expected: exit 2 at the precheck "ensure index "Item_state_idx" does not exist", with nothing applied. With main's postcheck the index was skipped and the run failed later with "Database schema does not satisfy contract". 4. **SQLite.** `User`/`Post` with `Int @id @default(autoincrement())` and `onDelete: Cascade`, applied with `db init`. Then change to `SetNull` and migrate. Expected: both exit 0, and `PRAGMA foreign_key_list("Post")` shows `SET NULL`. 5. **Postgres widen after rename.** Model `Order { id Int @id @default(autoincrement()) }`. Rename the table with `renameTable`, then change `id` to `BigInt` and migrate. Expected: exit 0, and the owned sequence `Order_id_seq` is `bigint`. ## Skill update `skills/prisma-8/references/migrations.md`: - `setDefault` takes no `operationClass` and has no postcheck. - `fn('autoincrement()')` reuses an owned sequence. - `alterColumnType` changes the owned sequence and refuses `using` without a type change. - `rawSql` states the postcheck rule. - A new pitfall: adopt a database that is ahead of the migrations with `db sign`. ## Follow-ups - [TML-3609](https://linear.app/prisma-company/issue/TML-3609): a planned `migration.ts` for the unbound namespace leaves out `schema`. - [TML-3610](https://linear.app/prisma-company/issue/TML-3610): MongoDB `createCollection` cannot be re-run after a partial failure. - [TML-3611](https://linear.app/prisma-company/issue/TML-3611): schema verification does not compare sequence width. - [TML-3612](https://linear.app/prisma-company/issue/TML-3612): a MongoDB collection's default collation looks like drift. - [TML-3613](https://linear.app/prisma-company/issue/TML-3613): check steps record what they expect, so the CLI need not read check descriptions. ## Alternatives considered - **A `setDefault` postcheck that compares the value.** It needs Postgres's stored text for the default predicted at plan time, which differs by type and expression. Rejected as fragile; the final schema check already compares the value. - **Removing name-only postchecks instead of strengthening them.** Without a postcheck, the "does not exist" precheck fails on any database that already has the object. That turns every replay into a failure. - **Keeping `operationClass` as an option on `this.setDefault`.** Leaving it out changed what ran, and nothing told the author so. The start contract already holds the answer. - **Replacing postchecks with "introspect the object and run the verifier's comparison".** That would remove the remaining copies of comparison rules, but it is a runner redesign. Not done here. - **Checking sequence width in schema verification.** This needs sequences in the schema IR. Filed as TML-3611. ## Checklist - [x] All commits are signed off (`git commit -s`) per the [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). - [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is scoped to one logical concern. - [x] Tests are updated. - [x] The PR title is in `TML-NNNN: <sentence-case title>` form. - [x] The **Skill update** section above is filled in. Agent: earendil-64 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Migration runs now skip operations when checks confirm their full intended effects are already present, then verify the final schema. * PostgreSQL autoincrement sequences adjust with column type changes, with checks to prevent unsafe narrowing. * Migration failures can identify the failed check and provide more specific recovery guidance. * **Bug Fixes** * Migration checks now compare database object definitions—not just names—across PostgreSQL, SQLite, and MongoDB, helping detect conflicting or incomplete changes. * **Documentation** * Added upgrade guidance for affected migrations and updated migration behavior references. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 1 天前 | |
docs: rewrite README for Prisma 8, fix orm init dist-tag, sweep "Prisma Next" prose (#30248) ## Linked issue n/a — no Linear ticket. Follow-up to the README banner swap in #30225. ## At a glance The README's getting-started commands, before and after, checked against [prisma.io/docs/getting-started](https://www.prisma.io/docs/getting-started): ```bash # before npm create prisma@next npx @prisma/cli@next orm init # after npm create prisma npx prisma orm init npx prisma skills sync ``` The `prisma` package has no `next` dist-tag any more (`latest` is `8.0.0-rc.13`, `prev` is `7.10.0`), so the old commands no longer resolve. ## Summary The README still introduced the product as "Prisma Next" in Early Access, pointed at the removed `next` dist-tag, listed extensions by their `@internal/*` workspace names, and linked to `prisma/prisma`. The rest of the repo had about a thousand prose mentions of the working name. This PR fixes all of it and one real bug the sweep turned up. ## Decision Five commits, each reviewable on its own (the fifth only records the sweep against the in-flight upgrade-instruction files for the coverage check): 1. **Rewrite the README against the live docs.** Every instruction in it now matches the getting-started, quickstart, `orm init`, `skills`, and extensions pages in prisma/web. 2. **Fix `orm init` to install `prisma@latest`.** The CLI added `prisma@next` as a dev dependency. That tag no longer exists on npm, so `orm init` fails at the install step for anyone running it today. The engine fallback moves from `@prisma/cli-engine@next` (0.2.3, stale) to `@latest` (0.3.0). 3. **Replace "Prisma Next" with "Prisma 8" in prose repo-wide.** Docs, doc comments, READMEs, package descriptions, skill references, and user-facing strings. 4. **Carry the pnpm trust-policy exemptions into the tarball smoke tests.** The scratch installs those tests run trip a trust-downgrade check on `undici-types@6.21.0` (no provenance, while 6.13.0 and 6.18.2 had it). The repo already exempts it for the workspace install; the test kit now restates `trustPolicy` and `trustPolicyExclude` in the scratch project the way it restates the release-age settings. Reproduced on main with a fresh metadata cache, so this is a pre-existing failure that any run without cached metadata hits. ## Reviewer notes - **Rebased on #30229.** That PR's release-candidate banner and its `scorecard.md` link replace the roadmap reference in the README, and `ROADMAP.md` stays deleted. The prose sweep re-applied cleanly on top of its CONTRIBUTING, SECURITY, and governance edits. - **Dated records keep the old name**, matching the allowances `scripts/lint-legacy-name.mjs` already defines for the `prisma-next` identifier: `CHANGELOG.md`, `docs/releases/`, the ADRs, `projects/`, and `drive/`. Rewriting those would misreport what was true at the time, and a mechanical pass produced sentences like "Prisma Next becomes Prisma 8" turning into "Prisma 8 becomes Prisma 8". - **Identifiers are untouched.** `prisma-next` package names, paths, env vars (`PRISMA_NEXT_*`), `PrismaNext*` types, the `images/prisma-next.png` file, and the `prisma-next.md` primer (the docs still call it that) are all unchanged. Renaming any of those is a behaviour change with an upgrade path, not a docs fix. - **The sweep is mechanical.** The third commit is a `sed` of `Prisma Next` and `Prisma-next` to `Prisma 8` over 345 files. Three sentences that became self-referential (`ROADMAP.md`, `ROADMAP.html`, `scorecard.md`) were rewritten by hand. - **`README.md` supported-databases section** now says PostgreSQL and MongoDB are first-class and SQLite is planned next, which is what [/docs/orm](https://www.prisma.io/docs/orm) says. The previous text referenced work "before the 8.0.0-rc.1 release". - **Discord channel name dropped.** The README linked to a `prisma-next` channel I could not verify; it now links to Discord generically. ## Behavior changes & evidence - **`orm init` installs `prisma@latest`** instead of `prisma@next`, and falls back to `@prisma/cli-engine@latest` when the manifest does not pin the engine. [packages/1-framework/3-tooling/cli/src/orm/init.ts](packages/1-framework/3-tooling/cli/src/orm/init.ts), [packages/1-framework/3-tooling/cli/src/orm/init-packages.ts](packages/1-framework/3-tooling/cli/src/orm/init-packages.ts). Evidence: [packages/1-framework/3-tooling/cli/test/orm/init-install.test.ts](packages/1-framework/3-tooling/cli/test/orm/init-install.test.ts), [test/integration/test/cli.init-skill-distribution.integration.test.ts](test/integration/test/cli.init-skill-distribution.integration.test.ts). - **Scaffolded quick-reference notes and the skill quickstart** tell users to run `prisma@latest orm init`. [packages/1-framework/3-tooling/cli/src/commands/init/templates/quick-reference-postgres.md](packages/1-framework/3-tooling/cli/src/commands/init/templates/quick-reference-postgres.md), [skills/prisma-8/references/quickstart.md](skills/prisma-8/references/quickstart.md). Evidence: [packages/1-framework/3-tooling/cli/test/commands/init/__snapshots__/templates.test.ts.snap](packages/1-framework/3-tooling/cli/test/commands/init/__snapshots__/templates.test.ts.snap). - **No other runtime change.** Every other edit is prose in docs, comments, `package.json` descriptions, and `//` comments in test fixture schemas, which the emitter drops. ## Testing performed - `pnpm test` in `packages/1-framework/3-tooling/cli`: 115 files, 1437 tests passed - `pnpm lint:legacy-name`, `pnpm lint:docs`, `pnpm lint:skills`, `pnpm lint:rules:footprint`, `pnpm lint:manifests`: all pass (the `errors` README warning is pre-existing) - `pnpm fixtures:check` could not run in this worktree because the examples' `prisma` binary is not installed. The only schema edits are `//` comments, which do not reach the emitted contract. ## Skill update `skills/prisma-8/references/quickstart.md` is updated in the second commit: its `orm init` commands moved from `@prisma/cli@next` to `prisma@latest`, the same change the README makes. ## Alternatives considered - **Rename the identifiers too** (`prisma-next.md`, `PRISMA_NEXT_*`, `PrismaNext*` types, the image file). Each is a user-visible surface with an upgrade path, and the docs still name `prisma-next.md`. Left for a deliberate rename with upgrade instructions. - **Sweep the ADRs, changelog, and project write-ups as well.** The repo's own legacy-name lint exempts them as dated records, and the mechanical pass mangled sentences that describe the rename itself. Following the existing policy keeps the diff honest. - **Keep `@latest` on the commands, as the docs pages write them.** The v8 line is `latest` now, so the tag adds nothing; the README uses the bare `npm create prisma` and `npx prisma …` forms. ## Checklist - [x] All commits are signed off (`git commit -s`) per the [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). - [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is scoped to one logical concern. - [x] Tests are updated (the CLI install-command tests and snapshots). - [ ] The PR title is in `TML-NNNN: <sentence-case title>` form. No Linear ticket exists for this change. - [x] The **Skill update** section above is filled in. ## Notes for the reviewer The first two commits are small and worth reading line by line. The third is large but uniform; spot-check a few files rather than reading all 345. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 1 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 天前 | ||
| 2 天前 | ||
| 4 个月前 | ||
| 2 个月前 | ||
| 1 天前 | ||
| 1 天前 | ||
| 1 个月前 |