Skip to content

fix(plan): support Prisma correlation through transparent derived tables - #26672

Merged
mergify[bot] merged 5 commits into
matrixorigin:mainfrom
VioletQwQ-0:codex/fix-24737-prisma-derived-correlation
Aug 4, 2026
Merged

fix(plan): support Prisma correlation through transparent derived tables#26672
mergify[bot] merged 5 commits into
matrixorigin:mainfrom
VioletQwQ-0:codex/fix-24737-prisma-derived-correlation

Conversation

@VioletQwQ-0

@VioletQwQ-0 VioletQwQ-0 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

What type of PR is this?

  • API-change
  • BUG
  • Improvement
  • Documentation
  • Feature
  • Test and CI
  • Code Refactoring

Which issue(s) this PR fixes:

issue #24737

What this PR does / why we need it:

Prisma 7.9.1 loads MySQL to-many relations with a correlated scalar aggregate over several transparent derived tables. MatrixOne rejected that valid shape with 20102: correlated subquery in FROM clause is not yet implemented.

This PR:

  • normalizes correlation only through unary PROJECT/FILTER chains ending in one TABLE_SCAN;
  • validates the complete chain before changing any CorrColRef depth or parent correlation state;
  • distinguishes a binderless derived-build context from a bound query scope by binder state and binding-tag ownership, including bound scopes with an empty FROM, so this does not implicitly add LATERAL or skip a real correlation level;
  • rejects scan block filters because the existing pull-up path cannot safely consume them;
  • routes both ordinary and numeric-projection derived-table entry points through the same normalization;
  • reuses the existing scalar-aggregate LEFT JOIN decorrelation and the empty-input fallback merged by fix(plan): preserve empty correlated aggregate projections #26510;
  • keeps JOIN, GROUP/HAVING, DISTINCT, WINDOW, SET OP, ORDER/LIMIT/OFFSET, OR, non-equality, projected correlation, and same-FROM correlation rejected with the existing NYI boundary.

Relation-local orderBy/take/skip remains out of scope and requires later per-parent Top-N work.

Base/head evidence

  • PR fix(plan): preserve empty correlated aggregate projections #26510 merge commit: d6800b98855b871459c3c4bad0ce73aa7321610f
  • Exact base: 21908973304c4117ee2f19670d1d8acd218cd0fb
  • Exact head: 71996f78f47dd697efd850ba54b2e1af40249f1d
  • Base target BVT: exit 1, 46/49; the raw Prisma SQL, local-filter variant, and derived COUNT all returned correlated subquery in FROM clause is not yet implemented.
  • Head target BVT: exit 0, 54/54.

Validation

  • PASS: focused TestTransparentCorrelatedDerivedTable and TestPreparedNumericAggregateReachesCorrelatedDerivedTable CGo planner tests.
  • PASS: .agents/skills/mo-dev/scripts/mo-cgo-test -count=1 -timeout=120s ./pkg/sql/plan/....
  • PASS: GOWORK=off go vet -mod=readonly ./pkg/sql/plan/....
  • PASS: make build on the exact head.
  • PASS: isolated target BVT scalar_correlated_projection, exit 0, 54/54, including empty-intermediate and both mixed empty/non-empty ancestor orderings.
  • PASS: BVT JSON cardinality assertions cover raw Prisma 0/1/many and local-filter 0/0/2 results without depending on unordered aggregate payload snapshots.
  • PASS: git diff --check.
  • PASS: mo-self-review with zero unresolved blockers.
  • PASS: preflight exact-head gate: PASS review=PASS validation=PENDING exact-head=71996f78f47dd697efd850ba54b2e1af40249f1d diff-hash=1c2010bc41e371f7e0e9680cf97334c46016527b145f7ca41f26072f502b3287.
  • PASS: temporary Prisma Client and @prisma/adapter-mariadb 7.9.1 fixture; the raw generated SQL executed unchanged and relationLoadStrategy: join decoded the same 0/1/many and nullable-field objects as query (DEEP_EQUAL=true).
  • PASS: clean rebase onto upstream/main 4b01f8b4bc36e461a48f09d191df4d8637ee3591, followed by the CR repair focused ancestor-scope planner matrix, full ./pkg/sql/plan/... CGo tests, go build, go vet, make build, git diff --check, mo-self-review, and exact-head preflight (PASS review=PASS validation=PENDING).
  • PASS: current upstream/main 6091b418fd529badca4e1855843bd38804de396f keeps the reported empty-intermediate SQL on the safe correlated subquery in FROM clause is not yet implemented boundary.
  • PENDING: required CI on the pushed exact head.
  • PENDING: MySQL 8.0.46 and deployment-environment QA handoff; the issue reproduction records MySQL PASS.

