Skip to content

TML-2979: Scope MTI variant predicates in count writes#940

Open
tensordreams wants to merge 1 commit into
mainfrom
tml-2979-sql-orm-updatecountdeletecount-compile-mti-variant-table
Open

TML-2979: Scope MTI variant predicates in count writes#940
tensordreams wants to merge 1 commit into
mainfrom
tml-2979-sql-orm-updatecountdeletecount-compile-mti-variant-table

Conversation

@tensordreams

@tensordreams tensordreams commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Refs TML-2979

Summary

updateCount and deleteCount now compile MTI variant-narrowed predicates through a correlated joined subquery instead of placing variant-table references directly in the outer write. The matching count read also keeps variant context so it counts the same joined row set that the write targets.

At a glance

const count = await mixedPoly
  .variant('Feature')
  .where((task) => task.priority.gt(1))
  .updateCount({ title: 'Queued' });

Before this change, the accepted predicate could compile into a bare UPDATE ... WHERE features.priority > ... without features in scope.

Decision

This PR ships correlated-subquery planning for MTI variant-narrowed updateCount and deleteCount.

The count read propagates variantName and modelName into compileSelect, so the existing read planner joins the selected MTI table. The count write detects MTI variant narrowing and wraps the original filters in WHERE EXISTS (...), where the inner query aliases the base table, joins the variant table, and correlates back to the outer write by primary key.

Reviewer notes

  • The important behavior lives in compileUpdateCount / compileDeleteCount; non-variant and STI paths intentionally keep the old plain-filter shape.
  • The write plan uses one SQL statement and avoids materialising matching primary keys in JS.
  • The new tests avoid adding bare as casts; the package lint still prints existing no-bare-cast infos from older production files, but it exits successfully.

How it fits together

  1. Collection.updateCount and Collection.deleteCount build a matching primary-key read before the write. That read now carries variantName and modelName into compileSelect, allowing the normal MTI join machinery to join features.
  2. query-plan-mutations adds buildCountMutationWhere, which only switches behavior when both a selected variant and model name identify an MTI variant.
  3. The MTI write filter creates an inner base-table alias, rewrites base-table references in the original filters to that alias, joins the selected variant table, and correlates the inner row to the outer write target on the primary key.
  4. Scalar variant fields and variant-declared relation predicates both work because their features.* references now live inside the joined subquery scope.

Behavior changes & evidence

Compatibility / migration / risk

No public API or extension-author migration change. The SQL shape changes only for MTI variant-narrowed updateCount and deleteCount; plain, non-variant, and STI count writes keep the existing direct WHERE shape.

Testing performed

  • pnpm --filter @prisma-next/sql-orm-client typecheck
  • pnpm --filter @prisma-next/sql-orm-client test -- collection-variant.test.ts
  • pnpm --filter @prisma-next/sql-orm-client test -- query-plan-mutations.test.ts
  • pnpm --filter @prisma-next/sql-orm-client lint
  • pnpm check:upgrade-coverage --mode pr
  • git diff --check

Skill update

n/a — internal bug fix; no user-facing commands, flags, public TypeScript API, error codes, or glossary terms changed.

Alternatives considered

  • Materialise matching primary keys before writing: this would reuse the joined read path directly, but it changes bulk count writes into a read-then-write-by-key workflow and can move an unbounded matching set through JS.
  • Add UPDATE ... FROM / joined delete support to the mutation AST: this would make the join explicit in the write AST, but it broadens the shared mutation AST and renderer surface for a narrow bug fix. The correlated subquery fits the existing AST.

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

Notes for the reviewer

The branch intentionally does not add an extension-author upgrade note; pnpm check:upgrade-coverage --mode pr passes and this is an internal planner bug fix.

Summary by CodeRabbit

  • Bug Fixes
    • Improved count-based update and delete operations so they respect the selected variant and return more accurate results for polymorphic data.
    • Fixed filtering behavior in variant-scoped write actions, ensuring related conditions are applied correctly during count operations.
    • Updated coverage for variant-based update/delete scenarios to help prevent regressions.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
@tensordreams
tensordreams requested a review from a team as a code owner July 9, 2026 16:24
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Update/delete count operations now propagate variantName and modelName context through Collection methods into compileUpdateCount/compileDeleteCount. A new WHERE-building helper generates EXISTS-based correlated subqueries for multi-table-inheritance (mti) polymorphism, replacing plain filter combination. New tests validate this variant-scoped behavior.

Changes

MTI-aware count mutation scoping

Layer / File(s) Summary
Table remapping and EXISTS-based WHERE builder
packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts
Adds createTableRefRemapper and buildCountMutationWhere, producing a correlated EXISTS subquery for mti polymorphism strategy or falling back to combined filters, with expanded AST imports.
compileUpdateCount/compileDeleteCount signature updates
packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts
Both functions gain optional variantName/modelName parameters and use buildCountMutationWhere(...) instead of combineWhereExprs(filters).
Collection.updateCount/deleteCount variant wiring
packages/3-extensions/sql-orm-client/src/collection.ts
countState, compileSelect, and the compileUpdateCount/compileDeleteCount calls now include variantName and this.modelName.
Variant-scoped write tests
packages/3-extensions/sql-orm-client/test/collection-variant.test.ts
Adds ExistsExpr/SelectAst imports, join/EXISTS assertion helpers, feature MTI predicate interfaces, and tests verifying features scoping in updateCount/deleteCount under variant('Feature').

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

Suggested reviewers: aqrln

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% 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 summarizes the main change: scoping MTI variant predicates in count write operations.
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-2979-sql-orm-updatecountdeletecount-compile-mti-variant-table

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.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 153.37 KB (+0.13% 🔺)
postgres / emit 128.7 KB (+0.15% 🔺)
mongo / no-emit 98.36 KB (0%)
mongo / emit 89.39 KB (0%)
cf-worker / no-emit 180.2 KB (0%)
cf-worker / emit 153.16 KB (0%)

@pkg-pr-new

pkg-pr-new Bot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/config-loader

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

@prisma-next/emitter

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

@prisma-next/language-server

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: c716b3c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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-mutations.ts`:
- Around line 390-400: Thread MTI context through
`#executeDeleteReturningWithIncludes` and the delete-all include path:
`deleteAll()` is still calling `compileDeleteCount()` without `variantName` and
`modelName`, which lets MTI deletes bypass the `EXISTS` filter. Update the call
chain so `variantName`/`modelName` are passed into `compileDeleteCount()` from
`#executeDeleteReturningWithIncludes`, and ensure the delete mutation continues
to route through `buildCountMutationWhere()` with the MTI predicates intact.
🪄 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: 88d9cf7a-387e-45b8-a54a-9d2af99a5a0c

📥 Commits

Reviewing files that changed from the base of the PR and between 9175ebb and c716b3c.

📒 Files selected for processing (3)
  • packages/3-extensions/sql-orm-client/src/collection.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts
  • packages/3-extensions/sql-orm-client/test/collection-variant.test.ts

Comment on lines +390 to +400
variantName?: string | undefined,
modelName?: string | undefined,
): SqlQueryPlan<Record<string, unknown>> {
const where = combineWhereExprs(filters);
const where = buildCountMutationWhere(
contract,
namespaceId,
tableName,
filters,
variantName,
modelName,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify remaining compileDeleteCount callers pass variant/model context when collection state is available.
rg -n -C4 '\bcompileDeleteCount\s*\(' packages/3-extensions/sql-orm-client/src

Repository: prisma/prisma-next

Length of output: 3559


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '385,430p' packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts
printf '\n---\n'
sed -n '2068,2165p' packages/3-extensions/sql-orm-client/src/collection.ts
printf '\n---\n'
sed -n '1028,1042p' packages/3-extensions/sql-orm-client/src/mutation-executor.ts

Repository: prisma/prisma-next

Length of output: 4573


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts
printf '\n---\n'
rg -n -C3 '\bdeleteAll\b|`#executeDeleteReturningWithIncludes`|compileDeleteCount\(' packages/3-extensions/sql-orm-client/src/collection.ts packages/3-extensions/sql-orm-client/src/mutation-executor.ts

Repository: prisma/prisma-next

Length of output: 14610


Thread MTI context through #executeDeleteReturningWithIncludes.

deleteAll() with includes still calls compileDeleteCount() without variantName/modelName, so MTI deletes can skip the EXISTS predicate and apply the outer DELETE directly to variant-table columns.

🤖 Prompt for 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.

In `@packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts` around
lines 390 - 400, Thread MTI context through `#executeDeleteReturningWithIncludes`
and the delete-all include path: `deleteAll()` is still calling
`compileDeleteCount()` without `variantName` and `modelName`, which lets MTI
deletes bypass the `EXISTS` filter. Update the call chain so
`variantName`/`modelName` are passed into `compileDeleteCount()` from
`#executeDeleteReturningWithIncludes`, and ensure the delete mutation continues
to route through `buildCountMutationWhere()` with the MTI predicates intact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant