Skip to content

TML-2794: M:N slice 5 — PSL authors many-to-many (junction → N:M + through)#819

Open
tensordreams wants to merge 7 commits into
tml-2790-mn-demo-examplesfrom
tml-2794-slice-5-psl-mn-authoring
Open

TML-2794: M:N slice 5 — PSL authors many-to-many (junction → N:M + through)#819
tensordreams wants to merge 7 commits into
tml-2790-mn-demo-examplesfrom
tml-2794-slice-5-psl-mn-authoring

Conversation

@tensordreams

Copy link
Copy Markdown
Contributor

Slice 5 of the SQL ORM: Many-to-Many End to End project (follow-on group). Refs TML-2794.

Stacked PR — base is tml-2790-mn-demo-examples (#742, slice 4); below that tml-2787-slice-3-write (#683) → main.

Overview

PSL could not author a navigable many-to-many relation: the relation resolver emitted only N:1/1:N, and a bare list field (tags Tag[]) with no FK pair was diagnosed as PSL_ORPHANED_BACKRELATION_LISTcardinality: 'N:M' + through existed only via the TS contract builder. This PR teaches the PSL interpreter explicit-junction recognition (form 1, pinned at pickup): when a bare list field has a recognisable junction model — composite @@id([a, b]) covering exactly the FK columns of two N:1 relations to the two side models — the list lowers to a navigable N:M relation through that junction, shape-identical to rel.manyToMany output.

Changes

  • psl-relation-resolution.ts: junction recognition in the previously-orphaned branch — findJunctionFkPairs + id-coverage gate + manyToManyRelationNode; child FK must reference the target's full id (set-equal), through.childColumns reordered to id order (the runtime joins childColumns[i] = targetColumns[i] by index); decline → existing orphaned diagnostic. Self-referential M:N disambiguated by the list's @relation name (pins the parent-side FK); unnamed symmetric lists get the ambiguity diagnostic. Surrogate-id join models are deliberately not recognised. Direct FK matches still win (stay 1:N).
  • 10 lowering unit tests (interpreter.relations.many-to-many.test.ts): both directions, composite keys, swapped-order FK references, self-referential, diagnostic preservation, validateContract round-trip.
  • PSL-authored integration fixture fixtures/mn-psl/ (User ↔ Tag via user_tags, mirroring the TS fixture) wired into the sql-orm-client emit pipeline so fixtures:check regenerates and diffs it.
  • mn-psl-parity.test.ts: 8 integration tests proving the full M:N ORM API (include explicit+implicit, some/none/every, connect, disconnect, nested create) against the PSL-emitted contract.

Why

The PG demo emits its contract from PSL, so it cannot show the M:N API until PSL can author it (slice 6 is blocked on this). Form 1 was chosen over Prisma-style implicit lists as the smallest surface that matches the existing diagnostic's guidance; implicit lists remain a clean follow-up.

Scope

PSL authoring layer + fixtures/tests only. No sql-orm-client runtime changes, no TS-builder changes, no demo changes (slice 6). Implicit-list authoring (form 2) deliberately excluded.

…ce-scoped)

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ations

A bare backrelation list field whose target pairs with the current model
through an explicit junction model (composite @@id columns exactly the FK
columns of the two N:1 relations) now lowers to a navigable relation with
cardinality N:M and a populated through descriptor, matching the shape the
TS builder emits for rel.manyToMany. The junction model and any direct
FK-side matches keep their existing 1:N/N:1 output untouched; lists with no
recognisable junction keep the PSL_ORPHANED_BACKRELATION_LIST diagnostic,
and structurally ambiguous junction pairings (e.g. unnamed self-referential
lists) report PSL_AMBIGUOUS_BACKRELATION_LIST.

TML-2794

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Junction recognition accepted any child FK regardless of what it
referenced, while downstream consumers pair through.childColumns
positionally against the target model id columns. A child FK
referencing a non-id unique, or a composite id in swapped order, was
recognised but emitted a silently wrong join.

Require the child FK to reference exactly the target model full id and
carry its junction columns in target-id order; anything else falls back
to the orphaned-backrelation diagnostic. The parent side stays as-is:
its pairing runs through the FK own parallel localFields/parentColumns
arrays and is order-safe by construction (pinned by the swapped-order
test).

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…er_tags)

Mirror the TS-builder M:N fixture in PSL: an explicit UserTag junction
model with composite @@id over its two FK columns plus bare
backrelation list fields on User and Tag. The emitted contract carries
cardinality N:M with a populated through descriptor on both sides --
shape-identical to what rel.manyToMany produces -- proving the PSL
explicit-junction lowering round-trips the canonical emit + validate
pipeline.

Generated artifacts come from the canonical CLI emit
(prisma-next contract emit --config .../mn-psl/prisma-next.config.ts)
and re-emit byte-identically, following the polymorphism fixture
convention.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…xture

Drive the PSL-emitted M:N contract through the full ORM API with one
representative integration case per surface: include (explicit select +
implicit default selection), some/none/every relation filters, and
connect/disconnect/nested-create junction writes -- mirroring the
TS-fixture coverage in mn-include / mn-filter / mn-nested-write.

Deserializing the emitted JSON runs the full sql contract validation
pipeline, so the suite also pins that a PSL-authored N:M + through
contract round-trips validation. The runtime is built against the
mn-psl contract (storageHash differs from the base fixture), following
the polymorphism-fixture contractOverride pattern.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The mn-psl PSL fixture config was not emitted by the package emit
script, so fixtures:check was vacuously green for it and a PSL
interpreter regression would leave the committed contract.json stale.
Append the mn-psl contract emit alongside the polymorphism fixture;
no cp step needed since the fixture emits into its own generated dir.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
@tensordreams tensordreams requested a review from a team as a code owner June 12, 2026 19:13
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: ff820cea-d4db-49f6-b89f-99ab2efa8a85

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2794-slice-5-psl-mn-authoring

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 and usage tips.

@github-actions

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 156.34 KB (0%)
postgres / emit 123.64 KB (0%)
mongo / no-emit 76.92 KB (0%)
mongo / emit 71.01 KB (0%)
cf-worker / no-emit 183.82 KB (0%)
cf-worker / emit 147.7 KB (0%)

@pkg-pr-new

pkg-pr-new Bot commented Jun 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/emitter

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 872c0ac

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.

1 participant