Every SQL index is name-identified — wire names, expression/partial capture, rename convergence - #1047
Conversation
|
Important Review skippedToo many files! This PR contains 157 files, which is 57 over the limit of 100. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (57)
📒 Files selected for processing (159)
You can disable this status message by setting the ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
size-limit report 📦
|
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/extension-supabase
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/config-loader
@prisma-next/emitter
@prisma-next/language-server
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
… content-hash helpers to naming formatWireName/parseWireName own the <prefix>_<8hex> format family-wide, normalizeSqlBody stabilizes authored SQL bodies for hashing, and computeIndexContentHash hashes the D4 index tuple (authored column order, String()-coerced options sorted by key). WIRE_NAME_PREFIX_MAX_LENGTH plus assertWireNamePrefixLength enforce the 54-character prefix cap. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…f shared naming helpers RLS call sites now use formatWireName/parseWireName/normalizeSqlBody from @prisma-next/sql-schema-ir/naming; the policy prefix cap goes through the shared assertWireNamePrefixLength (same 54-character rule, same message). The rls-canonicalize export keeps only the RLS content-hash surface — the RLS-specific wire-name names are gone, wire names stay byte-identical. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…aming-mode prefix, columns xor expression Index gains name (required full physical name), prefix (present iff managed), columns xor expression, where, and a required unique flag; the constructor throws on the D1 invariants. IndexSchema, the d.ts emitter literal, factories.index, and canonicalization (unique: false survives the default-omission walk) move with it. Both lowerings compute the names through one shared path (lowerAuthoredIndex in the new @prisma-next/sql-contract/index-naming): unnamed indexes and FK-backing indexes lower managed with the default prefix + content-hash wire name, PSL map: lowers exact (verbatim name, no hash), TS name: lowers managed with the authored prefix. unique always lowers false — no surface authors it yet. A parity test pins identical wire names for PSL and TS on the unnamed row. Diff-tree identity is deliberately unchanged: SqlIndexIR still pairs by column tuple; the new fields flow through unused. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
pnpm fixtures:emit sweep: every emitted contract.json/contract.d.ts now carries full index names (managed wire names from authoring, exact names from infer map:) plus the required unique flag; storage hashes move accordingly. The Supabase contract regenerates through its checked-in generator; the target-postgres namespaced-contract fixture is refreshed from its source of truth, the integration namespaced-accessors emit (byte-identical copy, as originally created). Example migration ops are static serializations and deliberately unchanged — diff identity still pairs indexes by column tuple in this slice. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…remaining test literals The 43 ported-corpus _fixture contracts re-emit through their per-suite contract emit path (same regen as #1035); pgvector and cross-package planner tests adopt the required name/unique index fields. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Runtime-validated fixtures gain the required name/unique fields, and the live-catalog assertions that named authored indexes now expect the managed wire names the toolchain creates (TS name: is a prefix). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ion captures expression and partial indexes SqlIndexIR is name-identified: name required, id = the name, prefix/ expression/where added, columns optional (xor expression). isEqualTo is mode-selected by the receiver — both modes compare unique/type strict, options loose, columns ordered-strict when both sides carry them; an exact receiver byte-compares expression/where; a managed receiver never compares bodies. Expression indexes stamp dependsOn chains to every column of their table (deterministic over-approximation) on the family, postgres, and introspection paths alike. Postgres introspection selects the per-position pg_get_indexdef reprint and the pg_get_expr partial predicate; an index with any expression element becomes an expression node carrying the whole element list as one opaque string. The expression-skip and same-tuple dedup are deleted — every non-constraint-backed index enters the tree keyed by its catalog-unique name, with prefix stamped from parseWireName. Same-tuple twins (unique + redundant plain) introspect as distinct siblings and a contract declaring both verifies clean; the duplicate-definition signature folds in expression, where, and unique so twins and distinct-bodied expression indexes are not false duplicates. Derivations pass unique/prefix/expression/where through (the hardcoded unique: false is gone; partial derives from where). contract infer skips expression- and where-carrying nodes until slice 4 — no authoring surface can hold their bodies yet — and the relation index: false decision counts only indexes infer actually emits. SQLite keys by name via the shared class; its defaultIndexName fallbacks stay as dead-but-compiling code for the next dispatch to delete. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…lity contract:generate against the checked-in reference fixture, through the hermetic PGlite generator: partial indexes leave the adopted @@index set (no authoring surface carries their predicates until slice 4 — adopting them name-only would fail the exact-mode body compare), the FK relation they used to back gains an explicit index: false, and a same-tuple twin the old introspection dedup swallowed is adopted. All 17 supabase suites verify clean against the restored reference. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The verify suites raw-SQL their backing index as post_userId_idx while the unnamed contract index now lowers to the content-hash wire name; under name identity the live index must carry that name to pair. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…the two-phase index rename post-pass CreateIndexCall takes its element list as a column tuple or one opaque expression string, and its extras gain where and unique; createIndex renders CREATE [UNIQUE ]INDEX … [USING …] (<quoted columns | expression verbatim>) [WITH (…)] [WHERE (<where verbatim>)] — bodies verbatim, never quoted or escaped, the RLS predicate stance. New RenameIndexCall / renameIndex render ALTER INDEX … RENAME TO with from-present/to-absent prechecks and a to-present postcheck, widening class (same typology rationale as the policy rename), registered in the op union, the migration-script runtime factory, the render coverage list, and the index DDL bucket. The planner gains the index rename post-pass beside the (untouched) policy pass: phase 1 pairs wire-parseable extras to missing nodes by (schema, table, hash); phase 2 pairs the remaining managed-missing nodes to any-shape extras by content (columns ordered-strict both-defined-or-both-undefined, unique/type strict, options loose via the now-exported loose comparison, bodies byte-equal). Both phases are deterministic by sorted names, run only under widening, and consume the paired issues before per-issue mapping; leftovers create/drop as before. mapIndexNodeIssue and every other dead identity fallback (postgres table-bundle, family schema tree, sqlite planner + recreate strategy) now read the node name directly; defaultIndexName remains only as the lowering default prefix. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…4 boundary The duplicate-content index guard must not block signing a database that legally carries byte-identical twin indexes under different names, and infer currently skips expression- and where-carrying nodes — both resolve when slice 4 lands D8. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ocation A policy-disallowed rename now surfaces as indexIncompatible at the index location (the new name — its contract-side identity) instead of falling through to the generic missingButNonAdditive/no-location defaults; pinned through the planIssues gating path with the rename label in the conflict summary. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…xpression migration, exact adoption Three CLI journeys prove the slice done conditions end-to-end on PGlite: a pre-slice database (plain default index names) adopts exactly via infer → emit → sign, switches to managed authoring, and its first widening plan is byte-asserted to be ALTER INDEX RENAMEs only — applied and verified clean with the wire names live in pg_indexes; a contract carrying expression, partial, and unique-expression indexes (authored through the factory layer, the only surface until slice 2) plans byte-asserted CREATE INDEX DDL onto a fresh database, applies, verifies clean, and fails verify by name after an out-of-band drop; and a fields-only database mixing default-named and custom-named indexes round-trips infer → emit → verify with zero issues and a dry-run update planning zero operations. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Surgical sentence-level corrections where the slice invalidated prose, no ADR restructuring: ADR 234 notes indexes adopted the wire-name scheme with their own hash tuple via the family-shared naming module; ADR 235 extends the rename-fold and content-addressed-node passages to indexes; ADR 009 marks the deterministic index name as the managed prefix (the physical name gains the hash suffix); ADR 210 replaces the (columns, type, options)-identity sentence with name pairing plus attribute comparison. The Data Contract and Contract Emitter subsystem examples carry the new index JSON shape, the schema-ir README documents name identity and the naming helpers, and the Supabase CONTRACT-FIDELITY record reflects the partial-index omissions and the 17th index: false relation from the full-fidelity regeneration. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
User skill: indexes-are-name-identified — the new contract index shape (name/unique always, prefix when managed, columns xor expression, moved storage hashes), the physical-name table for every authoring input, the renames-only widening convergence for existing databases and its additive-only degradation, and the hard-coded-name caveat. Extension skill: rls-wire-name-helpers-moved-to-sql-schema-ir-naming — the deleted target-postgres exports and their generalized replacements in @prisma-next/sql-schema-ir/naming; and sql-index-entities-are-name-identified — the required-name contract Index/IndexSchema shape, the index(...) factory signature, the name-identified SqlIndexIR input/id/isEqualTo semantics, and the contract-space re-emit flow. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… load-error text The additive-only story now states what shipped behavior does: the additive plan creates the new wire-named index beside the old one, and once both exist a later widening plan has no missing node to pair and the rename precheck (target absent) can never pass — the old index leaves only via a destructive-allowed drop; a rename happens only when a widening plan is the first convergence. Corrected in the frontmatter summary and the convergence prose, and in the project spec scenario-C row and ADR draft planner section. The quoted load error is now verified against the real validator: the message is a Contract structural validation failure whose text contains "indexes[0].name must be a string (was missing)" and "indexes[0].unique must be boolean (was missing)" (arktype does say "was missing", under the failing storage-namespace path). Both skill files quote it as a substring of the path-prefixed message, the extension entry now attributes the direct-construction throw to the Index constructor message instead, and a schema test pins both substrings so the documented text cannot drift. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…heme The six bare throws this slice introduced convert per the error-consolidation rules, messages unchanged: the Index constructor invariants (missing name, columns-xor-expression, prefix/wire-name mismatch) throw ContractValidationError in the storage phase — they are reachable from contract JSON that passes structural validation; the wire-name prefix cap throws structuredError with the new registered code CONTRACT.WIRE_NAME_PREFIX_TOO_LONG (documented in the error reference, meta carries prefix and maxLength); the impossible-state guards (map+name both set at lowering, SqlIndexIR columns-xor- expression from derivation/introspection producers) throw InternalError. lint:throws back to delta 0. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ndexes its end contract declares The chain replay test caught the static op serializations still creating the pre-slice plain index names while the regenerated end snapshot carries the content-hash wire names — the migration did not produce its own declared end state. The four createIndex calls now use the wire names, and migrations:regen:examples re-serialized ops.json / migration.json (migrationHash moves). The other example chains are unaffected: retail-store and mongo-demo are Mongo-family migrations, and the postgis/pgvector/sqlite chains create no SQL indexes. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…thoring) Grounded against the slice-1 substrate: the PSL diagnostic-code union is framework-closed and PSL has no non-blocking diagnostic concept. The spec resolves both without leaking family vocabulary into framework — a contributed-code seam for the three PSL_INDEX_* diagnostics, and the D9 warning through the existing contract-warnings process.emitWarning path, which covers both surfaces via the shared builder. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…l-stack-missing error
TML-3091 converted every sibling toOp lowerer guard to
postgresError('MIGRATION.POSTGRES_CONTROL_STACK_MISSING', ...); the
rename call, added on this branch, follows the same convention.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
| /** The user-typed (or default-derived) prefix. Present iff managed. */ | ||
| readonly prefix?: string; |
There was a problem hiding this comment.
What does this mean? I'm aware of a hash suffic, but not a prefix. If the user provides one, i.e. "iff managed", does that mean they're determining the whole index name? this comment is confusing, as is the attribute name
| /** Column-tuple elements. Exactly one of `columns` / `expression` is set. */ | ||
| readonly columns?: readonly string[]; |
There was a problem hiding this comment.
If one or the other attribute is permissable, use a disjoint data structure
| /** | ||
| * Opaque SQL: the entire element list between the parens of CREATE INDEX — | ||
| * one string, never parsed. | ||
| */ | ||
| readonly expression?: string; | ||
| /** Opaque SQL: partial-index predicate (WHERE body, without the keyword). */ | ||
| readonly where?: string; |
There was a problem hiding this comment.
Are these genuine SQL attributes, or postgres-specific? Do these belong on the Postgres subclass?
There was a problem hiding this comment.
DON'T PUT IMPLEMENTATION IN THE DAMN EXPORTS DIRECTORY
| * The tuple order and encoding are a stability commitment with the same | ||
| * status as the RLS tuple: any change re-suffixes every wire name. | ||
| */ | ||
| export function computeIndexContentHash(parts: IndexContentHashParts): string { |
There was a problem hiding this comment.
This really needs to live on the index IR node. Subclasses need to be able to override this logic
There was a problem hiding this comment.
Why is any of this in standalone functions
| export interface IndexNode { | ||
| readonly columns: readonly string[]; | ||
| /** Exact physical name (PSL `map:`) — adopted verbatim, no wire hash. */ | ||
| readonly map?: string; |
There was a problem hiding this comment.
Please stop adding optional params. Make it non-optional but potentially undefined.
| const indexOptions = | ||
| i.options !== undefined ? `; readonly options: ${serializeValue(i.options)}` : ''; | ||
| return `{ readonly columns: readonly [${cols}]${name}${indexType}${indexOptions} }`; | ||
| return `{ ${name}${prefix}${cols}${expression}${where}${unique}${indexType}${indexOptions} }`; |
There was a problem hiding this comment.
seems like a good candidate for a join()
| step( | ||
| `create index "${indexName}"`, | ||
| `CREATE INDEX ${quoteIdentifier(indexName)} ON ${qualified}${using} (${columnList})${withClause}`, | ||
| `CREATE ${unique}INDEX ${quoteIdentifier(indexName)} ON ${qualified}${using} (${elementList})${withClause}${whereClause}`, |
There was a problem hiding this comment.
Convert to the contract-free AST, don't continue adding strings
| `rename index "${fromName}" to "${toName}"`, | ||
| `ALTER INDEX ${qualifyTableName(schemaName, fromName)} RENAME TO ${quoteIdentifier(toName)}`, |
There was a problem hiding this comment.
Here too. Use the proper ast
6765474 to
41418eb
Compare
…ts directory src/exports/naming.ts is a pure re-export of the public naming surface — including defaultIndexName, which does not inherit grandfathering — and the implementation lives in src/naming.ts. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…nion; internal inputs are required-but-undefined columns xor expression becomes an input-type union (IndexElements / SqlIndexElements) with NO stored discriminant — the JSON shape stays flat and the constructor xor throws stay as backstops for JSON loads that bypass the types. IndexInput and AuthoredIndexInput adopt the required-key | undefined convention their sibling SqlIndexIRInput already carried, so every internal construction site states absence explicitly (user-facing authoring options stay optional). The prefix docs now state the load-bearing fact at the field: its PRESENCE is the managed/exact naming-mode discriminator, the toolchain owns the managed physical name (name === formatWireName(prefix, hash)), and an exact name is owned entirely by the author. The class docs carry the family- placement rationale: expression/where/unique are genuine SQL-family attributes — SQLite's rejection is a capability decision, not evidence of target-specificity. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…on with named normalization seams
The planner's parallel indexContentEqual is deleted: SqlIndexIR owns the
single relation (contentEquals), and both comparers call it with their
mode-appropriate strictness — the differ with 'when-both-defined' column
presence (a column node meeting an expression node skips the tuple), the
rename content-pairing with 'matching' (a column index never pairs an
expression index); bodies compare 'verbatim' or 'ignored' per naming
mode. The target-specific normalizations are named functions the
comparison applies — normalizeIndexType (the btree default compares as
absent, fixing the authored type: 'btree' perpetual-drift hole) and
normalizeIndexOptionValue (boolean reloptions canonicalize to the
on/off spelling across JS booleans and the catalog's verbatim-stored
string spellings). The canonical form feeds the wire-name hash tuple,
the option equality, and the DDL renderer alike, so an authored
{ fastupdate: false } compares and hashes stably against a live index
created under any boolean spelling — pinned by unit matrices and an
author-migrate-verify-clean round trip. Wire hashes move only for
boolean-option indexes (no fixture carries one). Every pre-existing
rename-planner test stays green unmodified.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…nel; introspection reprints only expression indexes RenameIndexCall.renderTypeScript omits schema for the unbound namespace like its twelve siblings, so the sentinel no longer leaks into committed migration source (unbound render pinned by test). Index introspection bounds the per-element pg_get_indexdef reprint with a CASE on the indkey: expression-carrying indexes still reprint every position (the element list is one opaque string), while pure-column schemas skip the catalog reconstruction entirely. Introspection output is byte-identical. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…arrays joined once
The pk/uniques/indexes/foreignKeys literal builders in the d.ts emitter
accumulate their fragments in arrays and join('; ') instead of chaining
conditional string concatenation. Emitted fixtures are byte-identical
(fixtures:check clean).
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…nodes CREATE INDEX / ALTER INDEX RENAME / DROP INDEX join the structured DDL alphabet: PostgresCreateIndex, PostgresAlterIndexRename, and PostgresDropIndex nodes (with a DdlIndexElements column/expression union), contract-free builders, and adapter renderer visitor arms — the index op factories lower nodes instead of hand-concatenating SQL. Verbatim bodies (expression element lists, WHERE predicates) stay verbatim in the nodes; quoting and escaping live only in the adapter renderer. Byte-level DDL assertions move beside the renderer in the adapter package (same placement as the RLS DDL suite); target-package tests assert node shapes through a recording lowerer. Rendered DDL is byte-identical except boolean reloptions, which now print in the canonical on/off spelling shared with equality and the wire-name hash. The adapter renderer arms landed with the previous commit's control-adapter change (same file). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…nates Journey and integration tests are named for what they exercise (convergence via renames, exact-mode adoption round trip, twin-index verification) instead of planning scenario letters; comments that pointed at plan slices now state the product condition they wait on. The expression-index journey header names Cipherstash as the motivating integration. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Fallout of making columns/expression a disjoint input union: derivation and introspection sites (family contract-to-schema-ir, the Postgres tree re-stamp, adapter introspection) pick the arm from the data instead of passing both keys; the index DDL node and builder signatures take required-but-undefined schema/type/options/where; test fixtures drop explicit undefined arm keys or go through loose-input cast helpers. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…steps Index create steps now flow through the DDL lowerer, which stamps an explicit empty params array on the execute request; the SQL bytes are unchanged. The migration hash follows. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…s, issue regressions (229 accounted) Second porting wave for the port-all-tests project, +229 checklist boxes accounted (488 → 717). Ported & passing: - mongo composites/list + composites/object (withMongoPort): create/createMany/delete/deleteMany/findFirst/findMany/update/upsert - relationMode foreignKeys referential-action matrices: 1-to-1, 1-to-n, m-to-n (Cascade/NoAction/Restrict/SetNull; @Map + nomap), plus 17255 same/mixed-action disconnect - issue regressions: 5952 decimal, 14954 date, 21631 batching-in-tx (Promise.all forms), 29174 jsonb, 13089 dollar-in-search (mongo) Ported & failing (test.fails, faithful gap): - 29267 Uint8Array-in-JSON serialization; required-composite null; implicit default onUpdate (NoAction vs Prisma Cascade) - relation-mode-gh-m-to-n @Map variants: prisma-next rejects the mapped join-table auto-index name (>54-char prefix limit, #1047); Prisma truncates/hashes. Tracked as it.fails that flips green when fixed. Non-portable (recorded per-test in non-ported/): - relationLoadStrategy, relationMode=prisma emulation, implicit embedded mongo m2m, array-batch $transaction([...]), embedded-subfield select / composite where operators / read-side aggregate+count in mongo ORM Built against current main: fixtures emitted with the new indexes[].unique contract shape (#1047); count-only ORM terminals renamed to createAndCount/updateAndCount/deleteAndCount (#1044). Corpus: 69 files, 476 passed | 52 expected-fail, typecheck + lint clean. failing.md matches the test.fails markers; non-ported ledgers mirror functional/<suite>/. All dispositions reviewer-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…s, issue regressions (229 accounted) Second porting wave for the port-all-tests project, +229 checklist boxes accounted (488 → 717). Ported & passing: - mongo composites/list + composites/object (withMongoPort): create/createMany/delete/deleteMany/findFirst/findMany/update/upsert - relationMode foreignKeys referential-action matrices: 1-to-1, 1-to-n, m-to-n (Cascade/NoAction/Restrict/SetNull; @Map + nomap), plus 17255 same/mixed-action disconnect - issue regressions: 5952 decimal, 14954 date, 21631 batching-in-tx (Promise.all forms), 29174 jsonb, 13089 dollar-in-search (mongo) Ported & failing (test.fails, faithful gap): - 29267 Uint8Array-in-JSON serialization; required-composite null; implicit default onUpdate (NoAction vs Prisma Cascade) - relation-mode-gh-m-to-n @Map variants: prisma-next rejects the mapped join-table auto-index name (>54-char prefix limit, #1047); Prisma truncates/hashes. Tracked as it.fails that flips green when fixed. Non-portable (recorded per-test in non-ported/): - relationLoadStrategy, relationMode=prisma emulation, implicit embedded mongo m2m, array-batch $transaction([...]), embedded-subfield select / composite where operators / read-side aggregate+count in mongo ORM Built against current main: fixtures emitted with the new indexes[].unique contract shape (#1047); count-only ORM terminals renamed to createAndCount/updateAndCount/deleteAndCount (#1044). Corpus: 69 files, 476 passed | 52 expected-fail, typecheck + lint clean. failing.md matches the test.fails markers; non-ported ledgers mirror functional/<suite>/. All dispositions reviewer-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…s, issue regressions (229 accounted) Second porting wave for the port-all-tests project, +229 checklist boxes accounted (488 → 717). Ported & passing: - mongo composites/list + composites/object (withMongoPort): create/createMany/delete/deleteMany/findFirst/findMany/update/upsert - relationMode foreignKeys referential-action matrices: 1-to-1, 1-to-n, m-to-n (Cascade/NoAction/Restrict/SetNull; @Map + nomap), plus 17255 same/mixed-action disconnect - issue regressions: 5952 decimal, 14954 date, 21631 batching-in-tx (Promise.all forms), 29174 jsonb, 13089 dollar-in-search (mongo) Ported & failing (test.fails, faithful gap): - 29267 Uint8Array-in-JSON serialization; required-composite null; implicit default onUpdate (NoAction vs Prisma Cascade) - relation-mode-gh-m-to-n @Map variants: prisma-next rejects the mapped join-table auto-index name (>54-char prefix limit, #1047); Prisma truncates/hashes. Tracked as it.fails that flips green when fixed. Non-portable (recorded per-test in non-ported/): - relationLoadStrategy, relationMode=prisma emulation, implicit embedded mongo m2m, array-batch $transaction([...]), embedded-subfield select / composite where operators / read-side aggregate+count in mongo ORM Built against current main: fixtures emitted with the new indexes[].unique contract shape (#1047); count-only ORM terminals renamed to createAndCount/updateAndCount/deleteAndCount (#1044). Corpus: 69 files, 476 passed | 52 expected-fail, typecheck + lint clean. failing.md matches the test.fails markers; non-ported ledgers mirror functional/<suite>/. All dispositions reviewer-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…s, issue regressions (229 accounted) Second porting wave for the port-all-tests project, +229 checklist boxes accounted (488 → 717). Ported & passing: - mongo composites/list + composites/object (withMongoPort): create/createMany/delete/deleteMany/findFirst/findMany/update/upsert - relationMode foreignKeys referential-action matrices: 1-to-1, 1-to-n, m-to-n (Cascade/NoAction/Restrict/SetNull; @Map + nomap), plus 17255 same/mixed-action disconnect - issue regressions: 5952 decimal, 14954 date, 21631 batching-in-tx (Promise.all forms), 29174 jsonb, 13089 dollar-in-search (mongo) Ported & failing (test.fails, faithful gap): - 29267 Uint8Array-in-JSON serialization; required-composite null; implicit default onUpdate (NoAction vs Prisma Cascade) - relation-mode-gh-m-to-n @Map variants: prisma-next rejects the mapped join-table auto-index name (>54-char prefix limit, #1047); Prisma truncates/hashes. Tracked as it.fails that flips green when fixed. Non-portable (recorded per-test in non-ported/): - relationLoadStrategy, relationMode=prisma emulation, implicit embedded mongo m2m, array-batch $transaction([...]), embedded-subfield select / composite where operators / read-side aggregate+count in mongo ORM Built against current main: fixtures emitted with the new indexes[].unique contract shape (#1047); count-only ORM terminals renamed to createAndCount/updateAndCount/deleteAndCount (#1044). Corpus: 69 files, 476 passed | 52 expected-fail, typecheck + lint clean. failing.md matches the test.fails markers; non-ported ledgers mirror functional/<suite>/. All dispositions reviewer-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…s, issue regressions (229 accounted) Second porting wave for the port-all-tests project, +229 checklist boxes accounted (488 → 717). Ported & passing: - mongo composites/list + composites/object (withMongoPort): create/createMany/delete/deleteMany/findFirst/findMany/update/upsert - relationMode foreignKeys referential-action matrices: 1-to-1, 1-to-n, m-to-n (Cascade/NoAction/Restrict/SetNull; @Map + nomap), plus 17255 same/mixed-action disconnect - issue regressions: 5952 decimal, 14954 date, 21631 batching-in-tx (Promise.all forms), 29174 jsonb, 13089 dollar-in-search (mongo) Ported & failing (test.fails, faithful gap): - 29267 Uint8Array-in-JSON serialization; required-composite null; implicit default onUpdate (NoAction vs Prisma Cascade) - relation-mode-gh-m-to-n @Map variants: prisma-next rejects the mapped join-table auto-index name (>54-char prefix limit, #1047); Prisma truncates/hashes. Tracked as it.fails that flips green when fixed. Non-portable (recorded per-test in non-ported/): - relationLoadStrategy, relationMode=prisma emulation, implicit embedded mongo m2m, array-batch $transaction([...]), embedded-subfield select / composite where operators / read-side aggregate+count in mongo ORM Built against current main: fixtures emitted with the new indexes[].unique contract shape (#1047); count-only ORM terminals renamed to createAndCount/updateAndCount/deleteAndCount (#1044). Corpus: 69 files, 476 passed | 52 expected-fail, typecheck + lint clean. failing.md matches the test.fails markers; non-ported ledgers mirror functional/<suite>/. All dispositions reviewer-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…s, issue regressions (229 accounted) Second porting wave for the port-all-tests project, +229 checklist boxes accounted (488 → 717). Ported & passing: - mongo composites/list + composites/object (withMongoPort): create/createMany/delete/deleteMany/findFirst/findMany/update/upsert - relationMode foreignKeys referential-action matrices: 1-to-1, 1-to-n, m-to-n (Cascade/NoAction/Restrict/SetNull; @Map + nomap), plus 17255 same/mixed-action disconnect - issue regressions: 5952 decimal, 14954 date, 21631 batching-in-tx (Promise.all forms), 29174 jsonb, 13089 dollar-in-search (mongo) Ported & failing (test.fails, faithful gap): - 29267 Uint8Array-in-JSON serialization; required-composite null; implicit default onUpdate (NoAction vs Prisma Cascade) - relation-mode-gh-m-to-n @Map variants: prisma-next rejects the mapped join-table auto-index name (>54-char prefix limit, #1047); Prisma truncates/hashes. Tracked as it.fails that flips green when fixed. Non-portable (recorded per-test in non-ported/): - relationLoadStrategy, relationMode=prisma emulation, implicit embedded mongo m2m, array-batch $transaction([...]), embedded-subfield select / composite where operators / read-side aggregate+count in mongo ORM Built against current main: fixtures emitted with the new indexes[].unique contract shape (#1047); count-only ORM terminals renamed to createAndCount/updateAndCount/deleteAndCount (#1044). Corpus: 69 files, 476 passed | 52 expected-fail, typecheck + lint clean. failing.md matches the test.fails markers; non-ported ledgers mirror functional/<suite>/. All dispositions reviewer-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…s, issue regressions (229 accounted) Second porting wave for the port-all-tests project, +229 checklist boxes accounted (488 → 717). Ported & passing: - mongo composites/list + composites/object (withMongoPort): create/createMany/delete/deleteMany/findFirst/findMany/update/upsert - relationMode foreignKeys referential-action matrices: 1-to-1, 1-to-n, m-to-n (Cascade/NoAction/Restrict/SetNull; @Map + nomap), plus 17255 same/mixed-action disconnect - issue regressions: 5952 decimal, 14954 date, 21631 batching-in-tx (Promise.all forms), 29174 jsonb, 13089 dollar-in-search (mongo) Ported & failing (test.fails, faithful gap): - 29267 Uint8Array-in-JSON serialization; required-composite null; implicit default onUpdate (NoAction vs Prisma Cascade) - relation-mode-gh-m-to-n @Map variants: prisma-next rejects the mapped join-table auto-index name (>54-char prefix limit, #1047); Prisma truncates/hashes. Tracked as it.fails that flips green when fixed. Non-portable (recorded per-test in non-ported/): - relationLoadStrategy, relationMode=prisma emulation, implicit embedded mongo m2m, array-batch $transaction([...]), embedded-subfield select / composite where operators / read-side aggregate+count in mongo ORM Built against current main: fixtures emitted with the new indexes[].unique contract shape (#1047); count-only ORM terminals renamed to createAndCount/updateAndCount/deleteAndCount (#1044). Corpus: 69 files, 476 passed | 52 expected-fail, typecheck + lint clean. failing.md matches the test.fails markers; non-ported ledgers mirror functional/<suite>/. All dispositions reviewer-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
At a glance
A database migrated with today's toolchain carries
user_email_idx; this branch's re-emitted contract expectsuser_email_idx_46df9cad. The first widening plan is exactly:— renames only, no rebuilds,
db verifyclean after apply (byte-asserted e2e). That suffix is the point of this PR: every SQL index node is now name-identified, with the physical name carrying an 8-hex content hash so the schema differ can pair and verify indexes — including kinds it previously couldn't see at all.Decision
Slice 1 of projects/functional-indexes (design in #1046; slice spec: indexes-are-name-identified.spec.md). The identity rule (ADR 234, extended from RLS policies): compare by content where content is faithfully comparable; where Postgres reprints it — SQL bodies — the name carries the content hash and the name is the equivalence relation. Two modes, encoded structurally (no strategy enum):
prefixpresent):<prefix>_<8hex>, hash over[expression, where, columns, unique, type, options]. Structured attributes compared; bodies never compared.prefixabsent): verbatim physical name; structured attributes AND bodies compared byte-for-byte. This is the adoption mode —contract inferalready emitsmap: "<live name>", and reprint-vs-reprint byte comparison is stable.Constraint nodes — primary key, foreign key, unique, check — are outside the rule: a constraint is its own discrete entity (ADR 161 superseding note), fully structured, so content comparison is already exact.
UniqueConstraint/SqlUniqueIRare untouched.No new authoring parameters. The existing spellings adopt their end-state meanings: PSL
@@index(map:)= exact name (now actually verified — previously decorative), TSconstraints.index({name})= managed prefix, unnamed indexes get default-prefix wire names. The expression/where/unique authoring matrix, policy@@map, and full infer emission are slices 2–4.What lands here
@prisma-next/sql-schema-ir/naminggainsformatWireName/parseWireName/normalizeSqlBody/computeIndexContentHash; the RLSwire-name.tsmodule is deleted (call sites migrated; RLS wire names byte-identical, pinned by the untouched RLS suites).Indexreshape —namerequired,prefix?encodes the mode,columns?xorexpression?,where?,uniquerequired. All fixtures/example contracts re-emitted (storage hashes move — one sweep). Found and fixed in passing: canonicalization droppedunique: falseon emit (same class as the fix(sql-contract): preserve false literal column defaults through canonicalization #904 false-literal-defaults bug), so emitted contracts failed their own schema on re-read.SqlIndexIR.idderives from the name; the equivalence matrix is mode-selected; Postgres introspection captures expression elements (per-positionpg_get_indexdef) and partial predicates (pg_get_expr(indpred)), and the expression-skip and same-tuple-dedup hacks are deleted — live functional indexes stop being invisible, and same-tuple twins (legal in Postgres) are representable.CREATE [UNIQUE] INDEXrenders expression/where verbatim (never escaped, same stance as RLS predicates); newRenameIndexCall(ALTER INDEX … RENAME TO, widening class); the rename post-pass pairs indexes in two phases beside the untouched policy pass: hash pairing (prefix renames) and content pairing (exact→managed transition and the upgrade path).Breaking changes
Pre-RC, zero-semver window — this is the deliberate moment for the identity switch (the ADR draft's "why the wholesale switch" section). Upgrade entries are recorded for both audiences (
check:upgrade-coveragegreen):@prisma-next/sql-schema-ir/namingunder generalized names.One transitional state, documented in
CONTRACT-FIDELITY.md: full-fidelity introspection exposed that the Supabase reference contract had been adopting partial indexes without their predicates (verify passed only because bodies were never captured). Until slice 4 teachescontract inferto emitwhere:, partial indexes are omitted from adoption (tolerated extras under the external control policy). The regenerated contract also adopts a same-tuple twin the old dedup silently swallowed.Verification
Every dispatch landed with the full set green; final state: build 68/68 · typecheck 143/143 · Lint job surface (lint 82/82,
lint:depsclean,lint:castsdelta 0,lint:skillsclean,check:upgrade-coverage --mode prexit 0) ·fixtures:checkclean and idempotent ·test:packages0 failures ·test:integration247/247 files (1336 passed, incl. the three new journeys) ·test:e2e20/20 files (109 passed). Planner-op byte-identity is proven by the target/adapter suites and themigration plane2e journeys, notfixtures:check.Base
Stacked on
project/functional-indexes-shaping(#1046 — the project's spec/plan/ADR draft). Merge #1046 first or merge this with its base retargeted tomainonce #1046 lands.Skill update
Recorded in this PR:
skills/upgrade/prisma-next-upgrade/upgrades/0.16-to-0.17(indexes-are-name-identified) andskills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17(rls-wire-name-helpers-moved-to-sql-schema-ir-naming,sql-index-entities-are-name-identified).Checklist
git commit -s) per the DCO.🤖 Generated with Claude Code