Test requirements

  • BVT: required
  • QA required: yes

The temporary Node/npm fixture is not committed, and this PR does not modify mo-auto-test.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@XuPeng-SH XuPeng-SH 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.

Blocking: keep deeper ancestor correlations fail-closed.

analyzeTransparentDerivedFilter accepts every positive CorrColRef.Depth unless a depth-1 tag is owned by the current FROM context. That also accepts a transparent derived filter whose correlation crosses a real intermediate binding scope. After the unconditional depth decrement, the existing deep-correlation guard is bypassed and optimization receives a dangling outer column.

Minimal public planner witness:

SELECT n1.N_NATIONKEY
FROM NATION n1
WHERE EXISTS (
  SELECT 1 FROM NATION n2
  WHERE n2.N_NATIONKEY = n1.N_NATIONKEY
    AND EXISTS (
      SELECT 1 FROM (
        SELECT n3.N_NATIONKEY FROM NATION n3
        WHERE n3.N_NATIONKEY = n1.N_NATIONKEY
      ) d
    )
);

On exact head f1aee5c9905366e4c71ad05042faed1a7b30cbbe, BuildPlan reaches optimizer validation and fails with Column remapping failed: cannot find column reference, missing RelPos=6 in a FILTER. The immediate-ancestor control (n3.N_REGIONKEY = n2.N_REGIONKEY) succeeds. The same SQL on base 21908973304c4117ee2f19670d1d8acd218cd0fb remains at correlated subquery in FROM clause is not yet implemented, and the equivalent direct deep-correlation control remains at the existing deep correlated predicate containing inner columns ... is not yet implemented boundary. A mixed immediate/grandparent predicate fails the same way.

Please either prove and implement the downstream deep-correlation/remapping closure, or atomically reject correlations that cross a non-transparent ancestor scope before mutating depths/context. Add immediate-parent, grandparent-only, and mixed-owner controls so the intended Prisma chain stays supported without broadening this unsafe state.

The submitted package tests, full ./pkg/sql/plan/... test suite, build, vet, and diff check otherwise pass.

@VioletQwQ-0

Copy link
Copy Markdown
Collaborator Author

Fixed on exact head aa68b1bced76444c76662ffadfdf2e34d50fd179.

The normalizer now accepts a correlation only when its binding tag belongs to the nearest non-empty ancestor scope. This preserves the intended immediate-parent Prisma chain while rejecting grandparent-only and mixed immediate/grandparent correlations before any CorrColRef.Depth or parent-context mutation.

Added public planner controls for immediate-parent success plus grandparent-only and mixed-owner NYI rejection, and a synthetic depth-3 atomicity assertion. Focused tests, the full ./pkg/sql/plan/... CGo suite, go vet, git diff --check, self-review, and exact-head preflight passed. Required CI is now pending on the new head.

@XuPeng-SH XuPeng-SH 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.

Deep-reviewed exact head caa830c. The previous non-empty intermediate-scope grandparent and mixed-owner witnesses are now rejected atomically, and the intended immediate-parent Prisma shape still passes. The same scope-ownership defect remains when the intervening query block has no FROM bindings.

P1 — Empty intermediate query scopes are incorrectly treated as binderless transparent wrappers

transparentDerivedCorrelationTargetsNearestAncestor walks ancestors and stops only when len(ancestor.bindingByTag) > 0. An empty SELECT query block has an empty bindingByTag, but it is still a real correlation/decorrelation level. Skipping it accepts a grandparent correlation, decrements CorrColRef.Depth, and again hands the optimizer a dangling outer column.

Public witness on this exact head:

SELECT n1.N_NATIONKEY
FROM NATION n1
WHERE EXISTS (
SELECT 1
WHERE EXISTS (
SELECT 1 FROM (
SELECT n3.N_NATIONKEY FROM NATION n3
WHERE n3.N_NATIONKEY = n1.N_NATIONKEY
) d
)
);

Head result: BuildPlan reaches optimizer validation and fails with Column remapping failed: cannot find column reference, missing RelPos=6 in a FILTER. Exact base b4a6da6 rejects the same SQL at the safe correlated subquery in FROM clause is not yet implemented boundary. The immediate-parent n2 control added by this PR succeeds.

