Skip to content

TML-3086: bind @db.Date to pg/date@1; infer 1:1 from unique indexes - #1038

Merged
wmadden-electric merged 6 commits into
mainfrom
worktree/0-16-infer-field-report-fixes-f04c9b
Jul 23, 2026
Merged

TML-3086: bind @db.Date to pg/date@1; infer 1:1 from unique indexes#1038
wmadden-electric merged 6 commits into
mainfrom
worktree/0-16-infer-field-report-fixes-f04c9b

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Refs TML-3086. Round 2 of the external field report behind TML-3037 / #1011. The reporter's remaining feature ask (relation-generation opt-out) is deferred to TML-3087.

At a glance

// test/integration/test/infer-roundtrip-runtime.integration.test.ts
const rows = await records.select('id', 'notedOn').all();
expect(rows).toEqual([{ id: 1, notedOn: new Date(Date.UTC(2024, 0, 15)) }]);

const includeResult = await owners
  .select('id')
  .include('records' as never, (record) => record.select('notedOn'))
  .all();
// `pg/date@1.decodeJson` accepts the bare `YYYY-MM-DD` that `json_agg` renders.
expect(includeResult).toEqual([
  { id: 1, records: [{ notedOn: new Date(Date.UTC(2024, 0, 15)) }] },
]);

Before this PR the include() call threw at decode time: @db.Date columns carried no codec binding and inherited pg/timestamptz@1, whose decodeJson rejects the bare YYYY-MM-DD that json_agg renders. The reporter types dates as String in production to survive this.

Decision

Two defects from the re-verified v0.16.0 field report, one slice:

  1. @db.Date (and bare Date) now bind pg/date@1. A deliberate interim, decided 2026-07-23: TML-2987 (infer prints bare types) is unscheduled and the runtime failure is hurting a production user now. The binding is deleted together with @db.* in TML-2988. Breaking: re-emitting a contract with date columns changes the column's codec ref and the contract hash (previously emitted contracts keep working — pg/timestamptz@1 still exists). Upgrade instructions recorded.
  2. contract infer detects a 1:1 enforced by a bare CREATE UNIQUE INDEX. Previously only UNIQUE constraints and primary keys counted, so index-enforced 1:1s inferred as list back-relations. Partial unique indexes prove nothing and are excluded, which required introspection to start reading pg_index.indpred.

Reviewer notes

  • Known gap, needs a follow-up decision: the newly inferred ? back-relation does not survive contract emit. A bare unique index has no PSL representation (the printer emits @unique/@@unique from table.uniques only), so the interpreter rejects the printed PSL with PSL_NON_UNIQUE_BACKRELATION. Resolving it means either printing @unique (declares a constraint the live DB doesn't have → verify drift) or teaching printer+interpreter about unique indexes — both beyond this slice's scope. The new e2e block pins the printed PSL and carries a comment recording the gap; likely bundled with TML-3087.
  • The bare-Date rebind looks like scope creep but isn't: the parity tests assert @db.Date and the bare Date constructor produce identical codec bindings, so rebinding one spelling alone is impossible. Both must pin pg/date@1 (coordinates with TML-2986, mid-flight in the Remove-@db.* project).
  • Index partiality deliberately stays out of differ/verify. SqlIndexIR.partial uses the same non-enumerable pattern as dependsOn: absent from JSON, structural equality, and drift detection. Surfacing partial-index drift is explicitly out of scope; pnpm fixtures:check confirms zero fixture churn.
  • Pre-existing, unchanged: when a total and a partial unique index share the same column tuple, the adapter's one-index-per-tuple dedup tie-breaks by name, so the partial one can shadow the total and the 1:1 is missed. Noted for completeness.

How it fits together

  1. Bind the codec at authoring. The db.Date entry in NATIVE_TYPE_SPECS goes from codecId: null (inherit from base type) to 'pg/date@1' (psl-column-resolution.ts); the bare Date constructor in the postgres adapter's authoring defaults follows (control-mutation-defaults.ts); the postgres target's codecDescriptorMap gains the missing date key (codec-type-map.ts).
  2. Teach introspection about partial indexes. The adapter's pg_index query now selects ix.indpred IS NOT NULL AS indpartial (control-adapter.ts), stamped onto the IR only when true.
  3. Carry partiality on the IR without changing its wire shape. SqlIndexIR.partial is declare readonly + defineNonEnumerable — the exact dependsOn pattern (sql-index-ir.ts).
  4. Use it in relation inference. detectOneToOne adds a third arm: an exact, order-insensitive column-set match against a unique, non-partial entry in table.indexes (relation-inference.ts).
  5. Record the upgrade. Prose entries under both 0.16-to-0.17 upgrade skills describe the codec-ref/hash change, the UTC-midnight canonical value, and that old contracts keep working; pnpm check:upgrade-coverage enforces coverage.

Behavior changes & evidence

Testing performed

On final HEAD (36a8015), full CI gate set:

  • pnpm build (68/68), pnpm typecheck:packages + pnpm typecheck:examples (green; typecheck --force hits a pre-existing Turbo build/typecheck scheduling race in packages untouched here — CI doesn't use --force)
  • Full Lint job (all steps incl. lint:casts, lint:deps, check:upgrade-coverage --mode pr) — green
  • pnpm fixtures:check — zero churn
  • pnpm test:packages (13499 passed), pnpm test:integration (1173/1173), pnpm test:e2e (109/109). Four timeout flakes under full-suite load (emitter, adapter render-roundtrip, two integration afterAll cleanups) each pass solo and touch files outside this diff.

Skill update

Upgrade skills updated: postgres-date-rebound-to-pg-date entries appended to skills/upgrade/prisma-next-upgrade/upgrades/0.16-to-0.17/instructions.md and skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/instructions.md. No CLI/API surface changed otherwise.

Follow-ups

  • TML-3087 — relation-generation exclusion via dotted-path config globs; the infer→emit gap for unique-index-backed 1:1s (reviewer note above) likely rides along.
  • Heads-up comments posted on TML-2986/TML-2988 so the Remove-@db.* project keeps both spellings pinned to pg/date@1.

Alternatives considered

  • Keep @db.Date unbound until the Remove-@db.* project lands (the TML-3037 disposition). Rejected 2026-07-23: that project's bare-types step (TML-2987) is unscheduled while a production user works around a runtime decode failure today. The interim binding dies with @db.* in TML-2988.
  • Make pg/timestamptz@1.decodeJson tolerate bare YYYY-MM-DD. Rejected: it would paper over a wrong codec assignment and change timestamptz semantics for everyone; a date column deserves the date codec.
  • Represent partiality as a serialized SqlIndexIR field. Rejected: it would change contract JSON, structural equality, and differ/verify semantics for a property nothing downstream consumes yet. The non-enumerable pattern already exists (dependsOn) for exactly this shape of need.
  • Also emit @unique for index-backed 1:1s so infer output round-trips through emit. Rejected for this slice: it would declare a constraint the live database doesn't have and surface as verify drift. Needs its own decision (see reviewer notes).

Checklist

  • All commits are signed off (git commit -s) per the DCO. The DCO status check will block merge if any commit is missing a Signed-off-by: trailer.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated (or n/a if the change is doc-only / refactor with no behavioural delta).
  • The PR title is in TML-NNNN: <sentence-case title> form (Linear ticket prefix + concise title naming the concrete deliverable). See .claude/skills/create-pr/SKILL.md for the full convention.
  • The Skill update section above is filled in (or stated n/a — internal only).
Slice spec (orphan slice — reconstructed contract)

Round 2 of the external field report (reporter "Eugen") that produced TML-3037 / #1011. Re-verified on v0.16.0: two defects remain in the infer → emit → runtime pipeline. The relation-generation opt-out is deferred to TML-3087.

Bind @db.Datepg/date@1. @db.Date carried codecId: null, so columns inherited pg/timestamptz@1, whose decodeJson rejects the bare YYYY-MM-DD strings that json_agg renders — .include() decode failed at runtime. Change the NATIVE_TYPE_SPECS entry, add the missing date key to the postgres target's codecDescriptorMap, flip the pinned red assertion in the infer-roundtrip runtime integration test, update interpreter/print-psl pins, and record upgrade instructions (breaking: codec ref + contract hash change on re-emit; old contracts keep working; no in-repo emitted fixture has a date column). Decision context: deliberately overrides TML-3037's recorded deferral (Will, 2026-07-23); deleted with @db.* in TML-2988; both spellings (@db.Date, bare Date) must pin pg/date@1.

detectOneToOne accepts total unique indexes. A 1:1 enforced by a bare CREATE UNIQUE INDEX inferred as a list back-relation because detection consulted only primaryKey + uniques. Introspection first learns partiality (pg_index.indpred); partiality is carried on SqlIndexIR as a first-class optional field excluded from structural equality and JSON (the dependsOn non-enumerable pattern) so differ/verify semantics do not change; detection then accepts an exact column-set match against a total unique index. Tests: total unique index → ?; partial → []; expression index excluded.

Close-out. Full CI gate set; sweep test/integration + test/e2e for stale assertions; PR with decision-led narrative; heads-up comments on TML-2986/TML-2988.

Summary by CodeRabbit

  • New Features

    • PostgreSQL and SQLite introspection now identifies partial indexes.
    • Relation inference recognizes exact matches to unique, non-partial indexes as one-to-one relationships.
    • PostgreSQL date columns now use the dedicated pg/date@1 codec.
    • Date values decode consistently, including through related-record queries.
  • Bug Fixes

    • Corrected PostgreSQL date mappings and improved migration handling for partial indexes.
  • Documentation

    • Added upgrade guidance for PostgreSQL date codec changes and related runtime behavior.

@wmadden-electric
wmadden-electric requested a review from a team as a code owner July 23, 2026 11:46
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@wmadden-electric, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c7973db-30f7-472f-a7e1-499fe6eb40f0

📥 Commits

Reviewing files that changed from the base of the PR and between cc37fbc and 89abff1.

📒 Files selected for processing (36)
  • packages/2-sql/1-core/schema-ir/src/ir/sql-index-ir.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/psl-column-resolution.ts
  • packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.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/3-extensions/pgvector/test/migrations/planner.behavior.test.ts
  • packages/3-extensions/postgres/test/native-type-parity.test.ts
  • packages/3-extensions/postgres/test/scalar-type-parity.test.ts
  • packages/3-targets/3-targets/postgres/src/core/codec-type-map.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/contract-to-postgres-database-schema-node.ts
  • packages/3-targets/3-targets/postgres/test/migrations/node-issue-planner.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/sqlite/test/migrations/node-issue-helpers.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-mutation-defaults.ts
  • packages/3-targets/6-adapters/postgres/test/control-adapter.test.ts
  • packages/3-targets/6-adapters/postgres/test/control-mutation-defaults.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/index-introspection.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/planner-schema-lookup.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/planner.unique-index-structural.test.ts
  • packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/control-adapter.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/integration/test/cli-journeys/infer-roundtrip-fidelity.e2e.test.ts
  • test/integration/test/infer-roundtrip-runtime.integration.test.ts
📝 Walkthrough

Walkthrough

The change adds explicit partial-index metadata across SQL IR, database introspection, migration planning, and relation inference. PostgreSQL date mappings now use pg/date@1, with corresponding runtime tests and upgrade guidance.

Changes

Schema metadata and date codec updates

Layer / File(s) Summary
Partial-index IR and relation inference
packages/2-sql/1-core/schema-ir/**, packages/2-sql/9-family/src/core/**, packages/2-sql/9-family/test/**, test/integration/test/cli-journeys/**
SqlIndexIR now requires a non-enumerable partial flag, and one-to-one inference accepts exact matches against unique, non-partial indexes.
Adapter propagation and validation
packages/3-targets/6-adapters/{postgres,sqlite}/**, packages/3-targets/3-targets/postgres/**, packages/3-extensions/pgvector/**
PostgreSQL and SQLite introspection propagate partial-index metadata, while migration, planner, and adapter fixtures use explicit non-partial values.
PostgreSQL date codec rebinding
packages/2-sql/2-authoring/contract-psl/**, packages/3-targets/3-targets/postgres/**, packages/3-targets/6-adapters/postgres/**, packages/3-extensions/postgres/**, test/integration/test/infer-roundtrip-runtime.integration.test.ts, skills/**
PostgreSQL date mappings use pg/date@1; parity tests, runtime decoding assertions, and 0.16-to-0.17 upgrade instructions reflect the new binding.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Database
  participant ControlAdapter
  participant SqlIndexIR
  participant RelationInference
  Database->>ControlAdapter: expose partial-index metadata
  ControlAdapter->>SqlIndexIR: construct index with partial flag
  SqlIndexIR->>RelationInference: provide index columns and uniqueness
  RelationInference-->>Database: infer singular or list back-relation
Loading
sequenceDiagram
  participant PSL
  participant DateResolver
  participant PostgresCodecMap
  participant Runtime
  PSL->>DateResolver: resolve Date or `@db.Date`
  DateResolver->>PostgresCodecMap: select pg/date@1
  PostgresCodecMap->>Runtime: decode date columns
  Runtime-->>PSL: return UTC-midnight Date values
Loading

Possibly related PRs

  • prisma/prisma-next#1015: Updates one-to-one back-relation inference around exact foreign-key and unique-column matching.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the two main changes: date codec rebinding and 1:1 inference from unique indexes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree/0-16-infer-field-report-fixes-f04c9b

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.

@pkg-pr-new

pkg-pr-new Bot commented Jul 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/config-loader

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

@prisma-next/emitter

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

@prisma-next/language-server

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 89abff1

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 163.84 KB (+0.03% 🔺)
postgres / emit 145.96 KB (+0.03% 🔺)
mongo / no-emit 100.14 KB (0%)
mongo / emit 89.85 KB (0%)
cf-worker / no-emit 189.44 KB (+0.03% 🔺)
cf-worker / emit 169.57 KB (+0.03% 🔺)

Comment thread packages/2-sql/1-core/schema-ir/src/ir/sql-index-ir.ts Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts (1)

1187-1207: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prefer total indexes when deduplicating identical column tuples.

If a table has both a total unique index and a partial unique index on the same columns, the current tie-breaker may retain the partial one based on its name. The downstream relation inference then cannot see the qualifying non-partial unique index and may infer a 1:N relation instead of 1:1. Prefer partial: false before the name tie-breaker and add a regression fixture covering both indexes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts` around
lines 1187 - 1207, Update the bestByColumnTuple selection in the
survivingIndexes deduplication loop to prefer total indexes (partial: false)
over partial indexes before comparing names, while preserving the existing
uniqueness preference and deterministic name tie-breaker. Add a regression
fixture covering identical-column total and partial unique indexes and verify
relation inference remains 1:1.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/instructions.md`:
- Around line 207-211: Replace broad Date substring matching in both upgrade
instruction registries with precise date-column detection: in
skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/instructions.md
lines 207-211, match affected contract/PSL declarations or codec references; in
skills/upgrade/prisma-next-upgrade/upgrades/0.16-to-0.17/instructions.md lines
193-198, ensure DateTime fields match only when annotated with `@db.Date`.

In `@test/integration/test/cli-journeys/infer-roundtrip-fidelity.e2e.test.ts`:
- Around line 416-418: Update the ticket reference in the comment near the
inferred singular back-relation explanation from TML-3086 to TML-3087, leaving
the surrounding rationale unchanged.

---

Outside diff comments:
In `@packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts`:
- Around line 1187-1207: Update the bestByColumnTuple selection in the
survivingIndexes deduplication loop to prefer total indexes (partial: false)
over partial indexes before comparing names, while preserving the existing
uniqueness preference and deterministic name tie-breaker. Add a regression
fixture covering identical-column total and partial unique indexes and verify
relation inference remains 1:1.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 62145add-8b48-41d9-9bb9-4ce281518924

📥 Commits

Reviewing files that changed from the base of the PR and between cdd7463 and cc37fbc.

📒 Files selected for processing (36)
  • packages/2-sql/1-core/schema-ir/src/ir/sql-index-ir.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/psl-column-resolution.ts
  • packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.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/3-extensions/pgvector/test/migrations/planner.behavior.test.ts
  • packages/3-extensions/postgres/test/native-type-parity.test.ts
  • packages/3-extensions/postgres/test/scalar-type-parity.test.ts
  • packages/3-targets/3-targets/postgres/src/core/codec-type-map.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/contract-to-postgres-database-schema-node.ts
  • packages/3-targets/3-targets/postgres/test/migrations/node-issue-planner.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/sqlite/test/migrations/node-issue-helpers.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-mutation-defaults.ts
  • packages/3-targets/6-adapters/postgres/test/control-adapter.test.ts
  • packages/3-targets/6-adapters/postgres/test/control-mutation-defaults.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/index-introspection.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/planner-schema-lookup.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/planner.unique-index-structural.test.ts
  • packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/control-adapter.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/integration/test/cli-journeys/infer-roundtrip-fidelity.e2e.test.ts
  • test/integration/test/infer-roundtrip-runtime.integration.test.ts

@wmadden-electric
wmadden-electric added this pull request to the merge queue Jul 23, 2026
@wmadden
wmadden removed this pull request from the merge queue due to a manual request Jul 23, 2026
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Re the outside-diff CodeRabbit finding on control-adapter.ts (~1187–1207, prefer total over partial in the same-column-tuple dedup): this is pre-existing behavior on main, unchanged by this PR, and already called out in the reviewer notes ("partial can shadow the total under the name tie-breaker, so the 1:1 is missed"). Deliberately out of scope for this slice; it's a candidate follow-up alongside the infer→emit gap tracked on TML-3086.

… works

Both spellings — legacy `DateTime @db.Date` (NATIVE_TYPE_SPECS) and the bare
`Date` type constructor (postgresNativeAuthoringTypes) — previously inherited
`pg/timestamptz@1`, whose decodeJson rejects the bare YYYY-MM-DD strings that
json_agg renders, so relation include() over a date column failed with
RUNTIME.DECODE_FAILED. Bind both to the dedicated pg/date@1 codec and add the
missing `date` key to the postgres codec descriptor map.

Breaking: re-emitted contracts with date columns change codec ref and storage
hash; previously emitted contracts keep working (pg/timestamptz@1 still
exists). Upgrade instructions recorded in both 0.16-to-0.17 skill packages.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
A one-to-one enforced by a bare CREATE UNIQUE INDEX (rather than a
UNIQUE constraint) inferred as a list back-relation because
detectOneToOne consulted only the primary key and unique constraints.
It now also accepts an exact, order-insensitive column-set match
against a unique index — but only a total one: a partial unique index
does not guarantee at-most-one child per parent, so introspection now
reads pg_index.indpred and carries partiality on SqlIndexIR as a
non-enumerable field excluded from structural equality and JSON
(same treatment as dependsOn), leaving differ/verify semantics and
serialized schemas unchanged. Expression unique indexes never survive
introspection with a usable column set, so they keep a list
back-relation; that behavior is pinned end-to-end.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…-unique-index journey skips emit (TML-3086)

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…y (TML-3086)

An optional partial field silently read as "total" when absent, which hid
the sqlite adapter never reporting partiality at all. Every producer now
states it: the postgres adapter stamps the indpred-derived boolean
unconditionally, the sqlite adapter reads the PRAGMA index_list partial
column, and contract-derived indexes stamp partial: false (PSL cannot
express partial indexes). The field stays non-enumerable — out of JSON,
Object.keys, and isEqualTo.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…t omitted (TML-3086)

name/type/options/annotations/dependsOn keep undefined-able values but
become required keys, so every construction site says name: undefined
rather than silently dropping the key. Instance shape is unchanged:
the constructor still assigns enumerable properties only for defined
values and keeps dependsOn/partial non-enumerable, so JSON output,
Object.keys, and isEqualTo are byte-identical. ifDefined spreads at
producer sites collapse to direct key assignments.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ate matches

The contains ["@db.Date", "Date"] predicate substring-matched every
DateTime field in .prisma files and ordinary Date usage in TS files,
so the detection stopped filtering at all. Replace it with regex
predicates that match the @db.Date attribute and the bare PSL Date
type token at a word boundary, which cannot occur inside DateTime.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric
wmadden-electric force-pushed the worktree/0-16-infer-field-report-fixes-f04c9b branch from 128b6c7 to 89abff1 Compare July 23, 2026 13:20
@wmadden-electric
wmadden-electric added this pull request to the merge queue Jul 23, 2026
Merged via the queue into main with commit 418da10 Jul 23, 2026
22 checks passed
@wmadden-electric
wmadden-electric deleted the worktree/0-16-infer-field-report-fixes-f04c9b branch July 23, 2026 13:47
wmadden-electric added a commit that referenced this pull request Jul 23, 2026
…us (#1035) + date-emission regen (#1038)

Merges 2 more origin/main commits on top of the prior merge (9b99660):
#1038 (TML-3086) binds @db.Date to pg/date@1 and infers 1:1 from unique
indexes, re-churning date-column contract hashes; #1035 ports the
488-case prisma test corpus under test/integration/test/ports/**, whose
fixture configs and generated contracts still carry this branch's
pre-rename keys since they were authored against pre-rename main.

The merge itself was conflict-free (git auto-resolved cleanly): the
two 0.16-to-0.17 upgrade-instruction files each gained one new sibling
entry from #1038 (postgres-date-rebound-to-pg-date) alongside the
entries this branch and main already both carried from the prior
merge — verified all entries from both sides survived. The corpus
key-rename and hash regeneration for #1038's date-column change land
in follow-up commits on top of this one.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric added a commit that referenced this pull request Jul 23, 2026
… fixtures

The 488-case prisma test corpus (#1035) was authored against pre-rename
main, so its 43 _fixture/prisma-next.config.ts sugar configs and shared
Postgres harness still used outputPath/extensionPacks. Renames outputPath
to output across all 43 configs plus this corpus's own README (which
documented the old key for contributors), and extensionPacks to
extensions in the one hand-written harness that builds a control stack
directly (_harness/postgres.ts) — the corpus's other 87 extensionPacks
occurrences were all in the checked-in generated contract.json/contract.d.ts
pairs, regenerated below rather than hand-edited.

Regenerated all 43 fixture pairs via the corpus's documented `contract
emit --config <fixture>` command (not covered by fixtures:emit/check,
per test/integration/test/ports/README.md). None of the corpus schemas
use the PSL `@db.Date` attribute, so none of the churn traces to #1038;
it is entirely the extensions key landing in the hashed bytes.

Regenerating legacy-json's fixture surfaced a real, pre-existing gap:
its schema authored `requiredJson`/`optionalJson` as bare `Json`, which
main's prior PSL scalar-type unification (#1022) binds to pg/json@1 (no
equality comparison) rather than the jsonb-backed storage upstream
Prisma actually gives its `Json` scalar. The test's own translation
comment confirms it means jsonb (`where: { requiredJson: { not: ... } }`
ported to `.neq(...)`), so the committed fixture predates #1022 and was
never regenerated afterward -- main's own CI never caught it because
the ports corpus has no fixtures:check coverage. Switched both fields to
`Jsonb`, matching the already-recorded 0.16-to-0.17 upgrade guidance
(postgres-json-rebound-to-native-json) exactly; scoped repo-wide and
confirmed no other suite in the corpus hits the same gap.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@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