TML-3068: reject a singular back-relation over a non-unique FK - #1015
Conversation
applyBackrelationCandidates lowered a matched singular back-relation candidate (e.g. `profile Profile?`) to cardinality '1:1' without checking whether the matched FK's local columns are actually unique on the declaring model. A hand-authored singular back-relation over a plain (non-unique) FK was silently accepted as 1:1, a shape `contract infer` never produces on its own. Add fkColumnsAreUnique, an exact-cover check against the declaring model's id and @unique/@@unique column sets, and run it before emitting the 1:1 relation for a singular candidate. A non-unique match now emits PSL_NON_UNIQUE_BACKRELATION, naming both fixes (add @unique/@@unique to the FK, or make the field a list). Thread modelUniqueColumnSets through interpretPslDocumentToSqlContract the same way modelIdColumns already flows to applyBackrelationCandidates. Implementation lifted from the parked branch worktree/contract-infer-relationships-bcbafa, adapted to main's current diagnostic-code family (PSL_ORPHANED_BACKRELATION / PSL_AMBIGUOUS_BACKRELATION already cover both list and singular candidates on main, so only the missing uniqueness check needed lifting) and to main's already-unified isList-based candidate shape. Refs: TML-3068 Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe PSL interpreter now passes all model unique column sets into backrelation resolution. Singular backrelations require exact foreign-key uniqueness, with diagnostics emitted for non-unique matches. New tests cover identifier, composite unique, column-order, subset, and non-unique cases. Changes1:1 backrelation uniqueness
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant PSLInterpreter
participant BackrelationResolver
participant UniquenessChecker
participant Diagnostics
PSLInterpreter->>BackrelationResolver: provide model unique column sets
BackrelationResolver->>UniquenessChecker: check singular FK columns
UniquenessChecker-->>BackrelationResolver: return uniqueness result
BackrelationResolver->>Diagnostics: emit PSL_NON_UNIQUE_BACKRELATION when invalid
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/extension-supabase
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/config-loader
@prisma-next/emitter
@prisma-next/language-server
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
size-limit report 📦
|
Linked issue
Refs TML-3068
At a glance
On
main, this schema emits a contract claiming a 1:1 relation the database cannot guarantee:After this PR, that schema fails emit with
PSL_NON_UNIQUE_BACKRELATION, telling the author to add@unique/@@uniqueto the FK fields or make the field a list.Decision
Before lowering a singular back-relation to
cardinality: '1:1', the interpreter verifies the matched FK's local columns exactly cover one of the declaring model's unique sets — single@unique, composite@@unique, or the primary key. Exact cover means order-insensitive set equality:@@unique([a, b])does not qualify an FK on[a]alone (a superset unique guarantees nothing about the subset). On failure the new diagnostic joins the existing family (PSL_ORPHANED_BACKRELATION,PSL_AMBIGUOUS_BACKRELATION).The gap: #1011 taught the interpreter to accept the singular back-relation shape
contract inferprints — but infer only prints it when the FK is unique, so the acceptance path never checked. Hand-authored PSL has no such guarantee.The implementation is lifted from the parked branch
worktree/contract-infer-relationships-bcbafa(thefkColumnsAreUniqueexact-cover check and its boundary tests), adapted to main's generalized diagnostic family.Behavior changes & evidence
packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts(fkColumnsAreUnique+ the check in the singular exactly-one-match path);packages/2-sql/2-authoring/contract-psl/src/interpreter.ts(buildsmodelUniqueColumnSetsalongside the existingmodelIdColumns). Evidence:packages/2-sql/2-authoring/contract-psl/test/interpreter.relations.one-to-one.test.ts— both rejection cases reproduced red against main's code before the fix.@@uniqueexact match, order-insensitive column order, and PK-covering FKs all still lower to'1:1'(same test file).Reviewer notes
contract inferoutput is unaffected — infer's uniqueness detection was already correct; this closes the hand-authoring path only. No fixture or pack contract changed (pnpm fixtures:checkclean).portal "C_N" does not existflake reproduced insql-orm-clientintegration tests during validation and passed in isolation — the documented portal-race family, unrelated to this diff.Testing performed
pnpm typecheck·pnpm lint·pnpm fixtures:check— greenpnpm test:packages— 13190 passedpnpm test:integration— 1172/1173 + 1 portal-race flake, flaked file 5/5 green in isolationSkill update
n/a — new diagnostic code only; wording follows the existing backrelation family. No CLI/API surface change.
Alternatives considered
Trust the shape because infer produces it. That was #1011's implicit position; it holds only for inferred PSL. Emit is a validation boundary for hand-authored schemas too.
Rewrite instead of lifting. The parked branch's check and boundary tests were already reviewed once (exact-cover semantics, superset rejection); rewriting would re-derive settled decisions.
Checklist
git commit -s) per the DCO. The DCO status check will block merge if any commit is missing aSigned-off-by:trailer.n/aif the change is doc-only / refactor with no behavioural delta).TML-NNNN: <sentence-case title>form (Linear ticket prefix + concise title naming the concrete deliverable). See.claude/skills/create-pr/SKILL.mdfor the full convention.n/a — internal only).Summary by CodeRabbit
Bug Fixes
Tests