Please distinguish a genuinely binderless derived wrapper from a binder-backed query scope that merely has no table bindings, or otherwise fail closed before changing depth/context. Add empty-intermediate and mixed empty/non-empty ancestor controls so every query-scope shape is covered, not only scopes with bindingByTag entries.

Unhappy-path audit: Q1 correlation ownership crosses a real empty scope and is transferred to the wrong ancestor; Q2 the failure surfaces later as an optimizer remap error after normalization rather than at the supported NYI boundary; Q3 no unbounded-growth or resource-lifecycle risk was found. Existing focused positive/negative planner tests, their race run, plan vet, diff check, and CI pass; this public counterexample is missing.

@VioletQwQ-0

Copy link
Copy Markdown
Collaborator Author

Fixed on exact head 71996f78f47dd697efd850ba54b2e1af40249f1d.

The ancestor-ownership check now treats every context with an installed binder as a real correlation level, even when that query block has no FROM bindings. It continues through only pre-binder derived-build contexts, matching baseBindColRef name-resolution traversal. Rejection still happens during analysis, before any CorrColRef.Depth or parent isCorrelated mutation.

Coverage added:

  • the reported empty-intermediate public SQL;
  • both empty-then-non-empty and non-empty-then-empty ancestor orderings;
  • a typed atomicity case proving a binder-backed empty scope leaves depth and parent state unchanged;
  • production BVT assertions for all three rejected shapes at the existing correlated subquery in FROM clause is not yet implemented boundary.

Validation on the exact head:

  • focused transparent-derived and prepared-numeric planner tests: PASS;
  • full .agents/skills/mo-dev/scripts/mo-cgo-test -count=1 -timeout=120s ./pkg/sql/plan/...: PASS;
  • GOWORK=off go build -mod=readonly ./pkg/sql/plan/...: PASS;
  • GOWORK=off go vet -mod=readonly ./pkg/sql/plan/...: PASS;
  • make build: PASS;
  • isolated scalar_correlated_projection BVT through mo-service/mo-tester: PASS, 54/54;
  • current upstream/main 6091b418fd529badca4e1855843bd38804de396f: reported SQL safely returns the existing NYI;
  • git diff --check: PASS;
  • whole-diff mo-self-review: PASS, zero unresolved blockers;
  • preflight: PASS review=PASS validation=PENDING, diff hash 1c2010bc41e371f7e0e9680cf97334c46016527b145f7ca41f26072f502b3287.

Fresh exact-head CI is now pending.

@XuPeng-SH XuPeng-SH 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.

Re-review of 71996f7: the empty-scope blocker is resolved.\n\nThe new boundary matches column binding semantics: build-time contexts without a binder remain transparent, while every binder-backed query block is treated as a real correlation level even when bindingByTag is empty. The ownership check runs during analysis, before depth/context mutation, so rejected deep or mixed-owner shapes remain atomic and fail at the existing NYI boundary rather than reaching optimizer remapping.\n\nFocused local validation passed for:\n- intended Prisma/nested transparent-derived success shapes\n- same-scope lateral, unsupported-plan, grandparent, mixed-owner, empty/non-empty ordering rejections\n- typed depth/context atomicity\n- prepared numeric assignment propagation\n- additional double-empty-scope rejection and multi-wrapper nearest-owner success counterexamples\n\nNo blocking correctness issue found.

@mergify mergify Bot added the queued label Aug 4, 2026
@mergify

mergify Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-08-04 11:05 UTC · Rule: main · triggered by rule Automatic queue on approval for main
  • Checks passed · in-place
  • Merged2026-08-04 12:22 UTC · at 52f59dd80ab2422613cf47ce228eac8f39697ed7 · squash

This pull request spent 1 hour 17 minutes 1 second in the queue, including 47 minutes 59 seconds running CI.

Required conditions to merge
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-approved [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / SCA Test on Linux/arm64
    • check-neutral = Matrixone CI / SCA Test on Linux/arm64
    • check-skipped = Matrixone CI / SCA Test on Linux/arm64
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PROXY)
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PROXY)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PROXY)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Utils CI / Coverage
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone UT Coverage / UT Coverage on Ubuntu/x86
    • check-neutral = Matrixone UT Coverage / UT Coverage on Ubuntu/x86
    • check-skipped = Matrixone UT Coverage / UT Coverage on Ubuntu/x86

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

Labels

kind/bug Something isn't working size/L Denotes a PR that changes [500,999] lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants