Skip to content

Fix join condition lost after pull up sublink to join - #1960

Open
Alena0704 wants to merge 1 commit into
apache:mainfrom
Alena0704:main-fix-join-cond-lost-pullup
Open

Fix join condition lost after pull up sublink to join#1960
Alena0704 wants to merge 1 commit into
apache:mainfrom
Alena0704:main-fix-join-cond-lost-pullup

Conversation

@Alena0704

Copy link
Copy Markdown
Contributor

Fix join condition lost after pull up sublink to join

After pulling up the sublink to join, the raw join condition may get
lost in the rewritten query, potentially leading to incorrect results.
Within the SubqueryToJoinWalker() function, we address this issue
by adding an 'else' branch to prevent the loss of join clauses and
keep them in their original positions.

(cherry picked from open-gpdb commit c06d16b)

Type of Change

  • Bug fix (non-breaking change)
  • New feature (non-breaking change)
  • Breaking change (fix or feature with breaking changes)
  • Documentation update

Breaking Changes

Test Plan

  • Unit tests added/updated
  • Integration tests added/updated
  • Passed make installcheck
  • Passed make -C src/test installcheck-cbdb-parallel

Impact

Performance:

User-facing changes:

Dependencies:

Checklist

Additional Context

CI Skip Instructions


After pulling up the sublink to join, the raw join condition may get
lost in the rewritten query, potentially leading to incorrect results.
Within the SubqueryToJoinWalker() function, we address this issue
by adding an 'else' branch to prevent the loss of join clauses and
keep them in their original positions.

Cherry-picked from open-gpdb; the expected files were regenerated on
Cloudberry, whose plans for the new test differ from GPDB's.

(cherry picked from commit c06d16b)
@Alena0704
Alena0704 force-pushed the main-fix-join-cond-lost-pullup branch from c052b35 to 8230bf0 Compare September 4, 2026 12:20
@Alena0704

Alena0704 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

When a correlated aggregate subquery is pulled up into a join, SubqueryToJoinWalker() records only two kinds of quals: non-correlated branches found inside an AND BoolExpr, and correlated equality OpExprs that become the grouping key. A single non-correlated qual matches neither branch — it falls through to the final return and is recorded nowhere. RemoveInnerJoinQuals() then clears je->quals, and subselect->jointree->quals is overwritten with what the walker collected, so the predicate is gone from the rewritten query.

A one-predicate JOIN ... ON clause is exactly such a qual, so its condition is silently dropped and the aggregate is computed over an unrestricted join:

set optimizer = off;

create table o (a int, d int);  insert into o  values (2, 1);
create table i1(a int);         insert into i1 values (1);
create table i2(a int);         insert into i2 values (1), (2);

select * from o where o.a > (select max(i2.a) from i1 join i2 on i2.a = i1.a where i1.a = o.d);
postgres=# create table o (a int, d int);  insert into o  values (2, 1);
NOTICE:  Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
HINT:  The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE TABLE
INSERT 0 1
postgres=# create table i1(a int);         insert into i1 values (1);
NOTICE:  Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
HINT:  The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE TABLE
INSERT 0 1
postgres=# create table i2(a int);         insert into i2 values (1), (2);
NOTICE:  Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
HINT:  The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE TABLE
INSERT 0 2
postgres=# 
postgres=# -- returns (2,1) as a SubPlan, returns nothing when pulled up
postgres=# select * from o where o.a > (select max(i2.a) from i1 join i2 on i2.a = i1.a where i1.a = o.d);
 a | d 
---+---
 2 | 1
(1 row)

postgres=# set optimizer = off;
SET
postgres=# select * from o where o.a > (select max(i2.a) from i1 join i2 on i2.a = i1.a where i1.a = o.d);
 a | d 
---+---
(0 rows)

i1.a = 1, so ON leaves only i2.a = 1 and the subquery is 1; 2 > 1 holds and the row must be returned. Instead the query returns nothing, because a Nested Loop with no Join Cond producing 2 rows instead of 1, which makes max come out as 2.

The fix adds an else branch that keeps such quals where they were. Only the Postgres planner is affected (optimizer=off, or an ORCA fallback); ORCA does its own decorrelation and preserves the ON condition.

@leborchuk leborchuk 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.

LGTM, let's place it without modification

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants