Skip to content

fix(plan): preserve empty correlated aggregate projections - #26510

Merged
XuPeng-SH merged 16 commits into
matrixorigin:mainfrom
VioletQwQ-0:codex/issue-25959-empty-correlated-agg
Aug 3, 2026
Merged

fix(plan): preserve empty correlated aggregate projections#26510
XuPeng-SH merged 16 commits into
matrixorigin:mainfrom
VioletQwQ-0:codex/issue-25959-empty-correlated-agg

Conversation

@VioletQwQ-0

@VioletQwQ-0 VioletQwQ-0 commented Jul 31, 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 #25959

What this PR does / why we need it:

Correlated scalar aggregates are decorrelated by grouping the inner rows on the pulled-up correlation key and joining the result back with a LEFT JOIN. When an outer key has no inner group, an expression evaluated below that join never runs, so examples such as COALESCE(SUM(v), 0), COUNT(*) + 1, CASE, and the JSON empty-array fallback produce the wrong value.

For the supported direct AGG and PROJECT -> AGG shapes, this change:

  • preserves and validates the scalar final expression before mutating the plan;
  • exposes raw aggregate outputs plus existing pulled-up correlation keys on the join right side;
  • defines the canonical empty-input contract for every current aggregate ID in aggexec and restores typed NULL, zero, or all-bits-set results after null extension;
  • evaluates COALESCE, IFNULL, CASE, arithmetic, or a multi-aggregate final expression above the join;
  • conservatively rejects aggregates with non-trivial unsupported empty state, explicit grouping, HAVING, row-order/row-limit wrappers, deep correlation, and non-aggregate inner-column references without partial plan mutation.

The compatibility oracle for this PR is MySQL 8.0.45, as recorded in #25959. Prisma relationLoadStrategy: join compatibility remains follow-up #24737 scope.

This PR does not modify executor execution behavior, the parser, protobuf, public API, persisted formats, LATERAL, Node_APPLY, mo-auto-test, or Node/npm dependencies.

Validation

  • Exact base: d29b4758f88f0894feeca15f31412446c17cb9d3
  • Exact head: eb983b2a748c8069845b4924680d89df591f3de8
  • Canonical BVT: test/distributed/cases/subquery/scalar_correlated_projection.sql
  • .agents/skills/mo-dev/scripts/mo-cgo-test -count=1 -timeout=300s ./pkg/sql/colexec/aggexec ./pkg/sql/plan: PASS
  • Focused contract, typed-boundary, eligible/rejected-shape, output-mapping, and residual-Expr_Corr tests: PASS
  • GOWORK=off go build -mod=readonly ./pkg/sql/plan/... ./pkg/sql/colexec/aggexec/...: PASS
  • GOWORK=off go vet ./pkg/sql/plan/... ./pkg/sql/colexec/aggexec/...: PASS
  • make build: PASS
  • git diff --check: PASS
  • mo-self-review: PASS, no unresolved findings
  • mo-pr-preflight-review: PASS review=PASS validation=PENDING, diff hash 20cdcad04d6ecfa637ca9c41ab10f0b6296dfc2250e423c2251685b3f4f57ca9
  • New exact-head GitHub CI: PENDING

BVT

BVT: required

The canonical subquery/scalar_correlated_projection case covers empty/present/all-NULL inner inputs, repeated parent keys, SUM/AVG/MIN/MAX, raw SUM, COUNT variants, BIT_AND/OR/XOR, APPROX_COUNT_DISTINCT, arithmetic, multiple aggregates, CASE, JSON fallback, a forwarding CTE, the issue-authentic CTE-internal correlation, and explicit GROUP BY/HAVING controls.

QA

QA required: yes

After merge, verify the original #25959 query and the JSON empty-array fallback on an identified MatrixOne build/environment. Keep #25959 open until the tested version/environment and terminal PASS are recorded.

This is the prerequisite for #24737. Real Prisma 5.22/current relationLoadStrategy: join QA remains PENDING and belongs to the follow-up PR/issue lifecycle; it is not claimed by this PR.

@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 →

@matrix-meow matrix-meow added size/L Denotes a PR that changes [500,999] lines and removed size/M Denotes a PR that changes [100,499] lines labels Aug 3, 2026
@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.

Deep-reviewed exact head fcefaa8a6a5aff7c1d8b0150b7aeefa4904c92da. Requesting changes for one remaining same-shape correctness gap.

P1 — Empty-input reconstruction is incomplete and aggregate-name-specific

prepareCorrelatedScalarAggregatePostJoinProjection hard-codes only sum/avg/min/max/json_arrayagg/count/starcount. For every other aggregate it falls back to the legacy grouped-correlated plan. A missing correlation key then has no right-hand group, so the LEFT JOIN produces NULL even when the aggregate contract requires a non-NULL empty value.

I reproduced this through SQL on a server built from this exact head. The independent empty-input control returned:

BIT_AND(empty) = 18446744073709551615
BIT_OR(empty) = 0
BIT_XOR(empty) = 0
APPROX_COUNT_DISTINCT(empty) = 0

For the equivalent correlated scalar subqueries whose outer key has no matching child row, this head returned NULL for all four. COUNT(empty)=0 and SUM(empty)=NULL were correct controls. Thus the rewrite still violates the core invariant: an implicit scalar aggregate over a correlated empty input must produce the same scalar value as that aggregate over the equivalent uncorrelated empty input.

Please drive post-join reconstruction from a complete canonical aggregate empty-input contract rather than a local name whitelist (or conservatively keep unsupported shapes from producing a wrong plan). Apply it to every aggregate referenced by the final scalar projection, including mixed expressions. Add a public metamorphic regression comparing correlated missing-key results with equivalent uncorrelated empty-input results across both NULL-on-empty and neutral-on-empty aggregate families; include BIT_AND/OR/XOR and APPROX_COUNT_DISTINCT.

Fresh go list, build, vet, focused new regressions, full pkg/sql/plan tests, and the exact-head service build pass; the blocker is semantic coverage, not test instability.

@VioletQwQ-0

Copy link
Copy Markdown
Collaborator Author

Addressed the new empty-input aggregate finding on exact head eb983b2a748c8069845b4924680d89df591f3de8.

  • Added a canonical aggregate-ID empty-result contract in aggexec for all current aggregate families (NULL, typed zero, all bits set, or conservatively unsupported).
  • Applied it to both direct AGG and PROJECT -> AGG correlated scalar shapes, including mixed final expressions.
  • Added typed boundary coverage for uint64, BINARY/VARBINARY width-preserving identities, unknown IDs, and conservative unsupported direct/project paths.
  • Added the requested public metamorphic BVT comparing a correlated missing key with an equivalent uncorrelated empty input for BIT_AND, BIT_OR, BIT_XOR, APPROX_COUNT_DISTINCT, and SUM.

Validation on this exact head:

  • canonical BVT: 40/40, 100%
  • full pkg/sql/colexec/aggexec and pkg/sql/plan CGo package tests: PASS
  • package build and vet: PASS
  • make build: PASS
  • git diff --check: PASS
  • self-review and exact-head preflight: PASS review=PASS validation=PENDING

The prior CI cancellations were superseded by run 30784170502, whose required jobs passed. New 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-reviewed exact head eb983b2a748c. The previous blocker is closed: empty-input reconstruction is now driven by a complete current aggregate-ID contract in aggexec, with NULL/zero/all-bits-set/unsupported outcomes, and is applied to both direct AGG and PROJECT→AGG shapes. BIT_AND/OR/XOR, APPROX_COUNT_DISTINCT, mixed expressions, typed uint64/binary identities, and conservative unsupported paths are covered; the public regression compares correlated missing-key results with equivalent uncorrelated empty inputs.

Fresh exact-head evidence: full pkg/sql/colexec/aggexec and pkg/sql/plan tests pass, controlled build/vet pass, diff check passes, and GitHub CI is green. No remaining blocker found.

@mergify

mergify Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-08-03 09:16 UTC · Rule: main · triggered by rule Automatic queue on approval for main
  • 🟠 Checks running · in-place
  • 🚫 Left the queue2026-08-03 10:00 UTC · at 55203b022b21cc79c73b64e2b65c032e82c41725

This pull request spent 43 minutes 39 seconds in the queue, with no time running CI.

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

Reason

Pull request #26510 has been dequeued

Pull request from fork cannot be queued. This pull request comes from a fork, and Mergify needs the author's permission to update its branch.

The author needs to enable "Allow edits from maintainers" on this pull request.

Failing checks:

Hint

You should look at the reason for the failure and decide if the pull request needs to be fixed or if you want to requeue it.
If you do update this pull request, it will automatically be requeued once the queue conditions match again.
If you think this was a flaky issue instead, you can requeue the pull request, without updating it, by posting a @mergifyio queue comment.

Tick the box to put this pull request back in the merge queue (same as @mergifyio queue).

  • Requeue this pull request

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

Labels

dequeued 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