| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(psl): a model with no @@map names its table verbatim, and the planner refuses the silent rename (TML-3248) (#30317) ## At a glance This schema: ```prisma model UserProfile { id Int @id email String } ``` used to create and read a table called `"userProfile"`. After this PR it creates and reads `"UserProfile"`, the name as written. ## The decision A model with no `@@map` names its table verbatim. The same rule applies to Mongo collections. Prisma 8's policy is that nothing transforms identifier case implicitly: if the storage name differs from the name in the schema, the schema says so with `@@map` or `@map`. Every other authoring surface already follows that policy. Fields use the field name as written. Native enums use the block name as written. The TypeScript authoring DSL uses the model name as written. The Prisma 7 schema source uses the model name as written, which is also what Prisma 7 itself did. The PSL interpreter was the one exception: it lowered the first letter of the model name, which nobody on the team knew or wanted, and which produced camelCase table names that match no convention. This PR removes that rule. There is now one function per interpreter that answers "what is the storage name of a model with no `@@map`", and it returns the model name unchanged. ## What this fixes right away `contract infer` decides whether to write `@@map` by asking whether the model name it chose differs from the table name it read. For a database table already called `"UserProfile"` the two are identical, so it wrote `model UserProfile` with no `@@map`, the contract then resolved to `"userProfile"`, and `db verify` reported the real table as missing (TML-3248). Every table created by an earlier Prisma version is PascalCase, so every such database hit this. With a verbatim default, "differs from the table name" is exactly the right test again, and `toModelName` now derives its decision from the same function the interpreter uses, so the two sides cannot drift apart. A new journey test infers, emits and verifies a `LegacyAccount` table end to end. ## What this breaks, and how the upgrade stays safe Any existing schema with a PascalCase model and no `@@map` now names a different table. Left alone, that is dangerous: `migration plan` diffs the new contract against the old one, sees table `userProfile` gone and `UserProfile` new, and plans a `DropTable` followed by a `CreateTable`. Prisma 8 has no rename-table operation, and `migration plan`, `migrate` and `db update` all allow destructive operations by default. So a user who upgrades and plans would get a migration that drops their table and recreates it empty. Two things prevent that. **A codemod that keeps every existing table exactly where it is.** `scripts/codemods/add-model-map.mjs` inserts `@@map("<current table name>")` into every `model` block that has none: ```prisma model UserProfile { id Int @id email String @@map("userProfile") } ``` The emitted contract, the storage hash, and the database are unchanged by that edit, so a user who runs it has no migration to apply and nothing to re-sign. The script is idempotent, skips `@@base` variants (they share their base's table), and skips `enum`, `view` and `native_enum` blocks. It refuses with a file and line number on any `model` line it cannot parse rather than skipping it. Twenty-three tests cover the header and body shapes it handles. **A planner guard for anyone who skips the codemod.** The Postgres and SQLite planners now detect a drop of `X` and a create of `Y` in the same namespace where `X` is `Y` with its first letter lowered. Instead of planning it, they fail with: ``` MIGRATION.TABLE_NAME_CASE_CHANGED: the plan would drop "userProfile" and create "UserProfile" ``` The message explains that this is the shape of a schema upgraded across this change, and gives both ways out: add `@@map("userProfile")` (or run the codemod) to keep the table, or, for a deliberate rename, run `ALTER TABLE "userProfile" RENAME TO "UserProfile"` by hand, after which the plan is empty. There is no bypass flag. The guard matches on name alone, so it still fires when the model also gained a column in the same change. It cannot fire on `db init` against an empty database, since there is nothing to drop. The guard is temporary; its test records the condition for removing it. ## How the repository itself was upgraded The codemod was run over every committed schema in the repo: 196 files, 335 added `@@map` lines, nothing else. `fixtures:check` re-emitted all 21 fixture projects and produced no change to any `contract.json`, `contract.d.ts`, migration plan or ref, which is the proof that the edit preserves storage. Prisma 7 grammar fixtures and the parser's formatting golden corpus were left untouched on purpose. Unit and integration tests that write PSL inline were updated to expect verbatim names, since those tests should assert the new behaviour rather than map around it. Two assertions that had silently become vacuous under the old default (a lookup keyed on the lowercase name returning `undefined`) now look up the real table. A pending upgrade fragment under `upgrade-instructions/` points at the codemod so the breaking-change coverage check passes; the full upgrade recipe and release notes follow in the next PR. `projects/psl-verbatim-table-names/` holds the spec, design notes and plan for the two-PR project. ## Alternatives considered - **Fix only `contract infer`** so it writes `@@map` for PascalCase tables. This was the original TML-3248 fix. It leaves in place a default nobody knew about and that contradicts every other authoring surface. - **Have infer always write `@@map`.** Same objection; it hides the default instead of removing it. - **Detect the rename in the planner and emit a table rename.** There is no rename-table operation to emit, and a structural-match heuristic would also fire on genuine renames later. Detect and refuse is the safe shape; the codemod is the fix. - **Regenerate every repo fixture with the new table names.** Adding `@@map` instead keeps every emitted artifact byte-identical, and exercises the user codemod on 196 real schemas before it ships. Refs: TML-3248 🤖 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** * Unmapped SQL tables and MongoDB collections now retain the model’s original capitalization by default. * Migration planning detects case-only table renames and stops safely with a suggested SQL rename. * Added a codemod to preserve existing table names by adding explicit mappings, including support for commented model blocks. * **Documentation** * Added upgrade guidance for adopting verbatim table and collection names. * Updated error-reference guidance with manual rename instructions. * **Tests** * Expanded coverage for naming behavior, migrations, codemod handling, and upgrade scenarios. <!-- 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.1 <noreply@anthropic.com> | 21 小时前 |