TML-2785: M:N slice 1 — correlated include read through the junction#679
TML-2785: M:N slice 1 — correlated include read through the junction#679tensordreams wants to merge 17 commits into
Conversation
|
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 (6)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughThis pull request implements end-to-end many-to-many include support in sql-orm-client. It extends type contracts to define junction-table metadata, resolves and propagates this metadata through query construction, generates correlated subqueries with junction joins for M:N relations, and validates the implementation with comprehensive unit and integration tests. ChangesMany-to-many include support
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
size-limit report 📦
|
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/extension-supabase
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/emitter
@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: |
4ee6af6 to
78affcf
Compare
b3bba1c to
e44f8aa
Compare
78affcf to
e4e3004
Compare
e44f8aa to
aaefe75
Compare
9e24a8b to
ed06084
Compare
87de795 to
4763690
Compare
38bcb74 to
c32cd78
Compare
d15ad0b to
5450121
Compare
7e21c60 to
f383943
Compare
67f9537 to
2aaf8c0
Compare
a36b0d5 to
07aa1cf
Compare
2aaf8c0 to
cf13cac
Compare
07aa1cf to
12ec127
Compare
…ndard (TML-2785) Read slice (correlated include through junction). Bakes the operator integration-test standard (whole-row asserts, explicit select, +implicit nested-M:N case) into the project cross-cutting requirements and slice 1. 3 dispatches: fixture M:N / read code / integration tests. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Add many-to-many include support via a single correlated subquery that joins the child table to the junction on junction.childColumns = child.targetColumns and correlates to the parent via WHERE junction.parentColumns = parent.parentLocalColumns. Composite keys AND across all column pairs. No LATERAL joins. - IncludeExpr gains `through?: IncludeThroughDescriptor` carrying junction table name, parentColumns, childColumns, targetColumns, and parentLocalColumns. - `resolveIncludeRelation` in collection-contract.ts surfaces `through` from the contract relation when present, resolving field names to column names for the parent local columns. - `Collection.include()` propagates `through` into IncludeExpr via spread. - `buildManyToManyJunctionArtifacts` in query-plan-select.ts builds the JOIN ON expression (BinaryExpr or AndExpr over child column pairs) and the correlated WHERE (BinaryExpr or AndExpr over parent column pairs), producing a non-lateral inner JoinAst to the junction table. - `buildIncludeChildRowsSelect` detects `include.through` and uses the M:N artifacts instead of the FK equality WHERE; `buildDistinctNonLeafChildRowsSelect` receives and applies the same junction joins. - `dispatchWithIncludes` in collection-dispatch.ts forces all `through.parentLocalColumns` (not just `localColumn`) into the parent SELECT augmentation for composite M:N keys. - `buildManyToManyContract` test helper and M:N unit tests covering single-column and composite-key junction shapes, plus a FK path non-regression test. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ManyJunctionArtifacts; add M:N+distinct+non-leaf test
F1: the two bare `as AnyExpression` casts in `buildManyToManyJunctionArtifacts` are replaced with
`castAs<AnyExpression>(…!)` — BinaryExpr is a union member of AnyExpression, so the assertion is
type-checked; the non-null assertion is safe because the branch is only taken when `length === 1`.
Adds the `castAs` import from `@prisma-next/utils/casts`. `lint:casts` delta: -1.
F2: new unit test `attaches junction join to baseInner in M:N + distinct + nested non-leaf path`
in the `M:N include correlated subquery` describe block. Constructs a contract with
parents→children (M:N via parent_child junction) + children→grandchildren (FK), sets
`distinct: ['name']` and a nested grandchild include on the M:N IncludeExpr, calls
`compileSelectWithIncludes`, and asserts:
- junction join (`INNER JOIN parent_child`, `lateral: false`) attaches to `baseInner`
(the innermost scalar SELECT inside the ROW_NUMBER wrap), not to the dedup wrapper or
outer distinct SELECT
- correlated WHERE (`parent_child.parent_id = parents.id`) is present at `baseInner`
- no junction join leaks to `innerSelect` or the outer `childRows`
All 493 tests pass; typecheck clean.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Adds `mn-include.test.ts` covering the end-to-end M:N include path for
`User.tags` via the `user_tags` junction, using the PGlite harness.
Tests satisfy the operator integration-test standard:
- Whole-row toEqual assertions on every test
- Explicit .select() on 6/7 tests
- One implicit/default-selection test (full User + tags: Tag[] shape)
- Single-execution assertion + no LATERAL in emitted SQL
- Depth-2: M:N tags nested under invitedUsers (1:N self-relation)
- Sibling depth-2: include("tags") alongside include("posts") in one execution
- Edge: user with no tags returns tags: []
- Edge: tag shared by multiple users resolves independently for each
Also extends `setupTestSchema` and adds `seedTags`/`seedUserTags` helpers
to `runtime-helpers.ts` to support the junction table in integration tests.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
… (TML-2785) Orchestrator artifacts for the read slice (fixture / read-path / integration dispatches; read-path took 3 rounds incl. a truncation recovery). Review log under reviews/ is gitignored. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…-2605 rebase Carry the resolved junction namespaceId onto IncludeThroughDescriptor and build the correlated include's junction TableSource with it, so the read path emits namespace-qualified SQL for the junction like the rest of the runtime after TML-2605. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ough descriptor The contract now carries the junction's namespaceId on through (slice 0); the include descriptor passes it through as a required field instead of re-deriving it from storage. Re-emit the M:N fixture so the User<->Tag through block carries it. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
… M:N contracts (TML-2808 rebase) Main moved namespace tables under entries.table; the hand-built M:N test contracts still used the flat tables record, so table resolution missed them. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…eated_at) The shared fixture's junction declares a nullable note and a defaulted created_at (exclusion-path coverage for requiredPayloadColumns); create the columns so the schema matches the contract. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…junction artifacts biome `noNonNullAssertion` (error-on-warnings) flagged the `joinOnPairs[0]!` / `correlationPairs[0]!` non-null assertions. Narrow with a truthy guard on the destructured first element instead, which also removes the castAs (so its import goes too). Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…-2841 coordinate rebase Main un-pinned the ORM client onto ADR 221 coordinates: resolveFieldToColumn gained a leading namespaceId, IncludeExpr requires relatedNamespaceId, and compileSelectWithIncludes takes (contract, namespaceId, table, state). Adapt the M:N parentLocalColumns resolution and the read-path tests accordingly. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…tions Slice 0 (the contract-shape change) merged into main, so its instructions touch is no longer in the open PRs diff — but slices 1-3 still touch packages/3-extensions/, which the check:upgrade-coverage gate requires be accompanied by an instructions touch. Record the M:N runtime slices as incidental for extension authors (no API/contract change beyond TML-2784). Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
12ec127 to
f4f4d0b
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@packages/3-extensions/sql-orm-client/src/query-plan-select.ts`:
- Around line 367-372: Add explicit fail-fast length checks instead of using the
fallback indexing in the M:N junction logic: before computing joinOnPairs (the
BinaryExpr.eq calls that use ColumnRef.of(junctionTable, junctionCol) and
ColumnRef.of(childTableRef, targetColumns[i] ?? junctionCol)), assert
childColumns.length === targetColumns.length and similarly assert
parentColumns.length === parentLocalColumns.length (or the actual parent-side
variable names used where parentLocalColumns are indexed). Throw a clear Error
with a descriptive message (e.g., "M:N junction: childColumns and targetColumns
length mismatch") so misaligned through descriptor arrays fail fast rather than
silently falling back.
In
`@skills/extension-author/prisma-next-extension-upgrade/upgrades/0.12-to-0.13/instructions.md`:
- Around line 55-59: Update the paragraph that claims full M:N support so it
accurately reflects this PR implements only the read-only slice: change wording
to state that TML-2785 implements correlated include reads (see
buildManyToManyJunctionArtifacts and include("tags") tests), and remove or
separate claims about EXISTS-through-junction filters and junction-table nested
writes (connect/disconnect/create) which belong to TML-2786/TML-2787
write/filter slices; explicitly note that write/filter functionality is planned
in those separate tickets and no extension-author action is required for the
current read-only runtime change.
In `@test/integration/test/sql-orm-client/mn-include.test.ts`:
- Line 106: The assertions checking for absence of "LATERAL" are case-sensitive;
update them to be case-insensitive by changing the checks on
runtime.executions[0]?.sql (and the similar check around line 279) to use a
case-insensitive match — e.g. replace .not.toContain('LATERAL') with
.not.toMatch(/lateral/i) or transform the SQL to one case and check
(.toUpperCase().not.toContain('LATERAL') or
.toLowerCase().not.toContain('lateral')) so both "LATERAL" and "lateral" are
caught.
🪄 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
Run ID: 83c5097d-c7ab-45bc-a8be-19c90fd0f633
⛔ Files ignored due to path filters (10)
projects/sql-orm-many-to-many/learnings.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/01-correlated-read-through-junction/dispatches/01-fixture-m2n.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/01-correlated-read-through-junction/dispatches/02-read-path.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/01-correlated-read-through-junction/dispatches/02-read-path.r2.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/01-correlated-read-through-junction/dispatches/02-read-path.r3.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/01-correlated-read-through-junction/dispatches/03-integration-tests.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/01-correlated-read-through-junction/plan.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/01-correlated-read-through-junction/spec.mdis excluded by!projects/**projects/sql-orm-many-to-many/spec.mdis excluded by!projects/**projects/sql-orm-many-to-many/trace.jsonlis excluded by!projects/**
📒 Files selected for processing (10)
packages/3-extensions/sql-orm-client/src/collection-contract.tspackages/3-extensions/sql-orm-client/src/collection-dispatch.tspackages/3-extensions/sql-orm-client/src/collection.tspackages/3-extensions/sql-orm-client/src/query-plan-select.tspackages/3-extensions/sql-orm-client/src/types.tspackages/3-extensions/sql-orm-client/test/helpers.tspackages/3-extensions/sql-orm-client/test/query-plan-select.test.tsskills/extension-author/prisma-next-extension-upgrade/upgrades/0.12-to-0.13/instructions.mdtest/integration/test/sql-orm-client/mn-include.test.tstest/integration/test/sql-orm-client/runtime-helpers.ts
Replace the silent `?? junctionCol` fallback indexing in buildManyToManyJunctionArtifacts with explicit length invariants over the through descriptor's column arrays (childColumns/targetColumns and parentColumns/parentLocalColumns), then index with assertDefined narrowing so a misalignment throws a clear error instead of emitting a wrong join or correlation predicate. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…spread
Replace the manual `...(x !== undefined ? { through: x } : {})` spreads in
resolveIncludeRelation and the include builder with the ifDefined helper from
@prisma-next/utils/defined, per the use-if-defined convention.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Migrate the M:N include query-plan unit tests off the hand-rolled
buildManyToManyContract helper and onto the real emitted fixture's relations:
User.tags via user_tags (single-column), Project.related via project_links
(composite, self-referential), and the distinct + nested-non-leaf lowering via
Project.related distinct('name').include('related'). Each test now asserts the
whole compiled plan.ast with a single toEqual and drops the lateral / 'no
LATERAL' assertions (there is only one strategy). buildManyToManyContract (and
its RawColumn type) had no remaining uses on this branch and is removed.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…M:N include Drop the SQL-string LATERAL assertions from the M:N include integration tests. LATERAL can't appear (no such strategy exists in code), so the assertion tested an implementation detail. The real non-functional invariant — the include resolves in a single SQL execution — is already asserted via runtime.executions length. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The 0.12->0.13 extension-author note overstated delivered functionality: it claimed EXISTS-through-junction filters (TML-2786) and junction nested writes (TML-2787) alongside the correlated include read. This slice is read-only, so narrow the note to TML-2785's M:N correlated include read; the filter and write paths are documented in their own slices. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
|
Thanks — the three findings in this review summary are already tracked as inline threads and have been addressed: query-plan-select.ts 367-372 fail-fast (6d924db), the 0.12->0.13 instructions scope (905f19c), and the LATERAL guard on mn-include.test.ts (removed entirely in f27df69, superseding the case-insensitivity suggestion). No separate change needed here. |
Slice 1 of the SQL ORM: Many-to-Many End to End project (Linear project). Reads an M:N relation through its junction.
Overview
db.orm.User.include(tags)now resolves a many-to-many relation to{ …user, tags: Tag[] }in a single correlated subquery that walks parent → junction → target — noLATERAL, no second query. Built on slice 0sResolvedRelation.through.Changes (4 commits)
fcecac5b3— integration fixture gains aUser ↔ TagM:N relation via aUserTagjunction (composite PKuser_id/tag_id);contract.json/.d.tsre-emitted.e587b433c— read path:IncludeExpr.through(surfaced byresolveIncludeRelation), andbuildCorrelatedIncludeProjectiongains an M:N branch —buildManyToManyJunctionArtifactsbuilds a non-LATERAL inner join to the junction (junction.childColumns = target.targetColumns) correlated to the parent (junction.parentColumns = parentanchor), composite-key AND-ed; FK decode path reused. Unit-tested at the AST level.b9c3e9f7b— replace 2 bareascasts withcastAs; add the missing M:N + distinct + non-leaf unit test.d3232cbad— 7 integration tests (PGlite).Integration tests (per the project standard)
Whole-row
toEqual; 6/7 use explicit.select(...)(so adding a model field wont churn assertions); test 5 uses implicit/default selection (fullUser+tags: Tag[]shape); a single-execution / no-LATERALassertion; depth-2 nesting (invitedUsers → tags); edges (user with no tags →tags: []; a tag shared by multiple users).Why
This is the first of the three relation-shaped M:N consumers (read / filter / write) over slice 0s shared
throughprimitive. The correlated-only approach matches the post-TML-2729 read path (no LATERAL to reintroduce).Scope / notes
Read only — filter (TML-2786) and write (TML-2787) are later slices. The fixture is one-directional (
User.tags; reverseTag.usersdeferred — adding it trips a latent create-overload type fragility in unrelated mutation-defaults tests; see the projects unattended-decisions log). Fixture re-emit used atsxbypass because the CLIcontract emitfails on a sandbox config-load env issue — CIfixtures:checkis the real golden-stability gate; please confirm its green (or re-run the canonical emit). Broad integration runs show pre-existing PGlite/WASM JIT flakiness; the M:N tests pass on targeted runs.Refs: TML-2785.
Summary by CodeRabbit
New Features
Tests