TML-2683: wire polymorphism into the SQL ORM .include() child path#669
TML-2683: wire polymorphism into the SQL ORM .include() child path#669tensordreams wants to merge 33 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 (10)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughAdds MTI/STI variant-aware include support: new types and resolver for variant columns, variant-aware model accessors, query-plan MTI joins/projections, include decoding that preserves nested payloads, updated collection typings, comprehensive tests, and a testing rule mandating whole-result-shape assertions. ChangesPolymorphic Include Support with Variant Awareness
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 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)
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/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: |
72db66a to
291bb99
Compare
67d9910 to
c6527c3
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/collection.ts`:
- Around line 245-281: The first() overloads must be made variant-aware like
where(): add/adjust the first(...) overloads that accept a callback so the
callback parameter is typed as VariantAwareModelAccessor<TContract, ModelName,
State['variantName']>, and in the implementation, when input is a function call
the callback with
blindCast<VariantAwareModelAccessor...>(createModelAccessor(this.ctx.context,
this.modelName, this.state.variantName)) (mirroring where()), so callbacks after
.variant('X') see the selected variant's fields; update the first function
signatures and implementation to parallel where's handling of function inputs
and shorthand/WhereDirectInput conversion.
In `@packages/3-extensions/sql-orm-client/test/collection-dispatch.test.ts`:
- Around line 442-445: Replace the loose toMatchObject assertions on the decoded
polymorphic rows with exact whole-shape assertions using toEqual and ensure the
query uses an explicit child select so any leaked sibling/base fields will fail;
specifically update the assertions on the tasks array (e.g., the expectations
for tasks[0] and tasks[1] in collection-dispatch.test.ts) to assert the complete
object shape via toEqual and adjust the test's select to explicitly select the
child-specific fields (and apply the same change to the other occurrences
mentioned around lines 513-523 and 556-557).
🪄 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: 1afdc2f9-d012-4c66-95d1-e29fd6a0999b
⛔ Files ignored due to path filters (12)
projects/tml-2683/design-notes.mdis excluded by!projects/**projects/tml-2683/dispatches/01-sql-side.mdis excluded by!projects/**projects/tml-2683/dispatches/02-decode-side.mdis excluded by!projects/**projects/tml-2683/dispatches/03-variant-narrowing.mdis excluded by!projects/**projects/tml-2683/dispatches/04-integration.mdis excluded by!projects/**projects/tml-2683/dispatches/05-mti-variant-where.mdis excluded by!projects/**projects/tml-2683/dispatches/06-polymorphism-test-hardening.mdis excluded by!projects/**projects/tml-2683/dispatches/07-mti-relationship-coverage.mdis excluded by!projects/**projects/tml-2683/dispatches/08-nested-poly-include-decode.mdis excluded by!projects/**projects/tml-2683/learnings.mdis excluded by!projects/**projects/tml-2683/plan.mdis excluded by!projects/**projects/tml-2683/spec.mdis excluded by!projects/**
📒 Files selected for processing (18)
.agents/rules/README.md.agents/rules/sql-orm-client-whole-shape-assertions.mdcAGENTS.mdpackages/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/model-accessor.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/collection-dispatch.test.tspackages/3-extensions/sql-orm-client/test/collection-variant.test.tspackages/3-extensions/sql-orm-client/test/helpers.tspackages/3-extensions/sql-orm-client/test/model-accessor.test.tspackages/3-extensions/sql-orm-client/test/polymorphism.test-d.tspackages/3-extensions/sql-orm-client/test/query-plan-select.test.tstest/integration/test/sql-orm-client/polymorphism-include-relationships.test.tstest/integration/test/sql-orm-client/polymorphism-include.test.tstest/integration/test/sql-orm-client/polymorphism.test.ts
c6527c3 to
2dbe544
Compare
…ew task columns buildMixedPolyContract now adds project_id/parent_id columns to tasks (to host the Project->Task and Task->Task self relations). Update the parent-side compileSelect MTI projection expectations to match the full base column set. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…variant Assert decodeIncludePayload maps included child rows of a polymorphic target to their variant shape: STI rows resolve by discriminator (matching variant field kept, other variant's NULL column stripped), MTI rows surface variant_table__column cells under their model field names, and a variant-narrowed include maps every child row via the named variant. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…lymorphicRow decodeIncludePayload mapped polymorphic-target included child rows with the base mapper, so STI rows came back base-shaped (variant fields dropped) and MTI rows lacked their variant columns. Resolve the related model's PolymorphismInfo once per include and, when polymorphic, map each child row via mapPolymorphicRow (passing nested.variantName for variant-narrowed includes); otherwise keep mapStorageRowToModelFields. This is the decode half of the parent-child symmetry the parent dispatchers already have. Nested recursion, scalar, combine, and empty-relation handling are unchanged. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…not the typed builder The poly contracts patch Account/Project/Task models in at runtime, so they are absent from the generated fixture's Models type; createCollectionFor's ModelName constraint rejected them and tsc failed. Construct the IncludeExpr directly from resolveIncludeRelation and dispatch with string model/table names — the same helper the plan-level poly tests already use — so the decode tests typecheck. Same assertions; variant narrowing now sets nested variantName through the include builder rather than post-hoc mapping. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…riant() narrows
Type tests: .include('<polyRel>') without refinement yields the variant union
row type; .include('<polyRel>', r => r.variant('X')) narrows the included value
to variant X's row. Runtime tests: r.variant('X') on an include refinement sets
nested.variantName; a bare include leaves it unset.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…n and .variant()
include() typed the included relation off DefaultModelRow (base fields only),
so a polymorphic-target include silently dropped variant fields at the type
level. Type the default included row off InferRootRow — the variant union —
mirroring the root collection's default row, so a bare include surfaces the
union and r.variant('X') in a refinement narrows to variant X's row. The
runtime already threads nested.variantName through the refinement collection;
no runtime change is needed.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…n a real DB
Cover .include('<polyRel>') where the related model is polymorphic, against
the PGlite (Postgres) integration harness: STI-target include returns each
child row shaped per its discriminator variant; MTI-target include surfaces
the joined variant table's columns; a variant-specific where on the include
refinement filters correctly; .variant('X') narrows a poly include to that
variant only, for both STI and MTI targets.
The parent-bearing poly contracts (Account->members->User STI, Project->tasks
->Task Bug-STI/Feature-MTI) are built locally so the shared poly helpers stay
stable for sibling tests whose hand-rolled DDL omits the parent FK column.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ly integration Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…t-field where
A variant-specific `where` on an MTI polymorphic include
(`include('tasks', t => t.variant('Feature').where(x => x.priority.gte(3)))`)
threw `TypeError: Cannot read properties of undefined` because the predicate
accessor resolved fields against the base table only, while MTI variant
columns live on the joined variant table.
Thread the selected `variantName` into `createModelAccessor`. When set,
MTI variant-owned fields resolve to a `ColumnRef` qualified against the
variant table that the read path already inner-joins into the correlated
child SELECT; base fields and the no-variant path are unchanged. The `where`
predicate-accessor type now exposes the selected variant's fields via
`VariantAwareModelAccessor`, gated on the type-level `variantName`.
STI variant columns ride the base table and were already covered; only the
MTI gap is closed here.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…target project Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Refactor polymorphism-include.test.ts to assert the entire result shape with toEqual and explicit .select(...) projections on both the parent collection and the included poly relation, ordering deterministically by the base id column. Replaces partial toMatchObject/toHaveProperty/lone toBe matchers, conforming to the sql-orm-client-whole-shape-assertions rule. Locks the empirically-confirmed select-vs-poly composition: STI variant fields are base columns that select projects and mapPolymorphicRow drops per sibling variant; MTI variant columns (features.priority) are joined+projected regardless of select. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…lden-rule pointer Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ge); rule implicit-default carve-out Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Replace partial matchers (toMatchObject / toHaveProperty / lone toBe) with whole-result toEqual, ordered deterministically by base id. Convert the base / variant queries into explicit implicit-default-selection tests (no .select), pinning the full default projected shape for the variant union, the STI Bug variant, and the MTI Feature variant. De-raw the STI-create discriminator read-back to an ORM read with whole-shape toEqual. Keep the MTI-create raw two-table check (base tasks row + features variant row) as a storage-level invariant the ORM hides, with a why-comment; keep raw DDL and seed inserts. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Extend poly-target include integration coverage in a new sibling file polymorphism-include-relationships.test.ts with local standalone fixtures (deep-cloned poly contracts + per-test DDL/seeds; shared helpers.ts untouched). Whole-shape toEqual, base-id ordering. Scenarios: - poly (MTI) model as the include PARENT (correlation across base + variant tables) - to-one (N:1) include whose TARGET is a poly model (per-row variant mapping on a single object) - a base with two MTI variant tables (no cross-variant column contamination) - relationship-level implicit-default selection for STI and MTI (no .select, full default per-variant shape — the rule's exception) The nested-include-through-poly-target scenario surfaced a real decode defect (grandchild stitches to null when the include target is poly): landed as it.skip asserting the correct shape, with root-cause notes, as a regression target. Not patched here (test-only scope). Add a TML-2783 note to the existing select-leak MTI assertion so the known poly-variant-column leak is traceable. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…epth-2 decode defect Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
… the raw child row A nested .include(...) hanging off a polymorphic include target decoded the grandchild to an empty value for every row. The poly branch of decodeIncludePayload mapped the child row via mapPolymorphicRow first — which keeps only variant model-field columns and so drops the nested payload's relation alias — then read each nested-include payload from the mapped row, where it was already gone. Source each nested-include payload from the raw child row instead, which always carries the payload under its relation alias. Behavior-preserving for non-poly includes; leaves mapPolymorphicRow's per-variant shaping untouched. Adds a unit test covering an MTI and a non-MTI variant (asserting both the stitched grandchild and that sibling-variant columns are still dropped) and unskips the integration scenario. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ant join sources Rebase integration with TML-2605: relation `to` now requires a `namespace` (domain namespace 'public' for these fixtures), and emitted variant-table sources carry the storage namespace (UNBOUND_NAMESPACE_ID). Updates the hand-built poly fixtures + the poly join-shape assertions accordingly. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
2dbe544 to
9263fb1
Compare
…y namespace-flat refs Remove the variantFieldColumnCache layer: resolvePolymorphismInfo already memoizes the variant lookup and the remaining work is a single pass over the variant field->column map, so the second cache (and its undescriptive object/string keys and Map-vs-Record mix) bought nothing. Document that VariantColumnRef.table and the model-name params are namespace-flat, with namespace bound downstream at table resolution. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…projection helper - Document DefaultModelRow vs InferRootRow and why the latter cannot be re-expressed in terms of the former. - Record that the variant-narrowed accessor surfaces base relations only, since the runtime resolves relations against the base model. - Spell out what the no-variant accessor path leaves unchanged. - Rename buildChildPolymorphismArtifacts -> buildChildPolymorphismJoinsAndProjection (and the polyJoinsAndProjection locals) to name what it returns. - Reword the metaphor-laden poly-column carry-through comments in plain terms. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Replace toMatchObject + not.toHaveProperty pairs on decoded polymorphic child rows with exact toEqual whole-shape assertions, per the sql-orm-client-whole-shape-assertions rule. The exact match surfaces the foreign-key field (projectId) the loose matchers were silently hiding, so the asserted shape is now complete. Drop the redundant toArray() (await already collects via AsyncIterableResult.then). Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…t toArray Document why the local IncludeRoot/IncludeRefinement/BaseRow and RawContract/MutableModel/RawTable stand-ins are needed: the relationship and poly models are patched onto a real emitted contract at runtime (no .psl source exists for them), so they cannot appear in the static TestContract Models type that drives the real Collection/include types. The base contract is the real emitted fixture. Make the local all() return PromiseLike<Row[]> (mirroring the real AsyncIterableResult) and drop the redundant .toArray() at every call site. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…re()
first()'s callback filter param is now
VariantAwareModelAccessor<TContract, ModelName, State["variantName"]> instead
of the base ModelAccessor, so `.variant('X').first(t => t.variantField…)` types
the predicate model as the selected variant (MTI variant columns included).
The runtime already routed first() through where(), which builds the
variant-aware accessor, so only the type signature needed to catch up.
Tests-first: added type-tests mirroring the where() variant-aware cases and a
runtime test asserting first() after variant(Feature) resolves the MTI variant
field against the features table. Also expanded the where() blindCast comment
to record why the variant widening stays callback-param-only rather than
re-typing createModelAccessor's return.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…transient project workspace Migrate the one durable learning from the project workspace (integration tests run against each package built dist, so rebuild before a bare vitest filter run) into docs/onboarding/Testing.md, then remove the transient project workspace. The remaining durable outcomes already live in the sql-orm-client-whole-shape-assertions rule, AGENTS.md golden rules, the filed follow-up tickets, and the code + tests on this PR. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
What
Fixes TML-2683:
db.orm.<parent>.include('<rel>')silently degraded when<rel>'s target model is polymorphic — STI returned base-shaped rows (variant fields dropped), MTI returned rows missing variant columns. Wires the parent-side polymorphism machinery into the child include path, end-to-end, including depth-2 nested includes.Outcome
.include('<polyRel>')returns rows shaped per each row's variant (STI + MTI);.include('<polyRel>', r => r.variant('X'))narrows at runtime and in the type; a variant-specificwherefilters correctly for STI (base-table) and MTI (variant-table) columns; and a nested.include()through a poly target stitches the grandchild (no depth-2 null degradation).Dispatches (drive build-workflow; implementer + reviewer subagents — all SATISFIED)
bb42ab153)decodeIncludePayloadmaps poly child rows viamapPolymorphicRow. (20b963758).variant()narrowing + result type (bare include types as the variant union). (4c8f2d460)where. (34becbd8a)where: variant-aware predicate accessor. (d5d9204b4)polymorphism.test.ts: whole-shapetoEqual, STI/MTI implicit-default tests, de-raw. (abbaacd77)c41940f73)67d99103a)Acceptance: 7/7 ACs PASS (0 FAIL, 0 open). Whole-slice DoD green at tip: workspace
typecheck,build, package tests, PGlite integration,lint:deps.Also adds a test-style rule:
.agents/rules/sql-orm-client-whole-shape-assertions.mdc(whole-resulttoEqual+ explicitselect). Slice spec / plan / design-notes inprojects/tml-2683/.Follow-ups (filed; out of scope here)
orderByon a variant-narrowed collection drops MTI variant-field columns..select(...)on a poly include doesn't restrict variant-table columns (filed in May WS2).Summary by CodeRabbit
New Features
Documentation
Tests