Skip to content

Every SQL index is name-identified — wire names, expression/partial capture, rename convergence - #1047

Merged
wmadden-electric merged 30 commits into
mainfrom
slice/indexes-are-name-identified
Jul 24, 2026
Merged

Every SQL index is name-identified — wire names, expression/partial capture, rename convergence#1047
wmadden-electric merged 30 commits into
mainfrom
slice/indexes-are-name-identified

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

At a glance

A database migrated with today's toolchain carries user_email_idx; this branch's re-emitted contract expects user_email_idx_46df9cad. The first widening plan is exactly:

ALTER INDEX "public"."user_email_idx" RENAME TO "user_email_idx_46df9cad";
ALTER INDEX "public"."post_userId_idx" RENAME TO "post_userId_idx_a489d58a";

— renames only, no rebuilds, db verify clean 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):

  • Managed (prefix present): <prefix>_<8hex>, hash over [expression, where, columns, unique, type, options]. Structured attributes compared; bodies never compared.
  • Exact (prefix absent): verbatim physical name; structured attributes AND bodies compared byte-for-byte. This is the adoption mode — contract infer already emits map: "<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/SqlUniqueIR are untouched.

No new authoring parameters. The existing spellings adopt their end-state meanings: PSL @@index(map:) = exact name (now actually verified — previously decorative), TS constraints.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

  1. Shared naming module@prisma-next/sql-schema-ir/naming gains formatWireName/parseWireName/normalizeSqlBody/computeIndexContentHash; the RLS wire-name.ts module is deleted (call sites migrated; RLS wire names byte-identical, pinned by the untouched RLS suites).
  2. Contract Index reshapename required, prefix? encodes the mode, columns? xor expression?, where?, unique required. All fixtures/example contracts re-emitted (storage hashes move — one sweep). Found and fixed in passing: canonicalization dropped unique: false on 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.
  3. Name identity in the diff tree + full-fidelity introspectionSqlIndexIR.id derives from the name; the equivalence matrix is mode-selected; Postgres introspection captures expression elements (per-position pg_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.
  4. PlannerCREATE [UNIQUE] INDEX renders expression/where verbatim (never escaped, same stance as RLS predicates); new RenameIndexCall (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).
  5. Scenario acceptance — e2e journeys byte-assert: the upgrade plan above (scenario I), an expression + partial + unique-expression index migrating a fresh database and failing verify when dropped out-of-band, and the adoption round-trip (infer → emit → verify zero issues, plan zero ops). Same-tuple twins are covered at the introspection and verify layers.

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-coverage green):

  • Contract shape: 0.16 contracts fail 0.17 validation; re-emit. Storage hashes move.
  • Physical names: unnamed/TS-named/FK-backing indexes gain the hash suffix; existing databases converge via the renames-only widening plan. Under additive-only the pairing degrades to create-only; once the create has run, the old index can only leave via a destructive-allowed drop (the rename precheck requires its target absent) — renames happen only when a widening-allowed plan is the first convergence. Matches the policy-rename degradation.
  • Deleted exports: the RLS wire-name helpers moved to @prisma-next/sql-schema-ir/naming under 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 teaches contract infer to emit where:, 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:deps clean, lint:casts delta 0, lint:skills clean, check:upgrade-coverage --mode pr exit 0) · fixtures:check clean and idempotent · test:packages 0 failures · test:integration 247/247 files (1336 passed, incl. the three new journeys) · test:e2e 20/20 files (109 passed). Planner-op byte-identity is proven by the target/adapter suites and the migration plan e2e journeys, not fixtures: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 to main once #1046 lands.

Skill update

Recorded in this PR: skills/upgrade/prisma-next-upgrade/upgrades/0.16-to-0.17 (indexes-are-name-identified) and skills/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

  • All commits are signed off (git commit -s) per the DCO.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated.
  • The Skill update section is filled in.

🤖 Generated with Claude Code

@wmadden-electric
wmadden-electric requested a review from a team as a code owner July 24, 2026 00:58
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 157 files, which is 57 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

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 configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 783f3536-4e8e-4338-8d13-81ee4ba04b5b

