Skip to content

TML-2683: wire polymorphism into the SQL ORM .include() child path#669

Open
tensordreams wants to merge 33 commits into
mainfrom
tml-2683-sql-orm-include-silently-degrades-on-polymorphic-target
Open

TML-2683: wire polymorphism into the SQL ORM .include() child path#669
tensordreams wants to merge 33 commits into
mainfrom
tml-2683-sql-orm-include-silently-degrades-on-polymorphic-target

Conversation

@tensordreams
Copy link
Copy Markdown
Contributor

@tensordreams tensordreams commented Jun 1, 2026

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-specific where filters 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)

  • D1 SQL side: child SELECT emits MTI variant joins + variant/discriminator projection. (bb42ab153)
  • D2 Decode side: decodeIncludePayload maps poly child rows via mapPolymorphicRow. (20b963758)
  • D3 .variant() narrowing + result type (bare include types as the variant union). (4c8f2d460)
  • D4 Integration (PGlite): STI/MTI includes, narrowing, STI variant-where. (34becbd8a)
  • D5 MTI variant-where: variant-aware predicate accessor. (d5d9204b4)
  • D6 Harden polymorphism.test.ts: whole-shape toEqual, STI/MTI implicit-default tests, de-raw. (abbaacd77)
  • D7 MTI+relationship coverage: MTI-as-parent, N:1 poly target, 2+ MTI variants, nested-through-poly, relationship implicit-default. (c41940f73)
  • D8 Fix depth-2 nested-include-through-poly decode (grandchild → null); unskip D7 scenario. (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-result toEqual + explicit select). Slice spec / plan / design-notes in projects/tml-2683/.

Follow-ups (filed; out of scope here)

  • TML-2782orderBy on a variant-narrowed collection drops MTI variant-field columns.
  • TML-2783 — explicit .select(...) on a poly include doesn't restrict variant-table columns (filed in May WS2).
  • SQLite poly-include integration coverage — subsumed by the multi-target test-runtime project; some SQLite breakage accepted through mid-July (Postgres GA focus).

Summary by CodeRabbit

  • New Features

    • Polymorphic relation includes now return correct per-variant fields, preserve nested include shapes, and support variant-aware predicates and variant-specific narrowing/filtering.
  • Documentation

    • New testing guidance: require asserting complete query result shapes (explicit select, deterministic ordering; prefer exact equality or snapshots).
  • Tests

    • Expanded unit and integration suites covering polymorphic includes, variant narrowing, and exact-result assertions.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: dbee20ee-8c52-4015-888a-c0fba85fc2f5

📥 Commits

Reviewing files that changed from the base of the PR and between 9263fb1 and eba13e0.

📒 Files selected for processing (10)
  • docs/onboarding/Testing.md
  • packages/3-extensions/sql-orm-client/src/collection-contract.ts
  • packages/3-extensions/sql-orm-client/src/collection.ts
  • packages/3-extensions/sql-orm-client/src/model-accessor.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-select.ts
  • packages/3-extensions/sql-orm-client/src/types.ts
  • packages/3-extensions/sql-orm-client/test/collection-dispatch.test.ts
  • packages/3-extensions/sql-orm-client/test/collection-variant.test.ts
  • packages/3-extensions/sql-orm-client/test/polymorphism.test-d.ts
  • test/integration/test/sql-orm-client/polymorphism-include-relationships.test.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/onboarding/Testing.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/3-extensions/sql-orm-client/test/collection-dispatch.test.ts
  • packages/3-extensions/sql-orm-client/test/polymorphism.test-d.ts
  • packages/3-extensions/sql-orm-client/src/model-accessor.ts
  • packages/3-extensions/sql-orm-client/src/types.ts
  • test/integration/test/sql-orm-client/polymorphism-include-relationships.test.ts

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Polymorphic Include Support with Variant Awareness

Layer / File(s) Summary
Documentation and testing rules
.agents/rules/README.md, .agents/rules/sql-orm-client-whole-shape-assertions.mdc, AGENTS.md
Adds agent rules documentation requiring whole-result shape assertions (explicit select + toEqual/snapshots) and updates AGENTS index.
Type infra and variant field resolution
packages/3-extensions/sql-orm-client/src/types.ts, packages/3-extensions/sql-orm-client/src/collection-contract.ts
Introduces VariantAwareModelAccessor type, VariantColumnRef, and resolveVariantFieldColumns that map variant fields to qualified table/column refs with caching.
Model accessor variant resolution
packages/3-extensions/sql-orm-client/src/model-accessor.ts
createModelAccessor optionally accepts variantName and resolves variant-qualified table/column references when building accessors and storage codecs.
Collection where() and include() type refinements
packages/3-extensions/sql-orm-client/src/collection.ts
Exposes VariantAwareModelAccessor in where(fn) callbacks via blindCast, and refines include() generic row typing to SimplifyDeep<InferRootRow<...>>.
Query plan polymorphic include artifacts
packages/3-extensions/sql-orm-client/src/query-plan-select.ts
Adds buildChildPolymorphismJoinsAndProjection to produce MTI variant joins and variant projection aliases; integrates these projections and joins into child SELECTs and non-leaf distinct lowering.
Include decoding with polymorphic row mapping
packages/3-extensions/sql-orm-client/src/collection-dispatch.ts
decodeIncludePayload now selects polymorphic row mapping when needed and decodes nested includes from the raw child row to preserve nested relation aliases.
Test helpers and contract fixtures
packages/3-extensions/sql-orm-client/test/helpers.ts
Extends contract builders with Project/Account parents, adds projectId/parentId FKs, subtasks relations, and new storage table definitions used by tests.
Unit tests for variant-aware field resolution and dispatch
packages/3-extensions/sql-orm-client/test/model-accessor.test.ts, packages/3-extensions/sql-orm-client/test/collection-dispatch.test.ts, packages/3-extensions/sql-orm-client/test/collection-variant.test.ts
Adds tests verifying variant-aware accessor behavior, STI/MTI include decoding, nested-include payload preservation, and include refinement variant(...) behavior.
Unit tests for polymorphic type definitions
packages/3-extensions/sql-orm-client/test/polymorphism.test-d.ts
Adds declaration tests for polymorphic contract extensions, project relation, variant-aware include typing, and variant-specific predicate typing.
Unit tests for query plan polymorphic includes
packages/3-extensions/sql-orm-client/test/query-plan-select.test.ts
Adds compilation tests for STI no-join projections, MTI LEFT JOIN + variant projections, variant-narrowed INNER JOINs, and self-relation JOIN-ON alias remapping.
Integration tests for polymorphic include relationships
test/integration/test/sql-orm-client/polymorphism-include-relationships.test.ts
End-to-end tests covering polymorphic includes across relationship shapes, nested includes, and implicit default selection rules with PostgreSQL fixtures.
Integration tests for STI and MTI polymorphic includes
test/integration/test/sql-orm-client/polymorphism-include.test.ts
Adds integration coverage for base include behavior, variant-specific .variant(...).where(...) filtering, and variant narrowing effects.
Integration tests for polymorphic variant projections
test/integration/test/sql-orm-client/polymorphism.test.ts
Refactors integration assertions to use deterministic ordering and exact equality (orderBy + toEqual), and validates create() behavior with storage-level checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • prisma/prisma-next#555: Modifies nested include decoding paths that overlap with the nested payload-preservation changes in this PR.

Suggested reviewers

  • aqrln

Poem

🐰 I hop through variants, soft and spry,
MTI joins and STI flags I spy,
Nested payloads kept safe and neat,
Tests clap paws for shapes complete,
A carrot-coded cheer — toEqual, bye-bye partials!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% 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 directly and clearly describes the main change: wiring polymorphism support into the SQL ORM's .include() child path, which is the core objective of this PR.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2683-sql-orm-include-silently-degrades-on-polymorphic-target

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

github-actions Bot commented Jun 1, 2026

size-limit report 📦

Path Size
postgres / no-emit 137.51 KB (+0.16% 🔺)
postgres / emit 109.06 KB (+0.23% 🔺)
mongo / no-emit 75.78 KB (0%)
mongo / emit 70.78 KB (0%)
cf-worker / no-emit 166.31 KB (0%)
cf-worker / emit 134.52 KB (0%)

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 1, 2026

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/emitter

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: eba13e0

@tensordreams tensordreams marked this pull request as ready for review June 1, 2026 17:04
@tensordreams tensordreams requested a review from a team as a code owner June 1, 2026 17:04
@tensordreams tensordreams force-pushed the tml-2729-sql-orm-drop-lateral-for-includes-in-favour-of-correlated branch from 72db66a to 291bb99 Compare June 2, 2026 12:53
Base automatically changed from tml-2729-sql-orm-drop-lateral-for-includes-in-favour-of-correlated to main June 2, 2026 13:10
An error occurred while trying to automatically change base from tml-2729-sql-orm-drop-lateral-for-includes-in-favour-of-correlated to main June 2, 2026 13:10
@tensordreams tensordreams force-pushed the tml-2683-sql-orm-include-silently-degrades-on-polymorphic-target branch from 67d9910 to c6527c3 Compare June 2, 2026 13:46
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e939a7a and c6527c3.

⛔ Files ignored due to path filters (12)
  • projects/tml-2683/design-notes.md is excluded by !projects/**
  • projects/tml-2683/dispatches/01-sql-side.md is excluded by !projects/**
  • projects/tml-2683/dispatches/02-decode-side.md is excluded by !projects/**
  • projects/tml-2683/dispatches/03-variant-narrowing.md is excluded by !projects/**
  • projects/tml-2683/dispatches/04-integration.md is excluded by !projects/**
  • projects/tml-2683/dispatches/05-mti-variant-where.md is excluded by !projects/**
  • projects/tml-2683/dispatches/06-polymorphism-test-hardening.md is excluded by !projects/**
  • projects/tml-2683/dispatches/07-mti-relationship-coverage.md is excluded by !projects/**
  • projects/tml-2683/dispatches/08-nested-poly-include-decode.md is excluded by !projects/**
  • projects/tml-2683/learnings.md is excluded by !projects/**
  • projects/tml-2683/plan.md is excluded by !projects/**
  • projects/tml-2683/spec.md is excluded by !projects/**
📒 Files selected for processing (18)
  • .agents/rules/README.md
  • .agents/rules/sql-orm-client-whole-shape-assertions.mdc
  • AGENTS.md
  • packages/3-extensions/sql-orm-client/src/collection-contract.ts
  • packages/3-extensions/sql-orm-client/src/collection-dispatch.ts
  • packages/3-extensions/sql-orm-client/src/collection.ts
  • packages/3-extensions/sql-orm-client/src/model-accessor.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-select.ts
  • packages/3-extensions/sql-orm-client/src/types.ts
  • packages/3-extensions/sql-orm-client/test/collection-dispatch.test.ts
  • packages/3-extensions/sql-orm-client/test/collection-variant.test.ts
  • packages/3-extensions/sql-orm-client/test/helpers.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts
  • packages/3-extensions/sql-orm-client/test/polymorphism.test-d.ts
  • packages/3-extensions/sql-orm-client/test/query-plan-select.test.ts
  • test/integration/test/sql-orm-client/polymorphism-include-relationships.test.ts
  • test/integration/test/sql-orm-client/polymorphism-include.test.ts
  • test/integration/test/sql-orm-client/polymorphism.test.ts

Comment thread packages/3-extensions/sql-orm-client/src/collection.ts
Comment thread packages/3-extensions/sql-orm-client/test/collection-dispatch.test.ts Outdated
Comment thread .agents/rules/sql-orm-client-whole-shape-assertions.mdc
@tensordreams tensordreams force-pushed the tml-2683-sql-orm-include-silently-degrades-on-polymorphic-target branch from c6527c3 to 2dbe544 Compare June 2, 2026 15:38
Comment thread packages/3-extensions/sql-orm-client/src/collection-contract.ts
Comment thread packages/3-extensions/sql-orm-client/src/collection-contract.ts Outdated
Comment thread packages/3-extensions/sql-orm-client/src/collection-contract.ts
Comment thread packages/3-extensions/sql-orm-client/src/collection-contract.ts
Comment thread packages/3-extensions/sql-orm-client/src/collection.ts Outdated
Comment thread packages/3-extensions/sql-orm-client/src/collection.ts
Comment thread packages/3-extensions/sql-orm-client/src/model-accessor.ts
Comment thread packages/3-extensions/sql-orm-client/src/model-accessor.ts Outdated
Comment thread packages/3-extensions/sql-orm-client/src/query-plan-select.ts Outdated
Comment thread packages/3-extensions/sql-orm-client/src/query-plan-select.ts Outdated
Comment thread packages/3-extensions/sql-orm-client/src/types.ts
Comment thread packages/3-extensions/sql-orm-client/test/collection-dispatch.test.ts Outdated
Comment thread packages/3-extensions/sql-orm-client/test/collection-dispatch.test.ts Outdated
Comment thread packages/3-extensions/sql-orm-client/test/collection-dispatch.test.ts Outdated
Comment thread projects/tml-2683/dispatches/01-sql-side.md Outdated
Comment thread test/integration/test/sql-orm-client/polymorphism-include-relationships.test.ts Outdated
…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>
@tensordreams tensordreams force-pushed the tml-2683-sql-orm-include-silently-degrades-on-polymorphic-target branch from 2dbe544 to 9263fb1 Compare June 3, 2026 11:36
…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>
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