You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudberry.apache.org by "Alena0704 (via GitHub)" <gi...@apache.org> on 2026/10/14 09:39:34 UTC

[PR] ORCA: preserve empty-input aggregate values and rows in NULL-accepting subquery filters [cloudberry]

Alena0704 opened a new pull request, #2127:
URL: https://github.com/apache/cloudberry/pull/2127

   ### What does this PR do?
   
   Fix incorrect results when ORCA unnests correlated aggregate subqueries or scalar subqueries in filters that can accept NULL. Preserve the aggregate result on empty input and keep outer rows that can satisfy the original predicate.
   
   **ORCA: preserve the empty-input COUNT result in a correlated IN subquery**
   
   With ORCA (`optimizer = on`), the following query incorrectly returns no rows before the fix. It must return the row `(0, 1)`:
   
   <details>
   <summary>Reproduction: correlated <code>IN</code> over <code>count(*)</code></summary>
   
   ```sql
   CREATE TABLE cb (a int, b int) DISTRIBUTED BY (a);
   INSERT INTO cb VALUES (0, 1);
   SET optimizer = on;
   
   select * from cb x where x.a in (select count(*) from cb y where y.b = x.a);
   ```
   
   Before the fix, with ORCA:
   
   ```text
    a | b
   ---+---
   (0 rows)
   ```
   
   After the fix, with ORCA:
   
   ```text
    a | b
   ---+---
    0 | 1
   (1 row)
   ```
   
   With `optimizer = off`, the same query already returns `(0, 1)` before the ORCA fix.
   
   </details>
   
   For the outer row, `x.a = 0`. The only inner row has `y.b = 1`, so no row satisfies `y.b = x.a`. The aggregate subquery still returns one row containing `0`, and the condition is `0 IN (0)`, which is true. The input to this particular aggregate is empty even though `cb` itself is not empty.
   
   The incorrect plan groups the inner scan by `y.b` and joins it to the outer scan. There is a group for `y.b = 1`, but none for `y.b = 0`. The inner join therefore discards the outer row before it can be compared with the count of zero.
   
   The fix uses a left outer apply with a match flag. After decorrelation, the left join keeps the outer row, and `CASE` supplies `0` when there is no matching group. The comparison above the join then evaluates to `x.a = 0`, preserving `(0, 1)`.
   
   <details>
   <summary>EXPLAIN (COSTS OFF) for the same query, before and after the fix</summary>
   
   ```sql
   SET optimizer = on;
   EXPLAIN (COSTS OFF)
   select * from cb x where x.a in (select count(*) from cb y where y.b = x.a);
   ```
   
   Before the fix:
   
   ```text
   Gather Motion 3:1  (slice1; segments: 3)
     ->  Hash Join
           Hash Cond: ((y.b = x.a) AND ((count(*)) = (x.a)::bigint))
           ->  GroupAggregate
                 Group Key: y.b
                 ->  Sort
                       Sort Key: y.b
                       ->  Redistribute Motion 3:3  (slice2; segments: 3)
                             Hash Key: y.b
                             ->  Seq Scan on cb y
           ->  Hash
                 ->  Seq Scan on cb x
   Optimizer: GPORCA
   ```
   
   After the fix:
   
   ```text
   Gather Motion 3:1  (slice1; segments: 3)
     ->  Result
           Filter: (x.a = CASE WHEN (true) THEN (count(*)) ELSE '0'::bigint END)
           ->  Hash Left Join
                 Hash Cond: (x.a = y.b)
                 ->  Seq Scan on cb x
                 ->  Hash
                       ->  GroupAggregate
                             Group Key: y.b
                             ->  Sort
                                   Sort Key: y.b
                                   ->  Redistribute Motion 3:3  (slice2; segments: 3)
                                         Hash Key: y.b
                                         ->  Seq Scan on cb y
   Optimizer: GPORCA
   ```
   </details>
   
   In the corrected plan, `(true)` is the marker projected by the inner side. For the missing `y.b = 0` group, the left join supplies `NULL` for that marker, so `CASE` takes the `ELSE 0` branch. The `Result` filter compares `x.a` with this corrected value.
   
   **Preserve rows in NULL-accepting scalar-subquery filters**
   
   GPORCA could also discard outer rows when a scalar-subquery filter accepts
   NULL, such as `IS NULL` or `COALESCE(..., 0) = 0`. An inner apply removes
   unmatched rows before the predicate can evaluate to true.
   
   <details>
   <summary>For example:</summary>
   
   ```sql
   CREATE TABLE outer_t(a int, b int, d int) DISTRIBUTED BY (a);
   INSERT INTO outer_t VALUES
       (3,1,1), (1,9,5), (0,2,7), (5,5,1), (2,4,9);
   
   CREATE TABLE inner_t(a int, b int) DISTRIBUTED BY (a);
   INSERT INTO inner_t VALUES (1,10), (2,20), (1,30);
   ANALYZE outer_t;
   ANALYZE inner_t;
   
   SET optimizer = on;
   
   SELECT a, d FROM outer_t o
   WHERE COALESCE(
       (SELECT sum(i.b) FROM inner_t i WHERE i.a = o.d), 0
   ) = 0
   ORDER BY 1, 2;
   
   SELECT a, d FROM outer_t o
   WHERE (SELECT i.b FROM inner_t i
          WHERE i.a = o.d AND i.b = 10) IS NULL
   ORDER BY 1, 2;
   ```
   
   Before this fix, both queries returned no rows. They now return the expected
   three rows:
   
   ```text
    a | d
   ---+---
    0 | 7
    1 | 5
    2 | 9
   ```
   </details>
   
   Keep the filter context when the predicate rejects NULL from each scalar subquery individually, or when the subquery is known to return a row. Otherwise, use the value context to preserve unmatched outer rows through an outer apply.
   
   Check each subquery separately, leaving the other subqueries unknown: `COALESCE(sq1, sq2) > 0` can be true when either subquery is NULL, even though it is NULL when both are NULL.
   
   Recognize singleton constant subqueries, possibly under projections without set-returning functions, as nonempty. This preserves the existing SubPlan for `(SELECT 1)` inside `LEAST()` and `GREATEST()`, avoiding an unnecessary outer join. A potentially empty subquery such as `(SELECT 1 WHERE false)` still receives the NULL-preserving treatment.
   
   Skip result-type lookup for structural scalar nodes such as array index lists and CASE arms, which do not have a result type of their own.
   
   **Preserve expression evaluation and aggregate semantics**
   
   Keep aggregate values and the match flag below the join, and evaluate expressions around aggregates in the appropriate `CASE` branch above it. This avoids division-by-zero errors for unused inner groups while preserving NULL results and errors required by the original query.
   
   Preserve decorrelation through `HAVING` by evaluating its condition above the join using matched or empty-input aggregate values. A rejected row produces NULL for a scalar subquery and FALSE for `IN`, including when the left operand is NULL. Evaluate the target expression only when `HAVING` accepts the row.
   
   Expose the aggregate columns and match flag needed when non-equality correlations require correlated execution. Retain correlated execution for nested aggregates and unsafe user-defined final functions.
   
   Introduce shared helpers for catalog-based empty-input constants, NULL-result properties, and final-function safety checks. These helpers do not change PostgreSQL planner subquery pull-up behavior in this PR.
   
   Recognize the NULL empty-input results of built-in numeric and int8 `AVG`/`SUM`, retaining inner joins where the comparison rejects those results. For built-in hypothetical `rank()` with constant direct arguments, use the empty-input value `1` instead of an extra empty aggregate and its join. Require constant direct arguments so this optimization does not suppress their evaluation or errors.
   
   Verified the #2046 reproducer and the added regression queries, including
   EXPLAIN output, on a three-segment cluster.
   
   Related issue: [#2046](https://github.com/apache/cloudberry/issues/2046).
   
   
   ### Type of Change
   
   - [x] Bug fix (non-breaking change)
   - [ ] New feature (non-breaking change)
   - [ ] Breaking change (fix or feature with breaking changes)
   - [ ] Documentation update
   
   ### Test Plan
   
   - [x] Added/updated SQL regression tests and expected output.
   - [x] Built the updated server and ORCA code.
   - [x] Ran the dedicated `orca_empty_input` and `orca_filter_subqueries` regression tests.
   - [x] Verified the #2046 reproducer and EXPLAIN output on a three-segment cluster.
   - [ ] Passed `make installcheck`.
   - [ ] Passed `make -C src/test installcheck-cbdb-parallel`.
   
   The dedicated tests cover missing and matching groups, all-NULL groups, multiple aggregates, scalar and `IN`/`NOT IN` subqueries, `OR`, `HAVING`, non-equality correlations, unsafe final functions, and expression evaluation errors. Filter tests cover `IS NULL`, `COALESCE` over two correlated sums, subqueries in array indexes and CASE arms, and singleton or empty subqueries inside `LEAST()` and `GREATEST()`.
   
   Refresh affected ORCA expected plans, including the PAX expectation for `HAVING`. PAX storage and the full regression suites were not run locally for this ORCA-only branch.
   
   ### Impact
   
   Correlated aggregate subqueries preserve their empty-input values when no inner group matches. Scalar-subquery filters such as `IS NULL` and `COALESCE(..., 0) = 0` retain outer rows that satisfy the predicate.
   
   Plans use a left join or outer apply when needed to preserve those results. Comparisons that reject empty-input values retain inner joins where safe, and recognized singleton constant subqueries retain their existing filter context. Unsupported or unsafe cases retain correlated execution. No new dependencies are introduced.
   
   ### Additional Context
   
   A subset of the aggregate regression tests is retained from [Greengage commit 2ef1914](https://github.com/GreengageDB/greengage/commit/2ef1914b4df1418befff7443ea5c2b22be74a726). The implementation of the fix is different.
   
   ### Checklist
   
   - [x] Followed [contribution guide](https://cloudberry.apache.org/contribute/code)
   - [ ] Added/updated documentation
   - [ ] Reviewed code for security implications
   - [ ] This PR contains AI-assisted code generation
   - [x] Requested review from [cloudberry committers](https://github.com/orgs/apache/teams/cloudberry-committers)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org


Re: [PR] ORCA: preserve empty-input results in aggregate subqueries [cloudberry]

Posted by "leborchuk (via GitHub)" <gi...@apache.org>.
leborchuk merged PR #2127:
URL: https://github.com/apache/cloudberry/pull/2127


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org


Re: [PR] ORCA: preserve empty-input results in aggregate subqueries [cloudberry]

Posted by "Alena0704 (via GitHub)" <gi...@apache.org>.
Alena0704 commented on PR #2127:
URL: https://github.com/apache/cloudberry/pull/2127#issuecomment-6417541242

   CI failed because I missed updating some expected test outputs. Those are now updated. I also added a comment explaining why a particular query is no longer pulled up: it is expected behavior because the function that used there is VOLATILE by default.
   
   <details>
   <summary> diff </summary>
   
   $ git diff                                                                                                                                                                                                                       16:28:32
   diff --git a/contrib/pax_storage/src/test/regress/expected/rangefuncs_optimizer.out b/contrib/pax_storage/src/test/regress/expected/rangefuncs_optimizer.out
   index 144044fa256..5d68b34e997 100644
   --- a/contrib/pax_storage/src/test/regress/expected/rangefuncs_optimizer.out
   +++ b/contrib/pax_storage/src/test/regress/expected/rangefuncs_optimizer.out
   @@ -261,8 +261,9 @@ HINT:  likely caused by a function that reads or modifies data in a distributed
    CONTEXT:  SQL function "rngfunct" statement 1
    -- function in subselect
    select * from rngfunc2 where f2 in (select f2 from rngfunct(1) z where z.rngfuncid = rngfunc2.rngfuncid) ORDER BY 1,2;
   -ERROR:  function cannot execute on a QE slice because it accesses relation "public.rngfunc2"
   -CONTEXT:  SQL function "rngfunct" during startup
   +ERROR:  query plan with multiple segworker groups is not supported
   +HINT:  likely caused by a function that reads or modifies data in a distributed table
   +CONTEXT:  SQL function "rngfunct" statement 1
    -- function in subselect
    select * from rngfunc2 where f2 in (select f2 from rngfunct(rngfunc2.rngfuncid) z where z.rngfuncid = 1) ORDER BY 1,2;
    ERROR:  query plan with multiple segworker groups is not supported
   diff --git a/contrib/pax_storage/src/test/regress/sql/qp_with_functional.sql b/contrib/pax_storage/src/test/regress/sql/qp_with_functional.sql
   index 74bdbfe80b1..8b15c56c729 100644
   --- a/contrib/pax_storage/src/test/regress/sql/qp_with_functional.sql
   +++ b/contrib/pax_storage/src/test/regress/sql/qp_with_functional.sql
   @@ -19,7 +19,7 @@
    -- m/.*ERROR:.*function cannot execute on a QE slice because it accesses relation.*/
    -- s/.*ERROR:.*/ERROR: error message might be different for CTE/
    --
   --- m/.*ERROR:  query plan with multiple segworker groups is not supported.*/
   +-- m/.*ERROR:  (spiexceptions\.FeatureNotSupported: )?query plan with multiple segworker groups is not supported.*/
    -- s/.*ERROR:.*/ERROR: error message might be different for CTE/
    --
    -- # This test file is included in the launcher script,
   diff --git a/src/test/regress/expected/qp_rowsecurity.out b/src/test/regress/expected/qp_rowsecurity.out
   index dd3a43d3122..ca05a110d6b 100644
   --- a/src/test/regress/expected/qp_rowsecurity.out
   +++ b/src/test/regress/expected/qp_rowsecurity.out
   @@ -188,6 +188,10 @@ t1.a=bar_rls.p and bar_rls.q is null and leak_data(c);
    
    -- The RTE of the relation containing security quals is present inside a
    -- SUBLINK node
   +-- leak_data() is VOLATILE by default and emits NOTICEs, so ORCA keeps a
   +-- SubPlan instead of pulling up the subquery into a semi join: decorrelation
   +-- could change the number of calls. This tests RLS quals inside a subquery,
   +-- not whether the subquery can be pulled up.
    explain (costs off ) select * from bar_rls where bar_rls.p in (select a
    from foo_rls where abs(a) >= 10 and leak_data(c)) and bar_rls.q is not null;
                                                     QUERY PLAN                                                  
   @@ -496,6 +500,10 @@ where t1.a=bar_part_rls.p and bar_part_rls.q is not null and leak_data(c);
    
    -- The RTE of the relation containing security quals is present inside a
    -- SUBLINK node
   +-- leak_data() is VOLATILE by default and emits NOTICEs, so ORCA keeps a
   +-- SubPlan instead of pulling up the subquery into a semi join: decorrelation
   +-- could change the number of calls. This tests RLS quals inside a subquery,
   +-- not whether the subquery can be pulled up.
    explain (costs off ) select * from bar_part_rls where bar_part_rls.p in
    (select a from foo_part_rls where abs(a) >= 10 and leak_data(c)) and
    bar_part_rls.q is not null;
   diff --git a/src/test/regress/expected/qp_rowsecurity_optimizer.out b/src/test/regress/expected/qp_rowsecurity_optimizer.out
   index 642d16e50d1..2c9a05987d0 100644
   --- a/src/test/regress/expected/qp_rowsecurity_optimizer.out
   +++ b/src/test/regress/expected/qp_rowsecurity_optimizer.out
   @@ -189,20 +189,26 @@ t1.a=bar_rls.p and bar_rls.q is null and leak_data(c);
    
    -- The RTE of the relation containing security quals is present inside a
    -- SUBLINK node
   +-- leak_data() is VOLATILE by default and emits NOTICEs, so ORCA keeps a
   +-- SubPlan instead of pulling up the subquery into a semi join: decorrelation
   +-- could change the number of calls. This tests RLS quals inside a subquery,
   +-- not whether the subquery can be pulled up.
    explain (costs off ) select * from bar_rls where bar_rls.p in (select a
    from foo_rls where abs(a) >= 10 and leak_data(c)) and bar_rls.q is not null;
   -                                                  QUERY PLAN                                                   
   ----------------------------------------------------------------------------------------------------------------
   - Gather Motion 3:1  (slice1; segments: 3)
   -   ->  Hash Semi Join
   -         Hash Cond: (bar_rls.p = foo_rls.a)
   +                                                     QUERY PLAN                                                    
   +-------------------------------------------------------------------------------------------------------------------
   + Result
   +   Filter: (hashed SubPlan 1)
   +   ->  Gather Motion 3:1  (slice1; segments: 3)
             ->  Seq Scan on bar_rls
                   Filter: (NOT (q IS NULL))
   -         ->  Hash
   -               ->  Seq Scan on foo_rls
   -                     Filter: ((("" IS DISTINCT FROM c) OR (("" + 1) = 4)) AND ((abs(a) >= 10) AND leak_data(c)))
   +   SubPlan 1
   +     ->  Materialize
   +           ->  Gather Motion 3:1  (slice2; segments: 3)
   +                 ->  Seq Scan on foo_rls
   +                       Filter: ((("" IS DISTINCT FROM c) OR (("" + 1) = 4)) AND ((abs(a) >= 10) AND leak_data(c)))
     Optimizer: GPORCA
   -(9 rows)
   +(11 rows)
    
    -- The RTE of the relation containing security quals is present inside a CTE
    explain (costs off ) with cte as (select * from foo_rls where
   @@ -453,23 +459,29 @@ where t1.a=bar_part_rls.p and bar_part_rls.q is not null and leak_data(c);
    
    -- The RTE of the relation containing security quals is present inside a
    -- SUBLINK node
   +-- leak_data() is VOLATILE by default and emits NOTICEs, so ORCA keeps a
   +-- SubPlan instead of pulling up the subquery into a semi join: decorrelation
   +-- could change the number of calls. This tests RLS quals inside a subquery,
   +-- not whether the subquery can be pulled up.
    explain (costs off ) select * from bar_part_rls where bar_part_rls.p in
    (select a from foo_part_rls where abs(a) >= 10 and leak_data(c)) and
    bar_part_rls.q is not null;
   -                                                  QUERY PLAN                                                   
   ----------------------------------------------------------------------------------------------------------------
   - Gather Motion 3:1  (slice1; segments: 3)
   -   ->  Hash Semi Join
   -         Hash Cond: (bar_part_rls.p = foo_part_rls.a)
   +                                                    QUERY PLAN                                                   
   +-----------------------------------------------------------------------------------------------------------------
   + Result
   +   Filter: (hashed SubPlan 1)
   +   ->  Gather Motion 3:1  (slice1; segments: 3)
             ->  Dynamic Seq Scan on bar_part_rls
                   Number of partitions to scan: 5 (out of 5)
                   Filter: (NOT (q IS NULL))
   -         ->  Hash
   -               ->  Dynamic Seq Scan on foo_part_rls
   -                     Number of partitions to scan: 6 (out of 6)
   -                     Filter: (((b IS DISTINCT FROM c) OR ((b + 1) = 4)) AND ((abs(a) >= 10) AND leak_data(c)))
   +   SubPlan 1
   +     ->  Materialize
   +           ->  Gather Motion 3:1  (slice2; segments: 3)
   +                 ->  Dynamic Seq Scan on foo_part_rls
   +                       Number of partitions to scan: 6 (out of 6)
   +                       Filter: (((b IS DISTINCT FROM c) OR ((b + 1) = 4)) AND ((abs(a) >= 10) AND leak_data(c)))
     Optimizer: GPORCA
   -(11 rows)
   +(13 rows)
    
    -- The RTE of the relation containing security quals is present inside a CTE
    explain (costs off ) with cte as (select * from foo_part_rls where
   diff --git a/src/test/regress/expected/qp_with_functional_inlining.out b/src/test/regress/expected/qp_with_functional_inlining.out
   index b1c571b9831..f7b8ec2853a 100644
   --- a/src/test/regress/expected/qp_with_functional_inlining.out
   +++ b/src/test/regress/expected/qp_with_functional_inlining.out
   @@ -29,7 +29,7 @@ set search_path='qp_with_functional';
    -- m/.*ERROR:.*function cannot execute on a QE slice because it accesses relation.*/
    -- s/.*ERROR:.*/ERROR: error message might be different for CTE/
    --
   --- m/.*ERROR:  query plan with multiple segworker groups is not supported.*/
   +-- m/.*ERROR:  (spiexceptions\.FeatureNotSupported: )?query plan with multiple segworker groups is not supported.*/
    -- s/.*ERROR:.*/ERROR: error message might be different for CTE/
    --
    -- # This test file is included in the launcher script,
   diff --git a/src/test/regress/expected/qp_with_functional_noinlining.out b/src/test/regress/expected/qp_with_functional_noinlining.out
   index bfdc05ca792..de4754b49fe 100644
   --- a/src/test/regress/expected/qp_with_functional_noinlining.out
   +++ b/src/test/regress/expected/qp_with_functional_noinlining.out
   @@ -28,7 +28,7 @@ set search_path='qp_with_functional';
    -- m/.*ERROR:.*function cannot execute on a QE slice because it accesses relation.*/
    -- s/.*ERROR:.*/ERROR: error message might be different for CTE/
    --
   --- m/.*ERROR:  query plan with multiple segworker groups is not supported.*/
   +-- m/.*ERROR:  (spiexceptions\.FeatureNotSupported: )?query plan with multiple segworker groups is not supported.*/
   
   </details>
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org


Re: [PR] ORCA: preserve empty-input results in aggregate subqueries [cloudberry]

Posted by "Alena0704 (via GitHub)" <gi...@apache.org>.
Alena0704 commented on PR #2127:
URL: https://github.com/apache/cloudberry/pull/2127#issuecomment-6274182977

   > Thanks for working on this. I built the branch locally (`3518a1c7170`, cassert, `-O0`, `--enable-pax --with-zstd`) on a 3-segment demo cluster and A/B'd it against the same tree with the `src/` diff reverted (clean rebuild of `gporca`/`gpopt`/`cdb` each way).
   > 
   > The correctness goal holds up well: the empty-input values for correlated `count`/`regr_count`/user-defined aggregates, and the `IS NULL` / `COALESCE(...) = 0` / `IS DISTINCT FROM` filters built on top of them, are genuinely fixed, and 32 ORCA-heavy regression tests show no expected-output diffs caused by the change.
   > 
   > Two issues I think should be addressed before merge.
   > 
   > ### 1. `agg_empty_input_is_safe()` disables decorrelation for most user-defined aggregates
   > `src/backend/cdb/cdbsubselect.c:899`:
   > 
   > ```c
   > safe = !OidIsValid(aggform->aggfinalfn) ||
   >     (initValueIsNull && func_strict(aggform->aggfinalfn));
   > ```
   > 
   > Any user-defined aggregate whose final function is not `STRICT` is classified unsafe. `CSubqueryHandler::Psd()` (`CSubqueryHandler.cpp:1233-1238`) then forces `m_fCorrelatedExecution`, so the correlated subquery loses decorrelation entirely.
   > 
   > Repro on this branch with 20,000 outer x 100,000 inner rows. The two aggregates are identical except for the strictness of the final function:
   > 
   > ```sql
   > create function uf(int) returns int as 'select coalesce($1, 0)' language sql;   -- non-strict
   > create function sf(int) returns int as 'select $1' language sql strict;         -- strict
   > create aggregate uagg(int) (sfunc = int4pl, stype = int4, finalfunc = uf);
   > create aggregate sagg(int) (sfunc = int4pl, stype = int4, finalfunc = sf);
   > 
   > select a, (select sagg(bi.b) from bi where bi.a = bo.d) from bo;
   > select a, (select uagg(bi.b) from bi where bi.a = bo.d) from bo;
   > ```
   > 
   > `sagg` (strict final function) decorrelates — **15 ms**:
   > 
   > ```
   >  Gather Motion 3:1  (slice1; segments: 3) (actual rows=20000 loops=1)
   >    ->  Hash Left Join (actual rows=6800 loops=1)
   >          Hash Cond: (bo.d = bi.a)
   >          ->  Redistribute Motion 3:3  (slice2; segments: 3) (actual rows=6800 loops=1)
   >                ->  Seq Scan on bo (actual rows=6781 loops=1)
   >          ->  Hash (actual rows=340 loops=1)
   >                ->  GroupAggregate (actual rows=340 loops=1)
   >                      ->  Sort (actual rows=34000 loops=1)
   >                            ->  Seq Scan on bi (actual rows=34000 loops=1)
   >  Optimizer: GPORCA
   >  Execution Time: 15.412 ms
   > ```
   > 
   > `uagg` (non-strict final function) does not — **56,919 ms**:
   > 
   > ```
   >  Result (actual rows=20000 loops=1)
   >    ->  Gather Motion 3:1  (slice1; segments: 3) (actual rows=20000 loops=1)
   >          ->  Seq Scan on bo (actual rows=6781 loops=1)
   >    SubPlan 1
   >      ->  Aggregate (actual rows=1 loops=20000)
   >            ->  Result (actual rows=100 loops=20000)
   >                  Filter: (bi.a = bo.d)
   >                  ->  Materialize (actual rows=100000 loops=20000)
   >                        ->  Gather Motion 3:1  (slice2; segments: 3) (actual rows=100000 loops=1)
   >                              ->  Seq Scan on bi (actual rows=34000 loops=1)
   >  Optimizer: GPORCA
   >  Execution Time: 56918.902 ms
   > ```
   > 
   > A non-`STRICT` final function is precisely what you write when an aggregate must return a specific value on empty input — PostGIS `ST_Union`, `hll`, `tdigest` and friends all qualify — so this is not an exotic shape. Could the condition be narrowed (treat the aggregate as unsafe only when the empty-input value genuinely cannot be determined), or at minimum be gated by a GUC so it can be turned off?
   > 
   > ### 2. `PexprSubstituteColRefs()` shares relational subtrees, producing non-disjoint Apply children
   > `CSubqueryHandler.cpp:672-676` — the non-scalar branch shares the child verbatim:
   > 
   > ```c
   > else
   > {
   >     pexprChild->AddRef();
   >     pdrgpexprChildren->Append(pexprChild);
   > }
   > ```
   > 
   > and the function is called **twice on the same expression** to build the matched and the empty `CASE` branch — lines 845/847 for HAVING, 857/859 for projections. So when a projection or HAVING above the correlated `GROUP BY ()` aggregate contains a subquery, that subquery's entire relational subtree ends up in the result twice with the _same_ colrefs, and the resulting Apply has non-disjoint children.
   > 
   > ```sql
   > select a from o where a in (select count(*) + (select max(x) from k) from i where i.a = o.d);
   > ```
   > 
   > On this branch:
   > 
   > ```
   > CLogical.cpp:265: Failed assertion: pcrs->IsDisjoint(pcrsChild) && "Input columns are not disjoint"
   >   gpopt::CLogical::PcrsDeriveOutputCombineLogical + 211
   >   gpopt::CLogicalLeftOuterApply::DeriveOutputColumns + 116
   >   ...
   >   gpopt::CXformSubqueryUnnest::PexprSubqueryUnnest + 1032
   >   gpopt::CXformSubqueryUnnest::Transform + 69
   > LOG:  GPORCA failed to produce plan (unexpected)
   > ```
   > 
   > and the query falls back to the Postgres planner. On the pre-PR baseline (same worktree, `src/` diff reverted, clean rebuild) ORCA plans this query without hitting the assertion.
   > 
   > Three more variants reach the same new assertion — these already fell back on the baseline, but on different, pre-existing assertions:
   > 
   > ```sql
   > select ... from i where i.a = o.d having count(*) > (select max(x) from k)
   > select ... from i where i.a = o.d having count(*) in (select x from k)
   > select count(*) + (case when count(*) in (select x from k) then 1 else 0 end) from i where i.a = o.d
   > ```
   > 
   > The part that worries me most is that in a release build the assertion is compiled out and ORCA keeps optimizing the malformed expression. Either remap the colrefs when copying the subtree, or bail out of `PexprScalarAggApply()` when the pulled-up projections/HAVING contain a subquery.
   > 
   > I also have a few lower-severity notes — pre-PR minidumps replaying with the wrong `IsEmptyInputNull`/`IsEmptyInputSafe` because `CMDAggregateGPDB::Serialize()` only emits the attributes when they are false while the parse handler defaults them to true; `IsPolymorphicType()` bailing out on `array_agg`/`string_agg`/`json_agg`, which adds an extra join and slice to a very common query shape; and `get_agg_empty_input_const()` not checking `aggkind`, so a user-defined ordered-set aggregate with a strict final function loses evaluation of its direct arguments. Happy to post those separately if they are useful.
   
   ```rust
   Hi,
   
   I've reworked the implementation since your review of `3518a1c7170`. The goal remains the same: preserve aggregate results on empty input while allowing supported queries to decorrelate.
   
   The approach builds on ORCA's existing scalar-subquery handling, which already restores COUNT's zero result on empty input. The original COUNT/IN bug exposed a gap in applying the same semantics to IN/ANY. I extended the scalar-subquery handling and reused it for supported single-row aggregate IN/ANY queries.
   
   I removed the additional metadata describing aggregate behavior on empty input. COUNT and built-in MIN/MAX use known empty-input values; other aggregates are evaluated on empty input only when no matching group exists. These results are preserved before evaluating expressions, HAVING, and surrounding predicates.
   
   Nested subqueries in projections or HAVING retain correlated execution, avoiding the shared-column-reference assertion reported in the review. IN/ANY forms that can return no rows retain quantified semantics.
   
   The PR now contains three commits:
   
   1. [275e1351647](https://github.com/apache/cloudberry/commit/275e135164789a6db2aeb94e093cac9434059725) — fix join selectivity for fractional row estimates.
   2. [9490dccff55](https://github.com/apache/cloudberry/commit/9490dccff55e52377d0ee52c8d3baceee2941cf1) — preserve scalar aggregate results, including HAVING and filters that accept NULL.
   3. [c47d048beb4](https://github.com/apache/cloudberry/commit/c47d048beb470484556287ad5aa87750edd65961) — reuse scalar aggregate handling for IN/ANY while preserving empty-result and NULL semantics.
   
   While testing nested aggregate subqueries, I also found a separate cardinality-estimation bug: fractional row estimates could produce join scale factors below one, making selectivity exceed one or triggering an assertion. The first commit fixes the affected scale factors and their bound.
   
   Examples, results, and query plans before and after the fix are included in the PR description.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org


Re: [PR] ORCA: preserve empty-input aggregate values and rows in NULL-accepting subquery filters [cloudberry]

Posted by "Alena0704 (via GitHub)" <gi...@apache.org>.
Alena0704 commented on PR #2127:
URL: https://github.com/apache/cloudberry/pull/2127#issuecomment-6189200951

   > Thanks for working on this. I built the branch locally (`3518a1c7170`, cassert, `-O0`, `--enable-pax --with-zstd`) on a 3-segment demo cluster and A/B'd it against the same tree with the `src/` diff reverted (clean rebuild of `gporca`/`gpopt`/`cdb` each way).
   > 
   > The correctness goal holds up well: the empty-input values for correlated `count`/`regr_count`/user-defined aggregates, and the `IS NULL` / `COALESCE(...) = 0` / `IS DISTINCT FROM` filters built on top of them, are genuinely fixed, and 32 ORCA-heavy regression tests show no expected-output diffs caused by the change.
   > 
   > Two issues I think should be addressed before merge.
   > 
   > ### 1. `agg_empty_input_is_safe()` disables decorrelation for most user-defined aggregates
   > `src/backend/cdb/cdbsubselect.c:899`:
   > 
   > ```c
   > safe = !OidIsValid(aggform->aggfinalfn) ||
   >     (initValueIsNull && func_strict(aggform->aggfinalfn));
   > ```
   > 
   > Any user-defined aggregate whose final function is not `STRICT` is classified unsafe. `CSubqueryHandler::Psd()` (`CSubqueryHandler.cpp:1233-1238`) then forces `m_fCorrelatedExecution`, so the correlated subquery loses decorrelation entirely.
   > 
   > Repro on this branch with 20,000 outer x 100,000 inner rows. The two aggregates are identical except for the strictness of the final function:
   > 
   > ```sql
   > create function uf(int) returns int as 'select coalesce($1, 0)' language sql;   -- non-strict
   > create function sf(int) returns int as 'select $1' language sql strict;         -- strict
   > create aggregate uagg(int) (sfunc = int4pl, stype = int4, finalfunc = uf);
   > create aggregate sagg(int) (sfunc = int4pl, stype = int4, finalfunc = sf);
   > 
   > select a, (select sagg(bi.b) from bi where bi.a = bo.d) from bo;
   > select a, (select uagg(bi.b) from bi where bi.a = bo.d) from bo;
   > ```
   > 
   > `sagg` (strict final function) decorrelates — **15 ms**:
   > 
   > ```
   >  Gather Motion 3:1  (slice1; segments: 3) (actual rows=20000 loops=1)
   >    ->  Hash Left Join (actual rows=6800 loops=1)
   >          Hash Cond: (bo.d = bi.a)
   >          ->  Redistribute Motion 3:3  (slice2; segments: 3) (actual rows=6800 loops=1)
   >                ->  Seq Scan on bo (actual rows=6781 loops=1)
   >          ->  Hash (actual rows=340 loops=1)
   >                ->  GroupAggregate (actual rows=340 loops=1)
   >                      ->  Sort (actual rows=34000 loops=1)
   >                            ->  Seq Scan on bi (actual rows=34000 loops=1)
   >  Optimizer: GPORCA
   >  Execution Time: 15.412 ms
   > ```
   > 
   > `uagg` (non-strict final function) does not — **56,919 ms**:
   > 
   > ```
   >  Result (actual rows=20000 loops=1)
   >    ->  Gather Motion 3:1  (slice1; segments: 3) (actual rows=20000 loops=1)
   >          ->  Seq Scan on bo (actual rows=6781 loops=1)
   >    SubPlan 1
   >      ->  Aggregate (actual rows=1 loops=20000)
   >            ->  Result (actual rows=100 loops=20000)
   >                  Filter: (bi.a = bo.d)
   >                  ->  Materialize (actual rows=100000 loops=20000)
   >                        ->  Gather Motion 3:1  (slice2; segments: 3) (actual rows=100000 loops=1)
   >                              ->  Seq Scan on bi (actual rows=34000 loops=1)
   >  Optimizer: GPORCA
   >  Execution Time: 56918.902 ms
   > ```
   > 
   > A non-`STRICT` final function is precisely what you write when an aggregate must return a specific value on empty input — PostGIS `ST_Union`, `hll`, `tdigest` and friends all qualify — so this is not an exotic shape. Could the condition be narrowed (treat the aggregate as unsafe only when the empty-input value genuinely cannot be determined), or at minimum be gated by a GUC so it can be turned off?
   > 
   > ### 2. `PexprSubstituteColRefs()` shares relational subtrees, producing non-disjoint Apply children
   > `CSubqueryHandler.cpp:672-676` — the non-scalar branch shares the child verbatim:
   > 
   > ```c
   > else
   > {
   >     pexprChild->AddRef();
   >     pdrgpexprChildren->Append(pexprChild);
   > }
   > ```
   > 
   > and the function is called **twice on the same expression** to build the matched and the empty `CASE` branch — lines 845/847 for HAVING, 857/859 for projections. So when a projection or HAVING above the correlated `GROUP BY ()` aggregate contains a subquery, that subquery's entire relational subtree ends up in the result twice with the _same_ colrefs, and the resulting Apply has non-disjoint children.
   > 
   > ```sql
   > select a from o where a in (select count(*) + (select max(x) from k) from i where i.a = o.d);
   > ```
   > 
   > On this branch:
   > 
   > ```
   > CLogical.cpp:265: Failed assertion: pcrs->IsDisjoint(pcrsChild) && "Input columns are not disjoint"
   >   gpopt::CLogical::PcrsDeriveOutputCombineLogical + 211
   >   gpopt::CLogicalLeftOuterApply::DeriveOutputColumns + 116
   >   ...
   >   gpopt::CXformSubqueryUnnest::PexprSubqueryUnnest + 1032
   >   gpopt::CXformSubqueryUnnest::Transform + 69
   > LOG:  GPORCA failed to produce plan (unexpected)
   > ```
   > 
   > and the query falls back to the Postgres planner. On the pre-PR baseline (same worktree, `src/` diff reverted, clean rebuild) ORCA plans this query without hitting the assertion.
   > 
   > Three more variants reach the same new assertion — these already fell back on the baseline, but on different, pre-existing assertions:
   > 
   > ```sql
   > select ... from i where i.a = o.d having count(*) > (select max(x) from k)
   > select ... from i where i.a = o.d having count(*) in (select x from k)
   > select count(*) + (case when count(*) in (select x from k) then 1 else 0 end) from i where i.a = o.d
   > ```
   > 
   > The part that worries me most is that in a release build the assertion is compiled out and ORCA keeps optimizing the malformed expression. Either remap the colrefs when copying the subtree, or bail out of `PexprScalarAggApply()` when the pulled-up projections/HAVING contain a subquery.
   > 
   > I also have a few lower-severity notes — pre-PR minidumps replaying with the wrong `IsEmptyInputNull`/`IsEmptyInputSafe` because `CMDAggregateGPDB::Serialize()` only emits the attributes when they are false while the parse handler defaults them to true; `IsPolymorphicType()` bailing out on `array_agg`/`string_agg`/`json_agg`, which adds an extra join and slice to a very common query shape; and `get_agg_empty_input_const()` not checking `aggkind`, so a user-defined ordered-set aggregate with a strict final function loses evaluation of its direct arguments. Happy to post those separately if they are useful.
   
   Thank you! Agree with this. I need some time to figure out how to think how to address these issues


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org


Re: [PR] ORCA: preserve empty-input aggregate values and rows in NULL-accepting subquery filters [cloudberry]

Posted by "yjhjstz (via GitHub)" <gi...@apache.org>.
yjhjstz commented on PR #2127:
URL: https://github.com/apache/cloudberry/pull/2127#issuecomment-6179751908

   Thanks for working on this. I built the branch locally (`3518a1c7170`, cassert, `-O0`, `--enable-pax --with-zstd`) on a 3-segment demo cluster and A/B'd it against the same tree with the `src/` diff reverted (clean rebuild of `gporca`/`gpopt`/`cdb` each way).
   
   The correctness goal holds up well: the empty-input values for correlated `count`/`regr_count`/user-defined aggregates, and the `IS NULL` / `COALESCE(...) = 0` / `IS DISTINCT FROM` filters built on top of them, are genuinely fixed, and 32 ORCA-heavy regression tests show no expected-output diffs caused by the change.
   
   Two issues I think should be addressed before merge.
   
   ---
   
   ### 1. `agg_empty_input_is_safe()` disables decorrelation for most user-defined aggregates
   
   `src/backend/cdb/cdbsubselect.c:899`:
   
   ```c
   safe = !OidIsValid(aggform->aggfinalfn) ||
       (initValueIsNull && func_strict(aggform->aggfinalfn));
   ```
   
   Any user-defined aggregate whose final function is not `STRICT` is classified unsafe. `CSubqueryHandler::Psd()` (`CSubqueryHandler.cpp:1233-1238`) then forces `m_fCorrelatedExecution`, so the correlated subquery loses decorrelation entirely.
   
   Repro on this branch with 20,000 outer x 100,000 inner rows. The two aggregates are identical except for the strictness of the final function:
   
   ```sql
   create function uf(int) returns int as 'select coalesce($1, 0)' language sql;   -- non-strict
   create function sf(int) returns int as 'select $1' language sql strict;         -- strict
   create aggregate uagg(int) (sfunc = int4pl, stype = int4, finalfunc = uf);
   create aggregate sagg(int) (sfunc = int4pl, stype = int4, finalfunc = sf);
   
   select a, (select sagg(bi.b) from bi where bi.a = bo.d) from bo;
   select a, (select uagg(bi.b) from bi where bi.a = bo.d) from bo;
   ```
   
   `sagg` (strict final function) decorrelates — **15 ms**:
   
   ```
    Gather Motion 3:1  (slice1; segments: 3) (actual rows=20000 loops=1)
      ->  Hash Left Join (actual rows=6800 loops=1)
            Hash Cond: (bo.d = bi.a)
            ->  Redistribute Motion 3:3  (slice2; segments: 3) (actual rows=6800 loops=1)
                  ->  Seq Scan on bo (actual rows=6781 loops=1)
            ->  Hash (actual rows=340 loops=1)
                  ->  GroupAggregate (actual rows=340 loops=1)
                        ->  Sort (actual rows=34000 loops=1)
                              ->  Seq Scan on bi (actual rows=34000 loops=1)
    Optimizer: GPORCA
    Execution Time: 15.412 ms
   ```
   
   `uagg` (non-strict final function) does not — **56,919 ms**:
   
   ```
    Result (actual rows=20000 loops=1)
      ->  Gather Motion 3:1  (slice1; segments: 3) (actual rows=20000 loops=1)
            ->  Seq Scan on bo (actual rows=6781 loops=1)
      SubPlan 1
        ->  Aggregate (actual rows=1 loops=20000)
              ->  Result (actual rows=100 loops=20000)
                    Filter: (bi.a = bo.d)
                    ->  Materialize (actual rows=100000 loops=20000)
                          ->  Gather Motion 3:1  (slice2; segments: 3) (actual rows=100000 loops=1)
                                ->  Seq Scan on bi (actual rows=34000 loops=1)
    Optimizer: GPORCA
    Execution Time: 56918.902 ms
   ```
   
   A non-`STRICT` final function is precisely what you write when an aggregate must return a specific value on empty input — PostGIS `ST_Union`, `hll`, `tdigest` and friends all qualify — so this is not an exotic shape. Could the condition be narrowed (treat the aggregate as unsafe only when the empty-input value genuinely cannot be determined), or at minimum be gated by a GUC so it can be turned off?
   
   ---
   
   ### 2. `PexprSubstituteColRefs()` shares relational subtrees, producing non-disjoint Apply children
   
   `CSubqueryHandler.cpp:672-676` — the non-scalar branch shares the child verbatim:
   
   ```c
   else
   {
       pexprChild->AddRef();
       pdrgpexprChildren->Append(pexprChild);
   }
   ```
   
   and the function is called **twice on the same expression** to build the matched and the empty `CASE` branch — lines 845/847 for HAVING, 857/859 for projections. So when a projection or HAVING above the correlated `GROUP BY ()` aggregate contains a subquery, that subquery's entire relational subtree ends up in the result twice with the *same* colrefs, and the resulting Apply has non-disjoint children.
   
   ```sql
   select a from o where a in (select count(*) + (select max(x) from k) from i where i.a = o.d);
   ```
   
   On this branch:
   
   ```
   CLogical.cpp:265: Failed assertion: pcrs->IsDisjoint(pcrsChild) && "Input columns are not disjoint"
     gpopt::CLogical::PcrsDeriveOutputCombineLogical + 211
     gpopt::CLogicalLeftOuterApply::DeriveOutputColumns + 116
     ...
     gpopt::CXformSubqueryUnnest::PexprSubqueryUnnest + 1032
     gpopt::CXformSubqueryUnnest::Transform + 69
   LOG:  GPORCA failed to produce plan (unexpected)
   ```
   
   and the query falls back to the Postgres planner. On the pre-PR baseline (same worktree, `src/` diff reverted, clean rebuild) ORCA plans this query without hitting the assertion.
   
   Three more variants reach the same new assertion — these already fell back on the baseline, but on different, pre-existing assertions:
   
   ```sql
   select ... from i where i.a = o.d having count(*) > (select max(x) from k)
   select ... from i where i.a = o.d having count(*) in (select x from k)
   select count(*) + (case when count(*) in (select x from k) then 1 else 0 end) from i where i.a = o.d
   ```
   
   The part that worries me most is that in a release build the assertion is compiled out and ORCA keeps optimizing the malformed expression. Either remap the colrefs when copying the subtree, or bail out of `PexprScalarAggApply()` when the pulled-up projections/HAVING contain a subquery.
   
   ---
   
   I also have a few lower-severity notes — pre-PR minidumps replaying with the wrong `IsEmptyInputNull`/`IsEmptyInputSafe` because `CMDAggregateGPDB::Serialize()` only emits the attributes when they are false while the parse handler defaults them to true; `IsPolymorphicType()` bailing out on `array_agg`/`string_agg`/`json_agg`, which adds an extra join and slice to a very common query shape; and `get_agg_empty_input_const()` not checking `aggkind`, so a user-defined ordered-set aggregate with a strict final function loses evaluation of its direct arguments. Happy to post those separately if they are useful.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org


Re: [PR] ORCA: preserve empty-input results in aggregate subqueries [cloudberry]

Posted by "Alena0704 (via GitHub)" <gi...@apache.org>.
Alena0704 commented on PR #2127:
URL: https://github.com/apache/cloudberry/pull/2127#issuecomment-6411726798

   > LGTM, but code need rebase
   
   Done. There were conflicts in tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org


Re: [PR] ORCA: preserve empty-input results in aggregate subqueries [cloudberry]

Posted by "Alena0704 (via GitHub)" <gi...@apache.org>.
Alena0704 commented on PR #2127:
URL: https://github.com/apache/cloudberry/pull/2127#issuecomment-6294645432

   Since my previous update, I narrowed the fix to avoid changing queries that do not need empty-input handling.
   Simple built-in SUM, AVG, MIN, and MAX now keep their previous plans. The existing handling of ANY/ALL and COUNT with correlated HAVING is also preserved.
   I added protection for volatile functions in separate commit (second commit), including aggregate FINALFUNC. These subqueries must not share one result between different outer rows. The general volatile-function check is in a separate commit before the aggregate fixes.
   
   Orca unlike Postgres optimizer can evaluate a volatile expression for rows that do not match the correlated subquery, causing an unexpected error. Bug reproduction:
   SET optimizer = on;
   
   ```
   CREATE TABLE o(k int) DISTRIBUTED BY (k);
   CREATE TABLE i(k int, b int) DISTRIBUTED BY (k);
   
   INSERT INTO o VALUES (1);
   INSERT INTO i VALUES (1, 1), (2, 0);
   ANALYZE o;
   ANALYZE i;
   
   SELECT k FROM o
   WHERE 0 IN (
       SELECT floor(random() / i.b)::int
       FROM i
       WHERE i.k = o.k
   );
   ```
   
   Only (1, 1) should match, so the query should return 1.
   Before the fix, ORCA turns the subquery into a Hash Semi Join. It evaluates random() / b before checking the join condition, including for the unrelated row (2, 0). This causes: `ERROR: division by zero`
   
   ```
   postgres=# SELECT k FROM o
   postgres-# WHERE 0 IN (
   postgres(#     SELECT floor(random() / i.b)::int
   postgres(#     FROM i
   postgres(#     WHERE i.k = o.k
   postgres(# );
   ERROR:  division by zero  (seg0 slice1 127.0.0.1:6002 pid=66392)
   postgres=# explain SELECT k FROM o
   postgres-# WHERE 0 IN (
   postgres(#     SELECT floor(random() / i.b)::int
   postgres(#     FROM i
   postgres(#     WHERE i.k = o.k
   postgres(# );
                                            QUERY PLAN                                         
   --------------------------------------------------------------------------------------------
    Gather Motion 3:1  (slice1; segments: 3)  (cost=0.00..862.00 rows=1 width=4)
      ->  Hash Semi Join  (cost=0.00..862.00 rows=1 width=4)
            Hash Cond: (o.k = i.k)
            ->  Seq Scan on o  (cost=0.00..431.00 rows=1 width=4)
                  Filter: (NOT (k IS NULL))
            ->  Hash  (cost=431.00..431.00 rows=1 width=4)
                  ->  Result  (cost=0.00..431.00 rows=1 width=4)
                        Filter: (0 = ((floor((random() / (i.b)::double precision)))::integer))
                        ->  Seq Scan on i  (cost=0.00..431.00 rows=1 width=8)
    Optimizer: GPORCA
   (10 rows)
   
   postgres=# set optimizer=off
   postgres-# ;
   SET
   postgres=# explain SELECT k FROM o               WHERE 0 IN (                                  SELECT floor(random() / i.b)::int                                                 FROM i                                                                WHERE i.k = o.k                                                                           );
                                                  QUERY PLAN                                                
   ---------------------------------------------------------------------------------------------------------
    Gather Motion 3:1  (slice1; segments: 3)  (cost=0.00..1.03 rows=1 width=4)
      ->  Seq Scan on o  (cost=0.00..1.01 rows=1 width=4)
            Filter: (SubPlan 1)
            SubPlan 1
              ->  Result  (cost=0.00..1.09 rows=2 width=4)
                    Filter: (i.k = o.k)
                    ->  Materialize  (cost=0.00..1.05 rows=2 width=8)
                          ->  Broadcast Motion 3:3  (slice2; segments: 3)  (cost=0.00..1.04 rows=2 width=8)
                                ->  Seq Scan on i  (cost=0.00..1.01 rows=1 width=8)
    Optimizer: Postgres query optimizer
   (10 rows)
   
   postgres=# SELECT k FROM o               WHERE 0 IN (                                  SELECT floor(random() / i.b)::int                                                 FROM i                                                                WHERE i.k = o.k                                                                           );
    k 
   ---
    1
   (1 row)
   ```
   
   After the fix, ORCA keeps a correlated SubPlan, checks i.k = o.k first, and returns 1.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org