📥 Commits

Reviewing files that changed from the base of the PR and between 89aa9c3 and 80606be.

⛔ Files ignored due to path filters (57)
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json is excluded by !**/generated/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • projects/functional-indexes/HANDOFF.md is excluded by !projects/**
  • projects/functional-indexes/plan.md is excluded by !projects/**
  • projects/functional-indexes/plans/expression-index-authoring.plan.md is excluded by !projects/**
  • projects/functional-indexes/spec.md is excluded by !projects/**
  • projects/functional-indexes/specs/adr-name-identified-indexes.md is excluded by !projects/**
  • projects/functional-indexes/specs/expression-index-authoring.spec.md is excluded by !projects/**
  • test/integration/test/namespaced-accessors/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/namespaced-accessors/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/blog-update/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/blog-update/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/extended-where/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/extended-where/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/filter-count-relations/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/filter-count-relations/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/issues-11974/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/issues-11974/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/issues-12378/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/issues-12378/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/issues-12557/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/issues-12557/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/issues-4004/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/issues-4004/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/legacy-aggregations/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/legacy-aggregations/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-count/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-count/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-createMany/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-createMany/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-createManyAndReturn/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-createManyAndReturn/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-findFirstOrThrow/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-findFirstOrThrow/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-findUniqueOrThrow/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-findUniqueOrThrow/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-updateManyAndReturn/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-updateManyAndReturn/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-upsert-native-atomic/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/methods-upsert-native-atomic/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/multi-schema/_fixture/different-names/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/multi-schema/_fixture/different-names/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/multi-schema/_fixture/identical-names/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/multi-schema/_fixture/identical-names/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/multi-schema/_fixture/no-map/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/multi-schema/_fixture/no-map/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/referential-actions-set-default-1to1/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/referential-actions-set-default-1to1/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/referential-actions-set-default-1ton/_fixture/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/ports/prisma/functional/referential-actions-set-default-1ton/_fixture/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/mn-psl/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/mn-psl/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.json is excluded by !**/generated/**
📒 Files selected for processing (159)
  • apps/telemetry-backend/src/prisma/contract.d.ts
  • apps/telemetry-backend/src/prisma/contract.json
  • docs/architecture docs/adrs/ADR 009 - Deterministic Naming Scheme.md
  • docs/architecture docs/adrs/ADR 210 - Index-type registry.md
  • docs/architecture docs/adrs/ADR 234 - Content-addressed wire names for Postgres-normalized objects.md
  • docs/architecture docs/adrs/ADR 235 - The schema differ walks two derived schema IRs.md
  • docs/architecture docs/subsystems/1. Data Contract.md
  • docs/architecture docs/subsystems/2. Contract Emitter & Types.md
  • docs/reference/error-reference.md
  • examples/paradedb-demo/src/prisma/contract.d.ts
  • examples/paradedb-demo/src/prisma/contract.json
  • examples/prisma-next-cloudflare-worker/src/prisma/contract.d.ts
  • examples/prisma-next-cloudflare-worker/src/prisma/contract.json
  • examples/prisma-next-demo-sqlite/src/prisma/contract.d.ts
  • examples/prisma-next-demo-sqlite/src/prisma/contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0720_initial/migration.json
  • examples/prisma-next-demo/migrations/app/20260422T0720_initial/migration.ts
  • examples/prisma-next-demo/migrations/app/20260422T0720_initial/ops.json
  • examples/prisma-next-demo/migrations/snapshots/6b917abed96371efb256becf8314e8bc72e91e7b4fc7dded7744b0e466580be4/contract.d.ts
  • examples/prisma-next-demo/migrations/snapshots/6b917abed96371efb256becf8314e8bc72e91e7b4fc7dded7744b0e466580be4/contract.json
  • examples/prisma-next-demo/src/prisma/contract.d.ts
  • examples/prisma-next-demo/src/prisma/contract.json
  • examples/react-router-demo/src/prisma/contract.d.ts
  • examples/react-router-demo/src/prisma/contract.json
  • packages/2-sql/1-core/contract/package.json
  • packages/2-sql/1-core/contract/src/canonicalization-hooks.ts
  • packages/2-sql/1-core/contract/src/exports/index-naming.ts
  • packages/2-sql/1-core/contract/src/factories.ts
  • packages/2-sql/1-core/contract/src/foreign-key-materialization.ts
  • packages/2-sql/1-core/contract/src/index-naming.ts
  • packages/2-sql/1-core/contract/src/index-type-validation.ts
  • packages/2-sql/1-core/contract/src/ir/sql-index.ts
  • packages/2-sql/1-core/contract/src/ir/storage-entry-schemas.ts
  • packages/2-sql/1-core/contract/src/validators.ts
  • packages/2-sql/1-core/contract/test/canonicalization-hooks.test.ts
  • packages/2-sql/1-core/contract/test/factories.test.ts
  • packages/2-sql/1-core/contract/test/sql-index.test.ts
  • packages/2-sql/1-core/contract/test/validators.test.ts
  • packages/2-sql/1-core/contract/tsdown.config.ts
  • packages/2-sql/1-core/schema-ir/README.md
  • packages/2-sql/1-core/schema-ir/src/exports/naming.ts
  • packages/2-sql/1-core/schema-ir/src/ir/sql-index-ir.ts
  • packages/2-sql/1-core/schema-ir/src/naming.ts
  • packages/2-sql/1-core/schema-ir/src/types.ts
  • packages/2-sql/1-core/schema-ir/test/naming.test.ts
  • packages/2-sql/1-core/schema-ir/test/sql-flat-tree-diffable.test.ts
  • packages/2-sql/1-core/schema-ir/test/sql-index-ir.test.ts
  • packages/2-sql/1-core/schema-ir/test/sql-schema-ir-node.test.ts
  • packages/2-sql/2-authoring/contract-psl/src/interpreter.ts
  • packages/2-sql/2-authoring/contract-psl/test/interpreter.index-naming.test.ts
  • packages/2-sql/2-authoring/contract-psl/test/interpreter.relations.test.ts
  • packages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.ts
  • packages/2-sql/2-authoring/contract-ts/src/build-contract.ts
  • packages/2-sql/2-authoring/contract-ts/src/contract-definition.ts
  • packages/2-sql/2-authoring/contract-ts/test/contract-builder.constraints.test.ts
  • packages/2-sql/2-authoring/contract-ts/test/contract-builder.contract-definition.test.ts
  • packages/2-sql/2-authoring/contract-ts/test/contract-builder.dsl.test.ts
  • packages/2-sql/2-authoring/contract-ts/test/contract-builder.index-naming.test.ts
  • packages/2-sql/2-authoring/contract-ts/test/contract-builder.normalization.test.ts
  • packages/2-sql/2-authoring/contract-ts/test/contract.integration.test.ts
  • packages/2-sql/2-authoring/contract-ts/test/contract.logic.test.ts
  • packages/2-sql/3-tooling/emitter/src/index.ts
  • packages/2-sql/3-tooling/emitter/test/emitter-hook.foreign-keys.test.ts
  • packages/2-sql/3-tooling/emitter/test/emitter-hook.generation.basic.test.ts
  • packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts
  • packages/2-sql/9-family/src/core/control-instance.ts
  • packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts
  • packages/2-sql/9-family/src/core/psl-contract-infer/relation-inference.ts
  • packages/2-sql/9-family/test/contract-to-schema-ir.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/relation-inference.test.ts
  • packages/2-sql/9-family/test/schema-verify.classify.test.ts
  • packages/2-sql/9-family/test/schema-verify.helpers.ts
  • packages/2-sql/9-family/test/schema-verify.verdict.test.ts
  • packages/3-extensions/pgvector/test/migrations/planner.behavior.test.ts
  • packages/3-extensions/pgvector/test/migrations/planner.contract-to-schema-ir.test.ts
  • packages/3-extensions/postgres/package.json
  • packages/3-extensions/postgres/test/contract-builder/rls-entities.test.ts
  • packages/3-extensions/supabase/src/contract/CONTRACT-FIDELITY.md
  • packages/3-extensions/supabase/src/contract/contract.d.ts
  • packages/3-extensions/supabase/src/contract/contract.json
  • packages/3-extensions/supabase/src/contract/contract.prisma
  • packages/3-targets/3-targets/postgres/src/contract-free/ddl.ts
  • packages/3-targets/3-targets/postgres/src/core/authoring.ts
  • packages/3-targets/3-targets/postgres/src/core/ddl/nodes.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/contract-to-postgres-database-schema-node.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/issue-planner.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/op-factory-call.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/indexes.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-schema-lookup.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/postgres-migration.ts
  • packages/3-targets/3-targets/postgres/src/core/psl-infer/infer-psl-contract.ts
  • packages/3-targets/3-targets/postgres/src/core/rls/canonicalize.ts
  • packages/3-targets/3-targets/postgres/src/core/rls/wire-name.ts
  • packages/3-targets/3-targets/postgres/src/exports/ddl.ts
  • packages/3-targets/3-targets/postgres/src/exports/errors.ts
  • packages/3-targets/3-targets/postgres/src/exports/op-factory-call.ts
  • packages/3-targets/3-targets/postgres/src/exports/rls-canonicalize.ts
  • packages/3-targets/3-targets/postgres/test/fixtures/namespaced-contract.d.ts
  • packages/3-targets/3-targets/postgres/test/fixtures/namespaced-contract.json
  • packages/3-targets/3-targets/postgres/test/migrations/index-ddl.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/index-rename-planner.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/node-issue-planner.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.lowering.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/render-typescript.test.ts
  • packages/3-targets/3-targets/postgres/test/postgres-table-schema-node.test.ts
  • packages/3-targets/3-targets/postgres/test/psl-infer/infer-psl-contract.test.ts
  • packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.naming-and-constraints.test.ts
  • packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.relations.test.ts
  • packages/3-targets/3-targets/postgres/test/rls-canonicalize.test.ts
  • packages/3-targets/3-targets/postgres/test/rls-wire-name.test.ts
  • packages/3-targets/3-targets/sqlite/src/core/migrations/issue-planner.ts
  • packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts
  • packages/3-targets/3-targets/sqlite/test/migrations/issue-planner.test.ts
  • packages/3-targets/3-targets/sqlite/test/migrations/node-issue-helpers.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/postgres/test/control-adapter.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/fixtures/runner-fixtures.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/index-boolean-options.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/index-ddl-rendering.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/index-introspection.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/index-twins-verify.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/introspect-select-only-privileges.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/op-factory-call.lowering.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/op-factory-call.rendering.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/planner-schema-lookup.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/planner.reconciliation.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/planner.unique-index-structural.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/render-typescript.roundtrip.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/rls-collect-extension-issues.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/rls-introspection.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/rls-verify-extension-issues.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/rls-walking-skeleton.integration.test.ts
  • packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/migrations/db-init-update.cli.test.ts
  • packages/3-targets/6-adapters/sqlite/test/migrations/fixtures/runner-fixtures.ts
  • packages/3-targets/6-adapters/sqlite/test/migrations/planner-introspection.integration.test.ts
  • packages/3-targets/6-adapters/sqlite/test/migrations/planner.test.ts
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/instructions.md
  • skills/upgrade/prisma-next-upgrade/upgrades/0.16-to-0.17/instructions.md
  • test/e2e/framework/test/sqlite/migrations/additive.test.ts
  • test/e2e/framework/test/sqlite/migrations/destructive.test.ts
  • test/e2e/framework/test/sqlite/migrations/fk-preservation.test.ts
  • test/integration/test/authoring/paradedb-bm25-narrowing.test.ts
  • test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json
  • test/integration/test/authoring/parity/core-surface/expected.contract.json
  • test/integration/test/authoring/parity/map-attributes/expected.contract.json
  • test/integration/test/authoring/parity/relation-backrelation-list/expected.contract.json
  • test/integration/test/authoring/side-by-side/postgres/contract.json
  • test/integration/test/cli-journeys/expression-index-migration.e2e.test.ts
  • test/integration/test/cli-journeys/index-name-convergence.e2e.test.ts
  • test/integration/test/cli.db-update.preflight-gaps.e2e.test.ts
  • test/integration/test/cross-package/postgres-issue-planner.test.ts
  • test/integration/test/family.schema-verify.basic.integration.test.ts
  • test/integration/test/family.schema-verify.basic.test.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/cli-journeys/contract-expression-indexes.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/cli-journeys/contract-index-upgrade.prisma
  • test/integration/test/referential-actions.integration.test.ts
  • test/integration/test/utils/journey-test-helpers.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slice/indexes-are-name-identified

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 165.89 KB (+0.92% 🔺)
postgres / emit 147.42 KB (+0.71% 🔺)
mongo / no-emit 100.26 KB (0%)
mongo / emit 90 KB (0%)
cf-worker / no-emit 191.55 KB (+0.77% 🔺)
cf-worker / emit 171.15 KB (+0.64% 🔺)

@pkg-pr-new

pkg-pr-new Bot commented Jul 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@1047

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@1047

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@1047

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@1047

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@1047

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@1047

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@1047

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@1047

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@1047

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@1047

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@1047

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@1047

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@1047

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@1047

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@1047

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@1047

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@1047

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@1047

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@1047

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@1047

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@1047

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@1047

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@1047

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@1047

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@1047

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@1047

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@1047

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@1047

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@1047

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@1047

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@1047

@prisma-next/config-loader

npm i https://pkg.pr.new/@prisma-next/config-loader@1047

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@1047

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@1047

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@1047

prisma-next

npm i https://pkg.pr.new/prisma-next@1047

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@1047

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@1047

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@1047

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@1047

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@1047

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@1047

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@1047

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@1047

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@1047

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@1047

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@1047

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@1047

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@1047

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@1047

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@1047

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@1047

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@1047

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@1047

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@1047

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@1047

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@1047

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@1047

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@1047

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@1047

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@1047

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@1047

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@1047

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@1047

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@1047

commit: 80606be

… 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>
Comment on lines +9 to +10
/** The user-typed (or default-derived) prefix. Present iff managed. */
readonly prefix?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +11 to +12
/** Column-tuple elements. Exactly one of `columns` / `expression` is set. */
readonly columns?: readonly string[];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If one or the other attribute is permissable, use a disjoint data structure

