Repository navigation
Conversation
Keep a Result node when replacing its child's target list would drop a SubPlan reference after Motion slices have been allocated. Build the proposed projection on copies so rejecting the fold leaves the child expressions intact. Add regression coverage for EXISTS and NOT EXISTS with nullable outer joins, LIMIT/OFFSET, and multiple unused SubPlans.
An outer Apply introduced below grouping for an existential or quantified subquery can have a multirow inner input. If decorrelation fails, implementing it as a scalar SubPlan raises a spurious cardinality violation before the grouping can run. Only generate that scalar correlated alternative when the inner input is proven to return at most one row. Keep the correlated alternatives generated from the original subquery. Add regression coverage for nullable FULL JOIN correlations, LIMIT/OFFSET, quantified subqueries, and genuine scalar cardinality errors.
| * plan tree. Keep the Result unless all child SubPlans survive. | ||
| */ | ||
| if (!preserves_subplans(child_plan->targetlist, tlist)) | ||
| return node; |
There was a problem hiding this comment.
return node here bypasses the plan_tree_mutator() call at the bottom of remove_redundant_results_mutator(), so when the fold is rejected nothing ever walks this Result's own target list. Redundant Result nodes inside the plan trees of the SubPlans referenced from that target list are therefore no longer removed (plan_tree_mutator() is called with recurse_into_subplans = true).
Before this patch, every path that kept a Result fell through to plan_tree_mutator() — the if body always eliminated the Result — so this is a newly introduced behavioural difference rather than a pre-existing one. The child plan itself is fine: it is walked explicitly a few lines above.
It is only a missed optimization and not a correctness problem, so feel free to leave it; but it would be nice to either fall through to plan_tree_mutator() on this path (keeping the Result and avoiding the double recursion into child_plan), or to walk the SubPlans of result_plan->plan.targetlist explicitly before returning.
Fixes #2221
What does this PR do?
Fix two planning errors for correlated EXISTS/NOT EXISTS queries containing LIMIT/OFFSET and a nested scalar subquery:
Example 1: missing MotionState
This is the minimal reproduction from #2221:
The only value of
y.idis 1, soy.id IS NULLis false and the query must return zero rows.Before the fix, with optimizer=on:
After the fix, with optimizer=on, and with optimizer=off on either build:
Before the fix — EXPLAIN (COSTS OFF), optimizer=on
After the fix — EXPLAIN (COSTS OFF), optimizer=on
When removing a Result node, the planner loses the reference to SubPlan 1, which computes count(*).
The executor still expects this subquery’s Motion slices, but cannot find them in the execution tree.
The fix keeps the Result node and SubPlan 1, so the query runs successfully and returns the expected zero rows.
Example 2: a scalar cardinality error inside EXISTS
Run this example in a fresh connection:
For
r.id = 10,r.k = 7matches two rows incard_s.This is valid: EXISTS tests whether any row exists. The nested
SELECT count(*) FROM card_sisa separate scalar subquery returning exactly one row; it does not make the surrounding EXISTS input scalar.
LIMIT NULL OFFSET NULLpreserves the input rows.Before the fix, with optimizer=on:
After the fix, with optimizer=on, and with optimizer=off on either build:
Rows are sorted here for presentation, with NULL displayed explicitly. The example has no ORDER BY; results were compared as multisets.
Before the fix — EXPLAIN (COSTS OFF), optimizer=on
After the fix — EXPLAIN (COSTS OFF), optimizer=on
The failing alternative introduces an outer Apply below a grouping operation. When decorrelation fails, ORCA can implement this Apply as a scalar SubPlan even though its inner input can produce multiple rows. The resulting scalar cardinality check fails before the grouping above it can run.
The fix allows this scalar plan only when ORCA can prove that the input returns at most one row, otherwise, ORCA can choose a plan that handles EXISTS correctly. In this example, the query succeeds and the outer HashAggregate is no longer needed.
Implementation
The changes are split into two commits:
orca.c, preserve every SubPlan referenced by a child's target list when folding Result projections. Build the proposed replacement on copies and keep the Result if folding would lose a reference.CXformApply2Join, restrict scalar correlated outer-Apply generation to inner inputs whose maximum cardinality is at most one. Other correlated alternatives remain available, and genuine scalar cardinality violations remain errors.Both fixes are required: the cardinality fix removes the incorrect 21000 error, but the new plan still fails with a missing MotionState unless the other fix is also applied.
The regression tests cover correlated EXISTS and NOT EXISTS queries with outer joins, NULL values, and LIMIT/OFFSET. They also check nested subqueries, ANY/ALL, and scalar aggregates, and verify that scalar subqueries still raise an error when they return more than one row.
Type of Change
Test Plan
orca_motion_subplanandorca_exists_cardinalitytogreenplum_schedule, outside the parallel group containingbfv_temp.optimizer=off/optimizer=onexecutions: all 12 SELECT pairs return identical expected rows. A genuine scalar cardinality error is also checked in both modes.make installcheckmake -C src/test installcheck-cbdb-parallelChecklist