{{ message }}
Tags: prisma/orm
Tags
fix(target-postgres): avoid uncast array_position for pg.enum ORDER BY ( #30191) ## Summary `ORDER BY` on a column typed `pg.enum(...)` rendered as `array_position(ARRAY[...]::text[], "col")` with no cast on the column argument. Against a native Postgres enum column that is rejected outright: ``` 42883: function array_position(text[], "TicketStatus") does not exist ``` So ordering by any enum-restricted column failed at runtime on rc.8. `db.sql` failed identically — it is the SQL renderer, not the ORM surface, so dropping to the SQL builder was not a workaround. Fixes #30163 ## The fix Gate the `array_position` declaration-order rewrite off for native enums, rather than casting the column argument to `text`. A native Postgres enum already sorts by declaration order under a plain `ORDER BY` — Postgres orders enum values by `pg_enum.enumsortorder` — so the rewrite is redundant there. It exists for value-sets backed by `text`/`varchar` columns with a CHECK constraint, which would otherwise sort alphabetically. Those are unaffected: they carry `pg/text@1`, the gate is inert for them, and the existing declaration-order suite passes 7/7 unchanged. The alternative was casting the column inside `array_position`. That also works — it preserves declaration order, it does not sort alphabetically — but it keeps a per-row function call that defeats a plain index on the column, and it leaves the renderer unable to distinguish "needs sort-order emulation" from "the database already sorts this correctly". Gating also agrees with `renderWhere`, which renders comparisons on the raw column, so a keyset/cursor predicate already compares by enum ordering where `array_position` did not. ## Why it is safe Contract declaration order and `pg_enum.enumsortorder` are kept identical by the migration planner: it can only append a value (`ALTER TYPE … ADD VALUE`, no `BEFORE`/`AFTER`) and refuses any other member change — rename, removal, or reorder — via `nativeEnumMemberChangeRefusal`. If that refusal is ever relaxed to permit reordering, this gate has to be revisited. The gate keys on `codecId`, not `nativeType`. A hand-authored contract carrying a `pg/text@1` codec over a column whose adopted physical type happens to be a native enum would not be caught — reachable only by hand-adopting an existing enum type as text, not by anything `pg.enum(...)` authoring produces. ## Interaction with #30099 #30099 ("enum ORDER BY / DISTINCT ON loses declaration order behind a derived table") touches the same function. **This PR should land first** — it is ~10 lines against a hard runtime error on a published release, while #30099 is larger and still in review. #30099 deletes `TableSourceCoordinate` / `collectTableSources` and both resolver functions here, replacing them with `resolveColumnValueSetFromSource(source, column, contract)` returning `{ found, values }`. On rebase, drop both call sites of `sortsByDeclarationOrderNatively` and call it once instead, in that PR's `table-source` branch, immediately after `storageColumn` is resolved: ```ts if (sortsByDeclarationOrderNatively(storageColumn)) return { found: true, values: undefined }; ``` `found: true`, not `false` — the column exists, it is simply not rewritten, and the identifier resolver's ambiguity counter depends on that distinction. That single site also covers #30099's new derived-table recursion, which this PR's two call sites do not reach. Re-inserting the gate at the two old call sites instead would pass the tests here but leave a native-enum column behind a `distinct()`/`groupBy()` wrap as a new, untested 42883. ## Testing `test/integration/test/ports/prisma/functional/issues-30163-enum-order-by` — an ORM-level port test. The harness pushes the contract through the plan → apply path (no hand-written DDL), then seeds rows and queries through the public facade: ```ts await db.public.Ticket.orderBy([(t) => t.status.asc(), (t) => t.id.asc()]) .select('id', 'status') .all(); ``` Declaration order is `open, closed`, so alphabetical ordering is distinguishable from a correct sort. Ascending and descending are both asserted on the whole result shape. Against the renderer on `main` both cases fail with `42883`; with the fix both pass. The existing text-backed value-set suite (`order-by-enum.integration.test.ts`) passes 7/7 unchanged, and the postgres adapter suite is green at 866 passed / 3 expected-fail. ## Release note `docs/releases/v8.0.0-rc.9.md` does not exist yet; the entry follows once it does, matching the precedent set by #30099 for rc.5. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01UcqoY3CKfnubdZt5YQk2Rq <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Fixed PostgreSQL native enum sorting so ascending and descending order follow the enum’s declared value order. - Prevented runtime errors when ordering columns backed by native PostgreSQL enums. - Improved deterministic results when multiple records share the same enum value. - Corrected `distinctOn` behavior to return one record for each enum value. - Ensured native enum queries behave consistently across supported ordering scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Steven McClankerton <tatarintsev@prisma.io> Co-authored-by: Steven McClankerton <tatarintsev@prisma.io> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(schema-ir): match int8 decimal-text defaults against their safe-i… …nteger number (#30194) ## Summary A literal `@default` on an `int8` column made `db migrate` fail its own post-apply verification with `MIGRATION.SCHEMA_VERIFY_FAILED`. An `int4` column with the same default verified fine. Fixes #30174 ## The true root cause `parsePostgresDefault`'s `isBigInt` branch (`packages/3-targets/3-targets/postgres/src/core/default-normalizer.ts:225-228`) returns decimal text for *every* `int8` default, regardless of DDL spelling. So even a bare `DEFAULT 0` introspects back as `"0"` (string) against the contract's `0` (number) for `pg/int8number@1`. It is a string/number type asymmetry, not a syntax one. Postgres does **not** re-derive a quoted cast for small `bigint` defaults: `bigint DEFAULT 0` comes back as `0` — bare within `int4` range, quoted+cast only beyond it. Verified on PostgreSQL 17.11 and PGlite 18.3, byte-identical: ``` bigint DEFAULT 0 -> 0 bigint DEFAULT 9223372036854775807 -> '9223372036854775807'::bigint ``` The `'0'::int8` our own DDL renders is ours, not Postgres's: `pgRenderDdlColumnDefault` runs the value through `codec.encode`, `pgInt8NumberEncode` returns a string, and `pgInlineLiteral` quotes and casts any string wire. ## Why the fix cannot live on the render path or in `postgresResolveDefault` `postgresResolveDefault`'s output becomes `resolvedDefault` (`packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts:127-129`), which `columnLike()` (`packages/3-targets/3-targets/postgres/src/core/migrations/column-ddl-rendering.ts:56`) maps into the `StorageColumn.default` slot that reaches `codec.decodeJson` (`packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts:1770`). *"One differ drives both verify and plan"* (`packages/3-targets/3-targets/postgres/src/core/migrations/diff-database-schema.ts:214`). The two codecs bound to the same `resolvedNativeType` are mutually incompatible: `PgInt8NumberCodec.decodeJson` requires a JSON number (`codecs.ts:723-725` — its own doc comment calls this "the deliberate exception to the decimal-text rule ... and the codec's purpose"), while `PgInt8Codec.decodeJson` requires a decimal string (`codecs.ts:660-668`). Introspection has no codec identity to consult, so it cannot pick a shape per column, and any normalization performed on the *shared* `resolvedDefault` before it reaches DDL rendering corrupts one codec or the other. Confirmed with three live-database probes: ``` PROBE A int8number + decimal-TEXT resolvedDefault (proposed normalize-to-string fix) -> pg/int8number@1 must be a number PROBE B int8@1 + NUMBER resolvedDefault (the inverse: normalize-to-number) -> pg/int8@1 must be a decimal string PROBE C int8number + NUMBER resolvedDefault (this PR's fix: unchanged) -> ok, stage execute, no failure ``` ## What the fix is One spelling-gated branch in the module-private `normalizeLiteralValue` (`packages/2-sql/1-core/schema-ir/src/ir/resolved-default-equality.ts`), beside the existing temporal-normalization branch: a safe-integer number is compared against the decimal text it denotes, only under an `int8`/`bigint` native type. It **normalizes rather than widens**: `literalValuesEqual` is byte-for-byte unchanged, and `resolvedDefaultsEqual`'s signature is unchanged. The normalized value lives for the one comparison call and never reaches `resolvedDefault` or the DDL renderer, so it cannot corrupt the codec-typed value `pgRenderDdlColumnDefault` depends on. ## Soundness `String(n)` is injective over safe integers, so no two distinct values collide. `pgInt8NumberGuard` (`packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts:158-169`) already rejects everything outside `Number.isSafeInteger` at both encode and decode, so the gate this fix adds is exactly coextensive with the codec's own domain and can produce no reachable false negative. ## On the reporter's claim He is right that `BigInt` and `BigIntNumber` produce byte-identical DDL. What's wrong is only the inference that the codec family causes it: `pg/int8@1` carries `"value": "0"` and round-trips text→text; `pg/int8number@1` carries `"value": 0` and is the sole reproducer. ## The layering residue, stated honestly `int8` is a Postgres alias for the standard SQL `bigint`, and this shared SQL-core package now names it. `isTemporalNativeType` directly above already matches `timestamptz` the same way, and `bigint` itself is standard SQL accepted by MySQL/MSSQL/SQLite. The clean fix is a comparison-only target hook — see follow-up below. ## Testing - `schema-ir`: 264 passed - `family-sql` (`packages/2-sql/9-family`): 340 passed - `adapter-postgres`: 867 passed, 3 expected-fail, 0 failed - End-to-end on real PostgreSQL 17.11: applied and verified the reporter's model plus a `Number.MAX_SAFE_INTEGER`-adjacent `int8` default with no precision loss - Unit coverage: the safe-integer match in both operand orders, a negative integer, a genuine mismatch, no effect without an `int8`/`bigint` native type, a rounded-number-vs-exact-text rejection, an outside-safe-integer-range rejection (even when the text is exact), and two huge decimal-text strings compared by identity ## Follow-ups (not opened as issues; not fixed in this PR) - **Comparison-only normalization seam.** `DefaultResolver` (`packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts:62-71`) cannot carry this normalization because its output is dual-purpose (verify *and* plan/DDL). The proper home is a new comparison-only target hook alongside `DefaultResolver` / `NativeTypeExpander` / `DefaultRenderer`, so this equality file needs no `int8`/`bigint` spelling at all. That's an architecture change (Ask First) beyond this bug fix. - **`int8`-family list-default crash.** `BigInt[] @default([1,2])` -> `CLI.UNEXPECTED: pg/int8@1 database JSON value must be a decimal string`; `BigIntNumber[] @default([1,2])` -> `... must be a number`. Cause: `pgRenderDdlColumnDefault` (`control-adapter.ts:1770`) hands the whole array to `codec.decodeJson` instead of decoding per element. Pre-existing, out of scope, and the same codec-shape constraint this fix navigates — the reporter's money model has 22 defaulted `int8` columns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01UcqoY3CKfnubdZt5YQk2Rq Signed-off-by: Steven McClankerton <tatarintsev@prisma.io> Co-authored-by: Steven McClankerton <tatarintsev@prisma.io> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(cli): use "Prisma ORM" for user-visible CLI strings (#30192) ## Summary User-visible CLI surfaces still said "Prisma Next" after the rename. This changes them to **"Prisma ORM"**, which is version-independent — these strings do not need revisiting at each major release. Fixes #30062 ## Surfaces changed - `orm init` command summary and its bug-report string - `lsp` command summary and description - The scaffolded quick-reference templates (postgres + mongo) - The scaffolded `README.md` templates (postgres + mongo) - The telemetry first-run consent notice — the first branded sentence a new user sees - The three config-validation diagnostics (`No Prisma ORM configuration was loaded`, etc.) - The two install-fallback warnings printed during `orm init` - The remaining internal doc comments in `cli/src`, so the source uses one name throughout After this change there is no occurrence of "Prisma Next" anywhere in `packages/1-framework/3-tooling/cli/src`. ## Deliberately unchanged - `QUICK_REFERENCE_FILE = 'prisma-next.md'` and every reference to it *by filename*. This is covered by the standing allowance at `scripts/lint-legacy-name.mjs:60`, which names the file specifically and tracks its removal to **ROADMAP § 3**. The filename/greeting mismatch is a known, tracked interim state. - `TELEMETRY_DOCS_URL` and the `prisma-next.dev` URLs — live infrastructure, not brand strings. - `PRISMA_NEXT_DISABLE_TELEMETRY` — renaming it would break existing opt-outs. - The `// use prisma-next` schema directive — a language construct, same allowance. - Legacy skill slugs in `skill-sources.ts`. The issue's fourth complaint (skills labeled "Prisma Next agent-skill") is **moot**: `orm init` no longer installs skills at all (`commands/init/skill-sources.ts:1-5`). ## Follow-ups, not in this PR - Runtime error strings in the target packs still say "Prisma Next": `packages/3-targets/3-targets/postgres/src/core/migrations/runner.ts:322`, `issue-planner.ts:516`, `packages/3-targets/3-targets/sqlite/src/core/migrations/runner.ts:335`. A different surface, not raised by the issue. - Package READMEs and `package.json` descriptions across `packages/3-targets/**` and `packages/3-extensions/**` — explicitly deferred by the issue ("internal package names, repo docs … can lag"). ## Testing Verified against the built bin rather than the diff: all help screens render "Prisma ORM" and contain no "Prisma Next"; a real `orm init` scaffold produces 11 "Prisma ORM" occurrences across `README.md` and `prisma-next.md` and zero legacy ones. - CLI package suite: 115 files, 1436 tests, green - Init-related integration tests (`cli.init-templates`, `cli.init-facade-imports`, `cli.init-skill-distribution`, `cli.config-section-requirements`, `cli.bin-smoke`): 5 files, 37 tests, green - `node scripts/lint-legacy-name.mjs`: clean - `pnpm lint` and `pnpm typecheck`: clean Per review feedback, the two rename-assertion tests added earlier were removed rather than adjusted — they only asserted the absence of strings this PR deletes, which is not coverage worth carrying. The existing template snapshots already pin the scaffolded output. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01UcqoY3CKfnubdZt5YQk2Rq <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Updated CLI help text, diagnostics, telemetry notices, and generated MongoDB/PostgreSQL templates to consistently use “Prisma ORM” branding. * Refreshed related setup guidance, sample descriptions, headings, and references across generated documentation. * Clarified terminology in control API and ORM configuration documentation. * **Tests** * Updated diagnostic expectations to reflect the “Prisma ORM” terminology. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Steven McClankerton <tatarintsev@prisma.io> Co-authored-by: Steven McClankerton <tatarintsev@prisma.io> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ci: fix the prisma-cli publish notification (event type, repo name) (#… …30179) ## Linked issue n/a — small change; fallout from the repository rename (prisma/prisma → prisma/orm). ## Summary The publish workflow's "Notify prisma-cli" step never reached its consumer. It sent `event_type: family-published`, but prisma-cli's `update-product-versions.yml` triggers on `product-published`, so the auto-repin only ever ran from its daily scheduled backstop. The payload also still named this repo `prisma/prisma`. The step now sends `product-published` with `repo: prisma/orm`. ## Testing performed - Verified the receiving side: `prisma/prisma-cli` `.github/workflows/update-product-versions.yml` triggers on `repository_dispatch: types: [product-published]` and reads nothing from the payload, so the `repo` field is informational. - The diff changes only characters inside an existing quoted scalar; no YAML structure changed. ## Skill update n/a — internal only (CI workflow; no user-facing surface). ## Checklist - [x] All commits are signed off (`git commit -s`) per the [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). The DCO status check will block merge if any commit is missing a `Signed-off-by:` trailer. - [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is scoped to one logical concern. - [x] Tests are updated (n/a — CI workflow change, no test surface). - [ ] The PR title is in `TML-NNNN: <sentence-case title>` form — no Linear ticket exists for this rename fallout; the title follows the repo's convention for such chores (cf. #30145). - [x] The **Skill update** section above is filled in. ## Notes for the reviewer - The event-type mismatch predates the rename. prisma-cli's consumer workflow was created on 2026-08-17 listening for `product-published` (prisma/prisma-cli#192); this sender, added on 2026-08-13, was never updated. The daily cron masked the dead letter — a lost dispatch delays the repin by at most a day, which is why nothing visibly broke. - Companion PRs update the other side of the rename: `prisma/orm` `v7` branch (`github.repository` guards), `prisma/engines-wrapper` (workflow dispatch target), and `prisma/prisma-engines` (Makefile clone URL). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated the publication notification event type to `product-published`. * Repository and version information in the notification remain unchanged. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Oleksii Orlenko <robot@aqrln.net>
TML-3199: Docs hygiene — dead links, stale API instructions, and the … …payload label (#30016) # Docs hygiene: dead links, stale API instructions, and the payload label Main-based cleanup (not part of the raw-SQL stack, though its items were surfaced by that campaign's reviews). ## What changed - **ADR-INDEX's dead ADR 035 link** — a pure capitalization mismatch (`Dual authoring conflict resolution` vs the file's `Dual Authoring Conflict Resolution`); corrected to the file's actual name. - **`error-reference.md`: "Meta:" → "Payload:" (260 entries) + one preamble sentence.** The old label was wrong for half the file: `structuredError()` writes `error.meta` but `runtimeError()` writes `error.details`, and a scripted classification showed a per-code label is ill-defined — of the codes classifiable at all, eight are raised through *both* constructors. The neutral label plus a preamble stating the rule (meta from structuredError, details from runtimeError, some codes both ways) is accurate today and stays accurate as raise sites move. No tooling reads the label (verified against `list-error-codes.mjs`). - **Stale `validateContract<Contract>(contractJson)` instruction removed from four surfaces** (`AGENTS.md` § Key Patterns, Testing Guide ×3, the Runtime subsystem doc, and the `typed-contract-in-tests` rulecard) — no such export exists, and the stale pattern had already generated a false review finding. Replacements verified against current code: the client factory hydrates (`postgres<Contract>({ contractJson, url })`), and tests use `validateSqlContractFully<Contract>(contractJson)` (the idiom with 174 current usages). The `validateContract` in `family-instance-domain-actions` is deliberately untouched — that one is the real ADR 204 control-plane primitive, a different thing sharing the name. - **ADR 012's refs clause** now states ADR 205's own conclusion: the unindexed-predicate lint and refs-based budget heuristic ran off the removed sidecar and no longer run for any plan. (The previous wording invited a hunt for a `meta.refs` field that no longer exists.) - **One ticketed item needed nothing**: the four "dead" source links in the Runtime & Middleware doc were already fixed upstream — verified resolving, left alone. ## Known merge note This PR and the raw-SQL stack (#29997) both edit the tail of the same ADR 012 update note, for different reasons. The conflict is one line but **semantic**: whichever lands second must carry both intents (the stack scopes the wire-level-rows claim as historical; this states the refs heuristics gone). Taking either side wholesale silently drops the other. Out of scope, ticketed: 68 further dead links across `docs/` + the missing link checker, two orphaned error-reference entries the one-directional checker cannot see, and ADR 205's own upstream ambiguity (all on TML-3211). Refs: TML-3199 https://claude.ai/code/session_01NnNjsNcPMtbJZhnZz5Zzbe <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Updated contract hydration and validation guidance to reflect the current workflow. * Refreshed testing and runtime examples for standalone contract usage. * Corrected architecture decision record titles, links, and descriptions of removed raw-plan metadata. * Clarified that contract data can be passed directly through runtime setup. * **Tests** * Updated typed contract fixture guidance to use full SQL contract validation for parsed contract data. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Oleksii Orlenko <robot@aqrln.net>
fix(skills): jj-aware artifact guard and v2-named schema helpers (TML… …-3223) (#30089) ## Linked issue Refs TML-3223 part 2 — the skills audit's script findings. ## Summary Two script-level findings from the audit, independent of the docs PR (#30088). **The artifact guard could not run in the repo that ships it.** `guard-review-artifacts-ignored.mjs` shells out to `git rev-parse`, so in a non-colocated Jujutsu workspace it failed before checking anything — and the review-fetch and review-triage phases both gate on it, so both were blocked here. Git stays the primary path, unchanged and exact. When git cannot answer, the guard walks up to the `.jj` directory and accepts an artifact directory under that workspace root's `wip/` tree, which `.gitignore` covers wholesale; anything outside it is refused. Demonstrated in this workspace, both states: ``` $ node …/guard-review-artifacts-ignored.mjs --dir wip/reviews/x ok: review artifacts are under the ignored wip/ tree: wip/reviews/x # exit 0 $ node …/guard-review-artifacts-ignored.mjs --dir docs error: without git, review artifacts must live under the ignored wip/ tree: …/wip # exit 1 ``` **The schema helpers were named for the version they no longer enforce.** `normalizeReviewStateV1` / `assertReviewStateV1` check `version: 2`. Renamed to `…V2` across the definition and its six call sites; `rg ReviewStateV1` returns nothing. Both SKILL.md files that describe the guard as a git check now describe both paths. ## Testing performed `pnpm test:scripts` (green — 0 failures), `pnpm lint:skills` (green), plus the guard demonstration above. ## Skill update This PR is skill maintenance: one guard script, one schema module and its consumers, and the two SKILL.md sentences that describe them. ## Notes for the reviewer The rename is pure — no behavior change, no signature change. The guard's git path is byte-identical; everything new sits behind the branch git previously threw from. https://claude.ai/code/session_01NnNjsNcPMtbJZhnZz5Zzbe <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added safe review-artifact validation for Jujutsu workspaces without a Git directory. * Artifacts can be stored under the workspace’s ignored `wip/` directory, while unsafe paths and symlink escapes are rejected. * **Improvements** * Updated review-state processing to use the current schema across workflows. * Preserved existing Git-based safety checks and command-line behavior. * **Tests** * Added coverage for valid, missing, boundary-violating, and unsafe workspace artifact paths. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Oleksii Orlenko <robot@aqrln.net>
PreviousNext