Comment on lines +13 to +19
/**
* 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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these genuine SQL attributes, or postgres-specific? Do these belong on the Postgres subclass?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really needs to live on the index IR node. Subclasses need to be able to override this logic

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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} }`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convert to the contract-free AST, don't continue adding strings

Comment on lines +119 to +120
`rename index "${fromName}" to "${toName}"`,
`ALTER INDEX ${qualifyTableName(schemaName, fromName)} RENAME TO ${quoteIdentifier(toName)}`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too. Use the proper ast

@wmadden-electric
wmadden-electric force-pushed the slice/indexes-are-name-identified branch from 6765474 to 41418eb Compare July 24, 2026 09:02

@wmadden wmadden left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preemptively approved

…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>
@wmadden-electric
wmadden-electric added this pull request to the merge queue Jul 24, 2026
Merged via the queue into main with commit cf0ab1c Jul 24, 2026
22 checks passed
@wmadden-electric
wmadden-electric deleted the slice/indexes-are-name-identified branch July 24, 2026 12:28
SevInf added a commit that referenced this pull request Jul 24, 2026
…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>
SevInf added a commit that referenced this pull request Jul 27, 2026
…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>
SevInf added a commit that referenced this pull request Jul 27, 2026
…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>
SevInf added a commit that referenced this pull request Jul 27, 2026
…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>
SevInf added a commit that referenced this pull request Jul 27, 2026
…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>
SevInf added a commit that referenced this pull request Jul 27, 2026
…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>
SevInf added a commit that referenced this pull request Jul 27, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants