| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
TML-3278: a field's domain type matches its column, and every stored value and default is read by its codec (#30451) ## At a glance A contract has a domain half, the fields as the application sees them, and a storage half, the columns. The same declared type used to come out differently in the domain half depending on how it was written: ```prisma types { Short = VarChar(10) } enum Role { @@type("pg/text@1") ADMIN = "admin" USER = "user" } type Address { code Short role Role } model Member { id Int @id role Role roles Role[] @noCheck(elementNotNull) code Short home Address } ``` Before this PR, on `main`: - `roles Role[]` had no domain `valueSet`, although `role Role` did. - `code Short` had no domain `typeParams`, although the same field written `code VarChar(10)` had `{ length: 10 }`. - The composite member `Address.code` lost its `typeParams`, and `Address.role` lost its `valueSet`. After this PR, a field's domain type is the same whether it is single or a list, inline or named, on a model or on a composite type member. The storage half and every hash are unchanged. ## What this PR changes 1. The PSL interpreter no longer rewrites domain fields after the shared SQL contract builder built them. That rewrite is what dropped `typeParams` and `valueSet`. 2. The builder builds every domain field through one function: model scalar fields, model value-object fields, and composite type members. It no longer hard-codes Postgres `jsonb` for value objects; the storage type comes from the target the stack declares. 3. A field or member typed by a named type carries the named type's parameters in its domain type. The SQL emitter no longer looks them up in storage. 4. Value-object fields: a multi-table-inheritance variant links to a base keyed by one; a list of them takes `@default([])` or a JSON array literal for its one column; a default must match the composite type, with each member value read by the member's codec; a stack with no value-object storage refuses the field instead of dropping it; an attribute inside a `type` block is refused instead of ignored. 5. Every built-in codec's `decodeJson`, SQL and Mongo, refuses a JSON value that is not a stored form of its type, including one its type parameters rule out (`VarChar(3)` holding `"toolong"`), instead of passing it through. The rule is stated once, on `Codec.decodeJson`, and the checks live in one set of shared readers. Float codecs read and write `"NaN"`, `"Infinity"` and `"-Infinity"`, and a non-finite float default renders in DDL. `mongoCodec` no longer compiles a codec narrower than `JsonValue` without a `decodeJson`. 6. `prisma contract print` writes and reads back enum list fields, parameterized and enum-typed composite members, and value-object list defaults. 7. One function writes every column default to SQL: a new table, a new column, a changed default, a rebuilt SQLite table. It reads each literal, and each list element, through the column's codec, so a default the codec refuses stops with `CONTRACT.DEFAULT_INVALID` naming the table and column instead of reaching the database. 8. A changed default on an existing Postgres column is now applied. Before, the migration runner skipped the `SET DEFAULT` because the old default already satisfied its check, and verification then failed. 9. Verification compares PostgreSQL type names through one normaliser, so a column written with an alias (`char`, `varchar`, `int`, `float`) or without a length (`Char`, `Bit`) verifies after apply instead of reporting a mismatch. A `numeric` column takes every scale PostgreSQL accepts, including a negative one. 10. The CLI reports a library error under its own code, removes the connection string from every field of it, and lets a Prisma bug surface as an internal error instead of reporting it as the user's mistake. Of the 427 changed files, about 160 are source and documentation; the rest are tests and regenerated contracts and fixtures. ## Why the domain fields were wrong The PSL interpreter (`packages/2-sql/2-authoring/contract-psl/src/interpreter.ts`) resolves each field to a column descriptor and hands model nodes to the shared contract builder (`packages/2-sql/2-authoring/contract-ts/src/build-contract.ts`), which builds the domain field. The interpreter then ran a second pass that replaced every scalar list field and every value-object field with an object built from a few properties, dropping the rest. That pass existed because the builder's value-object node hard-coded Postgres `jsonb` storage, while PSL takes the storage type from the target. The interpreter sent value-object fields to the builder disguised as scalar columns and patched the result. Composite type members were hand-built twice, once in the interpreter and once in the builder, and the two copies had drifted. ## Value objects are built once `ValueObjectFieldNode` now carries its storage descriptor, and the builder reads it like any column. Composite types reach the builder as `ValueObjectNode`s whose members are `ScalarMemberNode` or `ValueObjectMemberNode`; a model field is a member with a column (`FieldNode extends ScalarMemberNode`). A member is typed by `ScalarTypeDescriptor`, a codec id and type parameters only, so it cannot carry a column name or a storage-only value set. `buildDomainField` builds all of them. A value-object default is now read the way the database will read it: a list field takes an array, a single field an object, keys must be members, required members must be present, and each member value goes through the member codec's `decodeJson`. An enum member must hold one of the enum's values. ## Codecs check the JSON they read Checking member values through their codecs exposed that many codecs' `decodeJson` was a cast: `pg/text@1` returned a number unchanged, so `{"street": 1}` on a `String` member loaded, and a hand-edited contract with `"default": 1` on a text column passed. `decodeJson` also runs on database-produced JSON for relation includes, so every accepted form was checked against what Postgres and SQLite actually write (`to_json`, `json_agg`, `json_group_array`), including `text[]` NULL elements, padded `char`, `infinity` timestamps and float NaN. Each codec now refuses anything else with `RUNTIME.DECODE_FAILED` naming the codec and the value. Where a type has a constraint the database enforces, the codec checks it too: on PostgreSQL, `char`/`varchar` length, `bit`/`varbit` length, `numeric` precision and scale, and the `int4` range. Each check was compared against what PostgreSQL itself accepts. A shared SQL codec checks only what every SQL target stores, and the PostgreSQL target adds its own rules where it adapts the codec; SQLite enforces no declared length, so its codecs check none. A uuid default is written in the canonical form PostgreSQL stores, whatever spelling the schema used, and `pg/uuid@1` reads only that form. The checks are one set of readers in `@internal/framework-components/codec` (`decodeJsonString`, `decodeJsonInteger`, `decodeJsonFloat` and others), used by the SQL, PostgreSQL, SQLite and Mongo codecs and by pgvector and PostGIS. The rule they implement is written once, on `Codec.decodeJson`; the ADRs and the codec authoring guide point at it. The float codecs previously refused the `"NaN"` PostgreSQL writes, so an include of a row holding NaN failed; that is fixed, and SQLite's float projection now writes an infinity as the same text. A `.default(Infinity)` renders as `'Infinity'::float8` on PostgreSQL and `9e999` on SQLite. The Mongo runtime reads documents through `decode`, not `decodeJson`; its `decodeJson` is used by the PSL enum reader, which accepted `"low"` as an `int32` member. The `mongoCodec` factory defaulted `decodeJson` to identity, which is where the pattern came from; it is now required for narrower types. ## Every default goes through one renderer Once codecs checked the JSON they read, the remaining gap was the paths that wrote a default without reading it at all: Postgres `SET DEFAULT`, and SQLite `ADD COLUMN` and table rebuilds rendered their own SQL. Each adapter now implements one `renderColumnDefault`, used by CREATE TABLE and by every planner operation, so all four paths refuse and render identically. Replacing the Postgres `SET DEFAULT` path exposed that the runner skipped a changed default whose old value satisfied the operation's check; a changed default now always runs. SQLite reads a `BigInt` default back as a number, so its codec now stores the canonical digit text, and PostgreSQL `int8` defaults compare through the same canonical form. The `migration.ts` that `migration plan` writes carries each column's codec on both targets, so running it writes exactly what the planner would. A `migration.ts` still written in the earlier `setDefault({ defaultSql })` form stops with `MIGRATION.OPERATION_OPTION_REMOVED` instead of silently dropping the default. The text-backed date and time codecs now read only ISO forms, as the Temporal codecs already did, so an include of such a column needs PostgreSQL's default `DateStyle` of `ISO`. ## Tests Each behaviour change has a test that failed before its fix: interpreter and builder tests assert whole field and diagnostic shapes; table-driven decode-form tests per codec; codec conformance cases against live Postgres and SQLite; PGlite integration tests reading `text[]`/`int4[]` with NULL elements and float NaN/Infinity through relation includes; printer round-trip cases; a type-level test that a narrow `mongoCodec` without `decodeJson` does not compile. Fixtures regenerated by `pnpm fixtures:check` gain domain entries only. No storage, profile or execution hash moves. The example migration snapshots are regenerated by the same command; user snapshots need no change, because snapshot checks recompute the storage hash only. ## Upgrading `upgrade-instructions/pending/domain-types-match-their-columns/` records the changes for applications and extension authors, each with its fix: - Re-emit the contract. `prisma contract print` refuses a contract emitted before this change until it is re-emitted. - Some schemas that loaded on `main` are refused at `contract emit`: a value-object default of the wrong shape or with a member value its codec refuses, an attribute inside a `type` block, a SQL enum member its `@@type` codec does not take, and a literal default its column type rules out (`VarChar(3) @default("toolong")`, `Uuid @default("nope")`). - A literal default of the wrong JSON kind in a hand-edited `contract.json` fails when a migration is planned. - `pg/text-array@1` now reads a NULL element as `null` and its type is `readonly (string | null)[]`. Writing NaN through `sqlite/real@1` now throws instead of storing NULL. - A Mongo codec narrower than `JsonValue` needs a `decodeJson`. - A contract with a uuid default written in another spelling gets a new storage hash when re-emitted; the note says how to bring a database up to it. - `mapCaughtMigrationError` is removed from `@prisma/orm-toolchain/cli/control-api`; `errorFromCaught` replaces it. - A hand-written `migration.ts` that calls `setDefault` with `defaultSql`, or builds SQLite column specs with `defaultSql`, no longer compiles and stops with `MIGRATION.OPERATION_OPTION_REMOVED` when run; it takes the column instead. Its `ops.json` still applies. A migration planned by an earlier version that changes a default must have its `setDefault` call rewritten and its `migration.ts` run again (or be deleted and planned again), so the default is actually set. - An include of a text-backed date or time column needs `DateStyle` set to `ISO`, PostgreSQL's default. ## Alternatives considered - **Delete only the scalar-list rewrite branch.** The first version of this PR did that. It fixed the report and left the rewrite pass, the `jsonb` constant and the duplicated member builders that caused it. - **Keep the old lenient `decodeJson` and check value-object member values separately.** That would leave hand-edited contracts and relation includes unchecked, and duplicate each codec's knowledge of its own stored form. - **Accept `@default([])` only through a JSON literal on a list of value objects.** A list literal is how users write a list default, and it loaded on `main`. - **Check type parameters only when a default is authored, not in `decodeJson`.** That would leave hand-edited contracts unchecked and put a second copy of each type's rules outside its codec. This also fixes TML-2089 (a changed Postgres default was never applied). Related, and deliberately separate: TML-3341 (composite types always land in the default namespace) and TML-3384 (the Mongo runtime returns a document field as whatever BSON type it holds). Agent: nestor-25 🤖 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> | 10 小时前 | |
chore(release): bump to 8.0.0-rc.14 (#30541) ## Release `8.0.0-rc.13` → `8.0.0-rc.14` See [`docs/oss/versioning.md`](https://github.com/prisma/orm/blob/main/docs/oss/versioning.md) for the versioning policy and the release procedure. ## What to review - **Release notes:** [`docs/releases/v8.0.0-rc.14.md`](https://github.com/prisma/orm/blob/release/8.0.0-rc.14/docs/releases/v8.0.0-rc.14.md). This file becomes the body of the GitHub Release. `CHANGELOG.md` carries the same text. The notes cover the 15 pull requests merged since `v8.0.0-rc.13`; internal refactors, process notes and documentation-only changes are left out. - **Upgrade guides:** the [app guide](https://github.com/prisma/orm/blob/release/8.0.0-rc.14/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.13-to-8.0.0-rc.14/instructions.md) and the [extension guide](https://github.com/prisma/orm/blob/release/8.0.0-rc.14/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.13-to-8.0.0-rc.14/instructions.md). They are assembled from the 5 pending fragments, which moved unchanged to `upgrade-instructions/releases/8.0.0-rc.13-to-8.0.0-rc.14/sources/`. ## What changed - The `version` of every workspace `package.json`, and the `workspace:` specifiers that name it, in the package files and in `pnpm-lock.yaml`. - The `library_version` stamp in `skills/prisma-8/SKILL.md`. - The extension version stamp in the Supabase `contract.json` and `contract.d.ts` artefacts. - The upgrade guides, the archived fragments, the release notes and the changelog. ## How the guides were assembled - Order: package changes first (`@prisma/cli-engine` pin, `temporal-polyfill` peer), then the serverless client, date and time defaults, and the Mongo ORM. - Every line of every fragment is in a guide; I checked this by script. - The `temporal-polyfill-peer` fragments had their own `#` title. The guides drop it, so each change sits under a heading that names its change id, as every other entry does. - The app guide ends with the `contract-artifacts-restamp` entry, as the previous app guides do. - The fragments had no scripts or assets. `serverless-connect-returns-connection/extension` declared `changes: []` and adds nothing. ## Checks run locally - `pnpm check:upgrade-coverage --mode publish --prev v8.0.0-rc.13 --head HEAD` passes. - `pnpm check:release-notes --mode pr --prev origin/main` passes. - `pnpm lint:docs` passes. ## Pull requests that may merge first Four approved pull requests add pending fragments: #30521, #30530, #30512 and #30331. If one merges before this one, I merge `main` into this branch, add its fragment to the guides, archive it, and add its change to the notes. CI checks the merged tree, so a leftover fragment fails the merge queue. ## Merging this pull request ships the release The push to `main` carries the new root `version`. The `Publish to npm` workflow detects the change, publishes `8.0.0-rc.14` under the dist-tag `latest`, and creates a matching GitHub Release, marked as a pre-release. ## Docs site The matching docs-site pull request follows in prisma/web. It merges after this release is published. Agent: pasteur-43 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Postgres serverless connections now expose ORM and transaction helpers. Optional cursor batching and multi-file schema support in the language server are also available. * **Bug Fixes** * Improved MongoDB encoding, decoding, query, and upsert behavior; added Node 24 date/time support and made SQLite date defaults consistent. * Improved namespace-aware relation resolution, policy placement, database-sign guidance, and upgrade instructions. * **Breaking Changes** * Updated Postgres connection and cursor behavior, canonical date/time defaults and validation, MongoDB result shapes and codec requirements, and extension and contract diagnostics. <!-- 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> | 23 小时前 | |
chore(release): bump to 8.0.0-rc.14 (#30541) ## Release `8.0.0-rc.13` → `8.0.0-rc.14` See [`docs/oss/versioning.md`](https://github.com/prisma/orm/blob/main/docs/oss/versioning.md) for the versioning policy and the release procedure. ## What to review - **Release notes:** [`docs/releases/v8.0.0-rc.14.md`](https://github.com/prisma/orm/blob/release/8.0.0-rc.14/docs/releases/v8.0.0-rc.14.md). This file becomes the body of the GitHub Release. `CHANGELOG.md` carries the same text. The notes cover the 15 pull requests merged since `v8.0.0-rc.13`; internal refactors, process notes and documentation-only changes are left out. - **Upgrade guides:** the [app guide](https://github.com/prisma/orm/blob/release/8.0.0-rc.14/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.13-to-8.0.0-rc.14/instructions.md) and the [extension guide](https://github.com/prisma/orm/blob/release/8.0.0-rc.14/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.13-to-8.0.0-rc.14/instructions.md). They are assembled from the 5 pending fragments, which moved unchanged to `upgrade-instructions/releases/8.0.0-rc.13-to-8.0.0-rc.14/sources/`. ## What changed - The `version` of every workspace `package.json`, and the `workspace:` specifiers that name it, in the package files and in `pnpm-lock.yaml`. - The `library_version` stamp in `skills/prisma-8/SKILL.md`. - The extension version stamp in the Supabase `contract.json` and `contract.d.ts` artefacts. - The upgrade guides, the archived fragments, the release notes and the changelog. ## How the guides were assembled - Order: package changes first (`@prisma/cli-engine` pin, `temporal-polyfill` peer), then the serverless client, date and time defaults, and the Mongo ORM. - Every line of every fragment is in a guide; I checked this by script. - The `temporal-polyfill-peer` fragments had their own `#` title. The guides drop it, so each change sits under a heading that names its change id, as every other entry does. - The app guide ends with the `contract-artifacts-restamp` entry, as the previous app guides do. - The fragments had no scripts or assets. `serverless-connect-returns-connection/extension` declared `changes: []` and adds nothing. ## Checks run locally - `pnpm check:upgrade-coverage --mode publish --prev v8.0.0-rc.13 --head HEAD` passes. - `pnpm check:release-notes --mode pr --prev origin/main` passes. - `pnpm lint:docs` passes. ## Pull requests that may merge first Four approved pull requests add pending fragments: #30521, #30530, #30512 and #30331. If one merges before this one, I merge `main` into this branch, add its fragment to the guides, archive it, and add its change to the notes. CI checks the merged tree, so a leftover fragment fails the merge queue. ## Merging this pull request ships the release The push to `main` carries the new root `version`. The `Publish to npm` workflow detects the change, publishes `8.0.0-rc.14` under the dist-tag `latest`, and creates a matching GitHub Release, marked as a pre-release. ## Docs site The matching docs-site pull request follows in prisma/web. It merges after this release is published. Agent: pasteur-43 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Postgres serverless connections now expose ORM and transaction helpers. Optional cursor batching and multi-file schema support in the language server are also available. * **Bug Fixes** * Improved MongoDB encoding, decoding, query, and upsert behavior; added Node 24 date/time support and made SQLite date defaults consistent. * Improved namespace-aware relation resolution, policy placement, database-sign guidance, and upgrade instructions. * **Breaking Changes** * Updated Postgres connection and cursor behavior, canonical date/time defaults and validation, MongoDB result shapes and codec requirements, and extension and contract diagnostics. <!-- 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> | 23 小时前 | |
TML-3278: a field's domain type matches its column, and every stored value and default is read by its codec (#30451) ## At a glance A contract has a domain half, the fields as the application sees them, and a storage half, the columns. The same declared type used to come out differently in the domain half depending on how it was written: ```prisma types { Short = VarChar(10) } enum Role { @@type("pg/text@1") ADMIN = "admin" USER = "user" } type Address { code Short role Role } model Member { id Int @id role Role roles Role[] @noCheck(elementNotNull) code Short home Address } ``` Before this PR, on `main`: - `roles Role[]` had no domain `valueSet`, although `role Role` did. - `code Short` had no domain `typeParams`, although the same field written `code VarChar(10)` had `{ length: 10 }`. - The composite member `Address.code` lost its `typeParams`, and `Address.role` lost its `valueSet`. After this PR, a field's domain type is the same whether it is single or a list, inline or named, on a model or on a composite type member. The storage half and every hash are unchanged. ## What this PR changes 1. The PSL interpreter no longer rewrites domain fields after the shared SQL contract builder built them. That rewrite is what dropped `typeParams` and `valueSet`. 2. The builder builds every domain field through one function: model scalar fields, model value-object fields, and composite type members. It no longer hard-codes Postgres `jsonb` for value objects; the storage type comes from the target the stack declares. 3. A field or member typed by a named type carries the named type's parameters in its domain type. The SQL emitter no longer looks them up in storage. 4. Value-object fields: a multi-table-inheritance variant links to a base keyed by one; a list of them takes `@default([])` or a JSON array literal for its one column; a default must match the composite type, with each member value read by the member's codec; a stack with no value-object storage refuses the field instead of dropping it; an attribute inside a `type` block is refused instead of ignored. 5. Every built-in codec's `decodeJson`, SQL and Mongo, refuses a JSON value that is not a stored form of its type, including one its type parameters rule out (`VarChar(3)` holding `"toolong"`), instead of passing it through. The rule is stated once, on `Codec.decodeJson`, and the checks live in one set of shared readers. Float codecs read and write `"NaN"`, `"Infinity"` and `"-Infinity"`, and a non-finite float default renders in DDL. `mongoCodec` no longer compiles a codec narrower than `JsonValue` without a `decodeJson`. 6. `prisma contract print` writes and reads back enum list fields, parameterized and enum-typed composite members, and value-object list defaults. 7. One function writes every column default to SQL: a new table, a new column, a changed default, a rebuilt SQLite table. It reads each literal, and each list element, through the column's codec, so a default the codec refuses stops with `CONTRACT.DEFAULT_INVALID` naming the table and column instead of reaching the database. 8. A changed default on an existing Postgres column is now applied. Before, the migration runner skipped the `SET DEFAULT` because the old default already satisfied its check, and verification then failed. 9. Verification compares PostgreSQL type names through one normaliser, so a column written with an alias (`char`, `varchar`, `int`, `float`) or without a length (`Char`, `Bit`) verifies after apply instead of reporting a mismatch. A `numeric` column takes every scale PostgreSQL accepts, including a negative one. 10. The CLI reports a library error under its own code, removes the connection string from every field of it, and lets a Prisma bug surface as an internal error instead of reporting it as the user's mistake. Of the 427 changed files, about 160 are source and documentation; the rest are tests and regenerated contracts and fixtures. ## Why the domain fields were wrong The PSL interpreter (`packages/2-sql/2-authoring/contract-psl/src/interpreter.ts`) resolves each field to a column descriptor and hands model nodes to the shared contract builder (`packages/2-sql/2-authoring/contract-ts/src/build-contract.ts`), which builds the domain field. The interpreter then ran a second pass that replaced every scalar list field and every value-object field with an object built from a few properties, dropping the rest. That pass existed because the builder's value-object node hard-coded Postgres `jsonb` storage, while PSL takes the storage type from the target. The interpreter sent value-object fields to the builder disguised as scalar columns and patched the result. Composite type members were hand-built twice, once in the interpreter and once in the builder, and the two copies had drifted. ## Value objects are built once `ValueObjectFieldNode` now carries its storage descriptor, and the builder reads it like any column. Composite types reach the builder as `ValueObjectNode`s whose members are `ScalarMemberNode` or `ValueObjectMemberNode`; a model field is a member with a column (`FieldNode extends ScalarMemberNode`). A member is typed by `ScalarTypeDescriptor`, a codec id and type parameters only, so it cannot carry a column name or a storage-only value set. `buildDomainField` builds all of them. A value-object default is now read the way the database will read it: a list field takes an array, a single field an object, keys must be members, required members must be present, and each member value goes through the member codec's `decodeJson`. An enum member must hold one of the enum's values. ## Codecs check the JSON they read Checking member values through their codecs exposed that many codecs' `decodeJson` was a cast: `pg/text@1` returned a number unchanged, so `{"street": 1}` on a `String` member loaded, and a hand-edited contract with `"default": 1` on a text column passed. `decodeJson` also runs on database-produced JSON for relation includes, so every accepted form was checked against what Postgres and SQLite actually write (`to_json`, `json_agg`, `json_group_array`), including `text[]` NULL elements, padded `char`, `infinity` timestamps and float NaN. Each codec now refuses anything else with `RUNTIME.DECODE_FAILED` naming the codec and the value. Where a type has a constraint the database enforces, the codec checks it too: on PostgreSQL, `char`/`varchar` length, `bit`/`varbit` length, `numeric` precision and scale, and the `int4` range. Each check was compared against what PostgreSQL itself accepts. A shared SQL codec checks only what every SQL target stores, and the PostgreSQL target adds its own rules where it adapts the codec; SQLite enforces no declared length, so its codecs check none. A uuid default is written in the canonical form PostgreSQL stores, whatever spelling the schema used, and `pg/uuid@1` reads only that form. The checks are one set of readers in `@internal/framework-components/codec` (`decodeJsonString`, `decodeJsonInteger`, `decodeJsonFloat` and others), used by the SQL, PostgreSQL, SQLite and Mongo codecs and by pgvector and PostGIS. The rule they implement is written once, on `Codec.decodeJson`; the ADRs and the codec authoring guide point at it. The float codecs previously refused the `"NaN"` PostgreSQL writes, so an include of a row holding NaN failed; that is fixed, and SQLite's float projection now writes an infinity as the same text. A `.default(Infinity)` renders as `'Infinity'::float8` on PostgreSQL and `9e999` on SQLite. The Mongo runtime reads documents through `decode`, not `decodeJson`; its `decodeJson` is used by the PSL enum reader, which accepted `"low"` as an `int32` member. The `mongoCodec` factory defaulted `decodeJson` to identity, which is where the pattern came from; it is now required for narrower types. ## Every default goes through one renderer Once codecs checked the JSON they read, the remaining gap was the paths that wrote a default without reading it at all: Postgres `SET DEFAULT`, and SQLite `ADD COLUMN` and table rebuilds rendered their own SQL. Each adapter now implements one `renderColumnDefault`, used by CREATE TABLE and by every planner operation, so all four paths refuse and render identically. Replacing the Postgres `SET DEFAULT` path exposed that the runner skipped a changed default whose old value satisfied the operation's check; a changed default now always runs. SQLite reads a `BigInt` default back as a number, so its codec now stores the canonical digit text, and PostgreSQL `int8` defaults compare through the same canonical form. The `migration.ts` that `migration plan` writes carries each column's codec on both targets, so running it writes exactly what the planner would. A `migration.ts` still written in the earlier `setDefault({ defaultSql })` form stops with `MIGRATION.OPERATION_OPTION_REMOVED` instead of silently dropping the default. The text-backed date and time codecs now read only ISO forms, as the Temporal codecs already did, so an include of such a column needs PostgreSQL's default `DateStyle` of `ISO`. ## Tests Each behaviour change has a test that failed before its fix: interpreter and builder tests assert whole field and diagnostic shapes; table-driven decode-form tests per codec; codec conformance cases against live Postgres and SQLite; PGlite integration tests reading `text[]`/`int4[]` with NULL elements and float NaN/Infinity through relation includes; printer round-trip cases; a type-level test that a narrow `mongoCodec` without `decodeJson` does not compile. Fixtures regenerated by `pnpm fixtures:check` gain domain entries only. No storage, profile or execution hash moves. The example migration snapshots are regenerated by the same command; user snapshots need no change, because snapshot checks recompute the storage hash only. ## Upgrading `upgrade-instructions/pending/domain-types-match-their-columns/` records the changes for applications and extension authors, each with its fix: - Re-emit the contract. `prisma contract print` refuses a contract emitted before this change until it is re-emitted. - Some schemas that loaded on `main` are refused at `contract emit`: a value-object default of the wrong shape or with a member value its codec refuses, an attribute inside a `type` block, a SQL enum member its `@@type` codec does not take, and a literal default its column type rules out (`VarChar(3) @default("toolong")`, `Uuid @default("nope")`). - A literal default of the wrong JSON kind in a hand-edited `contract.json` fails when a migration is planned. - `pg/text-array@1` now reads a NULL element as `null` and its type is `readonly (string | null)[]`. Writing NaN through `sqlite/real@1` now throws instead of storing NULL. - A Mongo codec narrower than `JsonValue` needs a `decodeJson`. - A contract with a uuid default written in another spelling gets a new storage hash when re-emitted; the note says how to bring a database up to it. - `mapCaughtMigrationError` is removed from `@prisma/orm-toolchain/cli/control-api`; `errorFromCaught` replaces it. - A hand-written `migration.ts` that calls `setDefault` with `defaultSql`, or builds SQLite column specs with `defaultSql`, no longer compiles and stops with `MIGRATION.OPERATION_OPTION_REMOVED` when run; it takes the column instead. Its `ops.json` still applies. A migration planned by an earlier version that changes a default must have its `setDefault` call rewritten and its `migration.ts` run again (or be deleted and planned again), so the default is actually set. - An include of a text-backed date or time column needs `DateStyle` set to `ISO`, PostgreSQL's default. ## Alternatives considered - **Delete only the scalar-list rewrite branch.** The first version of this PR did that. It fixed the report and left the rewrite pass, the `jsonb` constant and the duplicated member builders that caused it. - **Keep the old lenient `decodeJson` and check value-object member values separately.** That would leave hand-edited contracts and relation includes unchecked, and duplicate each codec's knowledge of its own stored form. - **Accept `@default([])` only through a JSON literal on a list of value objects.** A list literal is how users write a list default, and it loaded on `main`. - **Check type parameters only when a default is authored, not in `decodeJson`.** That would leave hand-edited contracts unchecked and put a second copy of each type's rules outside its codec. This also fixes TML-2089 (a changed Postgres default was never applied). Related, and deliberately separate: TML-3341 (composite types always land in the default namespace) and TML-3384 (the Mongo runtime returns a document field as whatever BSON type it holds). Agent: nestor-25 🤖 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> | 10 小时前 |