Repository navigation
Conversation
b8ffb75 to
844ed55
Compare
6ef3f63 to
04d815b
Compare
04d815b to
1355a5c
Compare
d2fb03f to
be74b99
Compare
9781842 to
a8fda51
Compare
|
Updated commit - fixed comment explanation. |
69f7031 to
c6bb3e1
Compare
|
Found the bug, reproduction: The original code in the pr assumed that every aggregate except count() returns NULL on empty input. This is incorrect for aggregates such as regr_count(), which returns 0, and user-defined aggregates with a non-NULL initial value. test_outer contains rows 1 and 2, while test_inner contains only 1. |
c6bb3e1 to
1b6bff1
Compare
43ce9d9 to
ee4883c
Compare
|
Since my previous comment, I’ve updated the patch to preserve when expressions around aggregates are evaluated, retain decorrelation through The examples below use actual Common SQL setupCommon setup: SET optimizer_enable_master_only_queries = on;
SET optimizer_segments = 3;
SET optimizer_nestloop_factor = 1.0;
CREATE TABLE demo_outer (a int) DISTRIBUTED BY (a);
CREATE TABLE demo_inner (a int, b int) DISTRIBUTED BY (a);
INSERT INTO demo_inner VALUES (1, 10), (1, 20), (2, NULL);
ANALYZE demo_inner;1. Expressions around aggregates are evaluated in the appropriate CASE branch above the join. The reported division-by-zero error also involved evaluating the expression for an unused inner group. The grouped inner side now computes individual aggregate values and a match flag. The surrounding expression is evaluated after joining to the outer row. SQL, PG/ORCA plans and resultsSELECT o.a
FROM demo_outer o
WHERE o.a < (
SELECT 100 / coalesce(sum(i.b), 0)
FROM demo_inner i
WHERE i.a = o.a
);The following are four independent tests of the same SELECT above. Each setup replaces the rows in Run the SELECT above after each setup. The results below were verified with both Test 1 — no outer rows. TRUNCATE demo_outer;Result of the SELECT: There is no outer row that needs the expression, so the query must finish without a division-by-zero error. Test 2 — one outer row, with TRUNCATE demo_outer;
INSERT INTO demo_outer VALUES (1);Result of the SELECT: For this outer row, the matching inner values are Test 3 — two outer rows, with TRUNCATE demo_outer;
INSERT INTO demo_outer VALUES (1), (2);Result of the SELECT: The outer row with Test 4 — two outer rows, with TRUNCATE demo_outer;
INSERT INTO demo_outer VALUES (1), (3);Result of the SELECT: There is no inner row with Tests 1 and 2 reproduce the cases that previously raised an unwanted error in the comment. Tests 3 and 4 check that the patch still reports division by zero when the original query requires it. The plans below are for Test 2. Restore its outer-table contents first: TRUNCATE demo_outer;
INSERT INTO demo_outer VALUES (1);
ANALYZE demo_outer;Current ORCA plan ( Current PG plan ( The division is in the join filter’s 2. The existing correlated execution path was adapted to the new expression placement. SQL, ORCA plan and resultWith SELECT o.a
FROM demo_outer o
WHERE o.a + 4 IN (
SELECT count(*) + 100 / coalesce(sum(i.b), 0)
FROM demo_inner i
WHERE i.a <= o.a
);Current ORCA plan: Result with both optimizer settings: This query still uses a Additional data for examples 3–5For the remaining examples, add the all-NULL and missing groups on the outer side: INSERT INTO demo_outer VALUES (2), (3);
ANALYZE demo_outer;3. ORCA preserves decorrelation through HAVING while evaluating the target expression only for accepted rows. HAVING: SQL, ORCA plan and resultsSELECT o.a
FROM demo_outer o
WHERE 0 < (
SELECT 100 / count(*)
FROM demo_inner i
WHERE i.a = o.a
HAVING count(*) > 0
);Current ORCA plan: Result with both optimizer settings: The SELECT o.a,
(SELECT 100 / count(*)
FROM demo_inner i
WHERE i.a = o.a
HAVING count(*) > 0) AS quotient
FROM demo_outer o
ORDER BY o.a;The PG planner continues to use a 4. Strict comparisons with numeric and bigint AVG/SUM retain INNER joins. AVG: SQL, PG/ORCA plans and resultSELECT o.a
FROM demo_outer o
WHERE 40 > (
SELECT avg(i.b::numeric)
FROM demo_inner i
WHERE i.a = o.a
);Current ORCA plan: Current PG plan: Result with both optimizer settings: Both plans use an ordinary 5. A separate commit replaces the empty-input value of hypothetical rank with 1 when its direct arguments are constants. rank: SQL, PG/ORCA plans and resultSELECT o.a
FROM demo_outer o
WHERE o.a > (
SELECT rank(5) WITHIN GROUP (ORDER BY i.b)
FROM demo_inner i
WHERE i.a = o.a
);Before this optimization, the PG regression plan calculated the empty-input value through an InitPlan, with Current PG plan: Current ORCA plan: Result with both optimizer settings: For Local All three include |
0cfd8ce to
96df653
Compare
195ae1f to
3328ec4
Compare
|
I'm investigating why regression tests failed again and I'm going to move code with Orca fix in separate pr. I'll let you know when this pr will be ready for review |
bae3e0f to
96df653
Compare
96df653 to
b2a8e44
Compare
b2a8e44 to
9c1d080
Compare
Extract catalog-based empty-input constants and the NULL-result and safety checks from the PG pull-up patch. Include the built-in hypothetical rank constant handling. This commit introduces the helpers without changing PG subquery pull-up behavior. Partly cherry-pick from PR apache#1933
Extract catalog-based empty-input constants and the NULL-result and safety checks from the PG pull-up patch. Include the built-in hypothetical rank constant handling. This commit introduces the helpers without changing PG subquery pull-up behavior. Partly cherry-pick from PR apache#1933
With the Postgres planner, pulling up a correlated scalar aggregate subquery into an INNER join can discard outer rows whose subquery has empty input, even when the comparison against its empty-input value would pass. Use a LEFT join when such rows can pass. Add a match flag and evaluate the comparison above the join, choosing the actual expression for a matched group and its empty-input value otherwise. Retain an INNER join when the empty-input comparison folds to FALSE or NULL. Keep only grouping keys, the match flag, and aggregate values in the grouped subquery. Evaluate expressions around aggregates in the matched CASE branch above the join, so an unused inner group cannot cause a division-by-zero error. Mark their inputs with the LEFT join's nulling relid and keep nested subqueries at their original query level. Derive aggregate empty-input constants from pg_aggregate, including initial transition values and foldable final functions. For cases that need executor evaluation, construct empty-input aggregate sublinks that become InitPlans. Within compound expressions, avoid introducing non-NULL constants, or NULL constants under nonstrict expressions, that could trigger errors such as division by zero during planning. Do not fold user-defined final functions or invoke them speculatively through empty-input InitPlans. Retain correlated execution unless the aggregate's final function is safe on empty input. Expose these checks for the ORCA metadata translator as well. Keep unsupported window functions, correlated targetlists, and clauses whose Vars are unavailable at the join attachment point as SubPlans. Allow nested subqueries inside aggregate arguments and FILTER: these remain at their original query level in the grouped subquery. Retain correlated execution for SubLinks in the surrounding expression. Recognize NULL results of the built-in numeric and int8 AVG/SUM final functions on a NULL internal state. Retain INNER joins for strict comparisons with these aggregates, without an empty-input InitPlan. Use an int8 constant of 1 for the empty-input value of built-in hypothetical rank when all direct arguments are constants. Avoid an empty-input InitPlan, retaining the LEFT join and the CASE that selects the value for unmatched outer rows. Require constant direct arguments so that replacing the aggregate does not suppress their evaluation or errors. Keep the existing safeguards for expressions around aggregates. Add PG regression coverage for empty and matching outer inputs, multiple aggregates, NULL and non-NULL defaults, and unsafe final functions, including parallel execution. Refresh affected PG plans in subselect_gp, eagerfree and the PAX answer files. Updated from open-gpdb/cloudberry commit ad62f72 (PR apache#89), originally adapted from open-gpdb/gpdb#397 and GreengageDB/greengage#546. Co-Authored-By: excaliiibur <[email protected]> Backpatch-through: REL_2_STABLE
9c1d080 to
66e1223
Compare
Extract catalog-based empty-input constants and the NULL-result and safety checks from the PG pull-up patch. Include the built-in hypothetical rank constant handling. This commit introduces the helpers without changing PG subquery pull-up behavior. Partly cherry-pick from PR apache#1933
|
Moved bug fix for orca in #2127. It is ready for review |
Extract catalog-based empty-input constants and the NULL-result and safety checks from the PG pull-up patch. Include the built-in hypothetical rank constant handling. Recognize the NULL empty-input result of built-in array, string and JSON aggregates with non-strict internal-state final functions. Do not infer a constant for an ordered-set aggregate when the direct arguments were not supplied by an OID-only caller. This commit introduces the helpers without changing PG subquery pull-up behavior. Partly cherry-pick from PR apache#1933
Extract catalog-based empty-input constants and the NULL-result and safety checks from the PG pull-up patch. Include the built-in hypothetical rank constant handling. Recognize the NULL empty-input result of built-in array, string and JSON aggregates with non-strict internal-state final functions. Do not infer a constant for an ordered-set aggregate when the direct arguments were not supplied by an OID-only caller. This commit introduces the helpers without changing PG subquery pull-up behavior. Partly cherry-pick from PR apache#1933
What does this PR do?
Fix incorrect results in Cloudberry's PostgreSQL planner (
optimizer = off, or an ORCA fallback) when a correlated scalar aggregate subquery inWHEREis pulled up into a join.Previously,
convert_EXPR_to_join()could use an inner join and discard outer rows with no matching inner input, even when the comparison against the aggregate's empty-input value should pass.For example:
Before the fix, this query returns no rows.
Before the fix - the query returns no rows:
With the fix, it returns:
After the fix - the query returns one row with
a = 1:There is no matching inner row, so the subquery returns
0and the comparison is1 > 0.Preserve aggregate values on empty input
Keep an inner join only when the comparison against the empty-input value folds to
FALSEorNULL. Otherwise, use a left join, add a match flag, and evaluate the comparison above the join using:CASE WHEN matched THEN actual_value ELSE value_on_empty_input ENDThe match flag distinguishes a missing group from an existing group whose expression is
NULL. The comparison must remain above the left join: placing it in the join condition could make a matched row that fails the comparison appear missing and pass using the empty-input value.Derive empty-input constants from
pg_aggregate, including initial transition values and foldable final functions. For supported cases that require executor evaluation, construct empty-input aggregate subqueries that become InitPlans. This handles non-NULL defaults beyondcount(), includingregr_count(), hypothetical-set aggregates, and aggregates with a non-NULL initial state.Recognize NULL results of the built-in numeric and int8
AVG/SUMfinal functions on a NULL internal state, allowing strict comparisons to retain inner joins without an empty-input InitPlan. Also recognize the empty-input value1for built-in hypotheticalrank()with constant direct arguments.Preserve expression evaluation behavior
Keep only grouping keys, the match flag, and aggregate values in the grouped subquery. Evaluate expressions around aggregates in the matched
CASEbranch above the join, so an unused inner group cannot cause an error such as division by zero. Mark the inputs with the left join's nulling relid.Use InitPlan parameters where substituting empty-input constants into compound expressions could cause errors during planning, such as in
10 / count(*)or10 / coalesce(sum(x), 0). Evaluate the surrounding expression at execution time when its branch is needed. An empty outer table or a query where every outer row has a match must not fail merely because the hypothetical empty-input expression would raise an error.Do not fold user-defined final functions or invoke them speculatively through empty-input InitPlans. Retain correlated execution unless the aggregate's final function is safe on empty input.
Keep unsupported window functions, correlated targetlists, and clauses whose variables are unavailable at the join attachment point on their existing SubPlan path. Allow nested subqueries inside aggregate arguments and
FILTER, keeping them at their original query level in the grouped subquery. Retain correlated execution for SubLinks in the surrounding expression.Regression coverage
Add PostgreSQL planner regression coverage for empty and matching outer inputs, multiple aggregates, NULL and non-NULL defaults, unsafe final functions, and parallel execution. Check that comparisons rejecting empty input still use inner joins, and cover the empty-input optimization for hypothetical
rank().Refreshed affected PostgreSQL planner plans in
subselect_gp,eagerfree, and the PAX answer files.Type of Change
Test Plan
git diff --checkpassed.make installcheck.make -C src/test installcheck-cbdb-parallel.The full regression suites have not been run on this final PostgreSQL-only version.
Impact
Queries whose aggregate comparison can pass on empty input now preserve unmatched outer rows. Comparisons proven to reject empty input retain inner joins. Unsupported or unsafe cases retain correlated execution.
Some queries require a left join and an empty-input InitPlan to preserve their semantics. Recognized constant empty-input values avoid unnecessary InitPlans. No new dependencies are introduced.
Additional Context
Updated from open-gpdb/cloudberry commit ad62f729c20ee00447bd926cfafd65310ecf066e (PR #89), originally adapted from open-gpdb/gpdb PR #397 and GreengageDB/greengage PR #546.
Co-Authored-By: excaliiibur [email protected]
Checklist