Skip to content

TML-2783: Restrict MTI results to explicitly selected fields#984

Merged
aqrln merged 5 commits into
mainfrom
tml-2783-sql-orm-explicit-select-on-a-polymorphic-include-doesnt
Jul 16, 2026
Merged

TML-2783: Restrict MTI results to explicitly selected fields#984
aqrln merged 5 commits into
mainfrom
tml-2783-sql-orm-explicit-select-on-a-polymorphic-include-doesnt

Conversation

@tensordreams

@tensordreams tensordreams commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Refs TML-2783
Prerequisite PR (merged): #976 / TML-2980

At a glance

const selectedMtiRows = await projects
  .select('id', 'name')
  .orderBy((project) => project.id.asc())
  .include('tasks', (tasks) =>
    tasks.select('id', 'priority').orderBy((task) => task.id.asc()),
  )
  .all();

expect(selectedMtiRows).toEqual([
  {
    id: 1,
    name: 'Roadmap',
    tasks: [{ id: 1 }, { id: 2 }, { id: 3, priority: 1 }, { id: 4, priority: 3 }],
  },
  { id: 2, name: 'Empty', tasks: [] },
]);

Previously, multi-table inheritance (MTI) columns were projected regardless of the child selection, so unselected variant fields could appear in these task objects.

Decision

This PR ships selection-aware MTI result shaping for both root polymorphic queries and correlated includes: explicit .select(...) controls visible base, STI, and MTI fields; selected MTI fields appear only on matching variants; omitted selection keeps the existing full default shape; and SQL-only joins, discriminators, and relation keys remain available internally wherever query execution needs them.

Notes for the reviewer

  • Most of the diff is in query-plan-select.ts: the key distinction is between columns visible because the user selected them and columns carried only for execution or mapping. Selection narrows projection, not MTI join availability.
  • The hidden __prisma_polymorphic_discriminator alias is deliberate when an MTI field is selected without type. mapPolymorphicRow() uses it to choose the variant, then drops it because it has no model-field mapping.
  • PR TML-2980: support variant-declared relations in SQL ORM includes #976's variant-declared relation support is preserved. variant-include.collection-dispatch.test.ts proves the nested assignee still maps while unselected MTI scalar and relation-key fields stay out of the result.
  • Distinct/non-leaf paths intentionally keep MTI-local relation keys in the inner derived query and omit them from the outer result projection. The exact wrapper boundaries, including composite M:N correlation, are pinned in variant-include.query-plan-nested.test.ts.
  • The small acquireRuntimeScope() edit is convert-on-touch cast cleanup with no intended behavior change; existing direct/scoped release tests cover it. The first post-rebase integration typecheck failure was stale workspace linking after main added dependencies; pnpm install --frozen-lockfile changed no tracked or lockfile content, and the exact typecheck then passed.

Summary

SQL ORM explicit selection already restricted base-table fields, but MTI variant-table fields were projected unconditionally and could leak into root and include result shapes. This change makes the projection contract consistent across inheritance storage while preserving the joined data needed for query semantics and variant mapping.

How it fits together

  1. resolvePolymorphicProjectionSelection() resolves selected domain fields and storage columns into a base/STI list plus a per-MTI-table column set. selectedFields === undefined remains the signal for the complete implicit projection.
  2. buildMtiJoins() and the root compilers keep the required left or inner joins while adding only selected MTI columns to the visible projection. The same partition feeds ordinary root queries, roots with includes, and correlated include children.
  3. When a selected MTI field needs variant mapping but the user did not select the discriminator, buildHiddenDiscriminatorProjection() adds a private alias and mapPolymorphicRow() consumes it without exposing it.
  4. buildDistinctNonLeafChildRowsSelect() separates the inner query projection from the outer visible projection. Selected fields, private mapping data, and MTI-local nested-relation keys cross only the SQL scope boundaries that need them; the outer JSON/result shape follows the original selection.

Behavior changes & evidence

Compatibility / migration / risk

Explicit selections on polymorphic roots and includes intentionally return fewer fields when MTI columns were not selected; queries without an explicit selection retain their previous full shape. MTI joins remain in the SQL plan because predicates, ordering, narrowing, nested relations, distinct wrappers, and M:N correlations can depend on them, while selected column lists become narrower. Internal discriminator and relation-key aliases are stripped before the user-visible result boundary. No public API, contract or emitted artifact, extension-authoring surface, adapter API, or downstream source translation changed, so no consumer migration is required.

Testing performed

  • pnpm --filter @prisma-next/sql-orm-client test — 61 files, 663 tests passed
  • pnpm --filter @prisma-next/integration-tests test test/sql-orm-client/polymorphism-include.test.ts — pretest build 68/68 tasks passed; PGlite 9/9 tests passed
  • pnpm --filter @prisma-next/sql-orm-client typecheck and pnpm --filter @prisma-next/integration-tests typecheck — passed
  • pnpm --filter @prisma-next/sql-orm-client lint and pnpm --filter @prisma-next/integration-tests lint — passed
  • pnpm lint:deps — 1,197 modules and 2,730 dependencies passed
  • pnpm lint:casts — 934 casts versus 936 on origin/main, delta -2
  • pnpm fixtures:check — passed
  • pnpm check:upgrade-coverage --mode pr — passed
  • pnpm lint:skills — passed
  • pnpm check:release-notes --mode pr — passed
  • git diff --check origin/main...HEAD — passed

These are local final-HEAD results; this draft does not claim that GitHub CI has run.

Skill update

The extension upgrade transition at skills/extension-author/prisma-next-extension-upgrade/upgrades/0.15-to-0.16/ records changes: []. This is an incidental internal SQL ORM projection fix with no extension-author SPI, contract shape, syntax, configuration, adapter API, or downstream source translation to migrate.

Alternatives considered

  • Remove MTI joins when no MTI field is selected: not chosen because filtering, ordering, variant narrowing, nested and variant-declared relations, distinct wrappers, and M:N correlation can still require those physical tables even when their fields are absent from the result.
  • Expose or require the discriminator whenever an MTI field is selected: not chosen because the discriminator is mapping metadata rather than part of the requested result shape; a private alias supplies the mapper and is stripped afterward.
  • Continue projecting every MTI field and strip unselected fields after fetching: not chosen because it would preserve the overprojection and allow unwanted columns into intermediate JSON and distinct-wrapper shapes; enforcing selection in the planner keeps SQL and returned shape aligned.

Checklist

  • All commits are signed off (git commit -s) per the DCO. The DCO status check will block merge if any commit is missing a Signed-off-by: trailer.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated (or n/a if the change is doc-only / refactor with no behavioural delta).
  • The PR title is in TML-NNNN: <sentence-case title> form (Linear ticket prefix + concise title naming the concrete deliverable). See .claude/skills/create-pr/SKILL.md for the full convention.
  • The Skill update section above is filled in (or stated n/a — internal only).

Summary by CodeRabbit

  • Bug Fixes
    • Improved polymorphic/inherited query planning so explicit field selection controls which variant/MTI columns are projected.
    • Prevented internal discriminator and variant columns from leaking into returned records.
    • Ensured correct shapes for polymorphic nested includes and distinct queries.
    • Strengthened runtime connection release handling and polymorphic row mapping.
  • Documentation
    • Updated upgrade guidance to clarify scope and that no public surface changes are introduced.

@tensordreams
tensordreams requested a review from a team as a code owner July 15, 2026 16:10
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: 1cbdd63c-ac54-4536-9907-2623221f4a90

📥 Commits

Reviewing files that changed from the base of the PR and between af12c00 and c2d6bb8.

📒 Files selected for processing (1)
  • test/integration/test/sql-orm-client/polymorphism-variant-include-relationships.test.ts
💤 Files with no reviewable changes (1)
  • test/integration/test/sql-orm-client/polymorphism-variant-include-relationships.test.ts

📝 Walkthrough

Walkthrough

Polymorphic SQL planning now limits MTI projections to selected fields, carries hidden discriminator aliases through distinct and nested queries, and maps aliased discriminators at runtime. Tests cover query plans, row mapping, dispatch, integration behavior, and upgrade documentation.

Changes

Polymorphic projection selection

Layer / File(s) Summary
Runtime discriminator handling
packages/3-extensions/sql-orm-client/src/collection-contract.ts, packages/3-extensions/sql-orm-client/src/collection-runtime.ts, packages/3-extensions/sql-orm-client/test/collection-runtime.test.ts
Defines the discriminator alias, resolves variants from aliased row values, preserves connection release binding, and tests hidden discriminator mapping.
Polymorphic projection selection
packages/3-extensions/sql-orm-client/src/query-plan-select.ts
Computes selected base and MTI fields, filters variant joins, and adds hidden discriminator projections for root and included selects.
Distinct and nested projection forwarding
packages/3-extensions/sql-orm-client/src/query-plan-select.ts, packages/3-extensions/sql-orm-client/test/variant-include.query-plan*.test.ts
Separates visible and inner projections and forwards selected MTI aliases and hidden discriminators through distinct and nested queries.
Selection behavior regression coverage
packages/3-extensions/sql-orm-client/test/query-plan-select.test.ts, packages/3-extensions/sql-orm-client/test/variant-include.collection-dispatch.test.ts, test/integration/test/sql-orm-client/polymorphism-include*.test.ts, skills/extension-author/.../instructions.md
Verifies implicit and explicit MTI selections, returned row shapes, nested includes, direct .all() usage, and internal-only upgrade scope.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: aqrln

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 clearly matches the main change: limiting MTI results to explicitly selected fields.
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.
✨ 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-2783-sql-orm-explicit-select-on-a-polymorphic-include-doesnt

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.

@tensordreams
tensordreams force-pushed the tml-2783-sql-orm-explicit-select-on-a-polymorphic-include-doesnt branch from 1e510bc to 0874b7e Compare July 15, 2026 16:23
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 158.98 KB (+0.43% 🔺)
postgres / emit 132.5 KB (+0.51% 🔺)
mongo / no-emit 98.71 KB (0%)
mongo / emit 89.43 KB (0%)
cf-worker / no-emit 185.14 KB (0%)
cf-worker / emit 155.9 KB (0%)

@pkg-pr-new

pkg-pr-new Bot commented Jul 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/config-loader

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

@prisma-next/emitter

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

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@984

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: c2d6bb8

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
@tensordreams
tensordreams force-pushed the tml-2783-sql-orm-explicit-select-on-a-polymorphic-include-doesnt branch from 0abafe3 to af12c00 Compare July 16, 2026 11:21
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
@aqrln
aqrln added this pull request to the merge queue Jul 16, 2026
Merged via the queue into main with commit 696473f Jul 16, 2026
29 of 30 checks passed
@aqrln
aqrln deleted the tml-2783-sql-orm-explicit-select-on-a-polymorphic-include-doesnt branch July 16, 2026 12:51
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