Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/backend/gpopt/translate/CTranslatorQueryToDXL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4142,19 +4142,27 @@ CTranslatorQueryToDXL::TranslateJoinExprInFromToDXL(JoinExpr *join_expr)
{
continue;
}
GPOS_ASSERT(IsA(join_alias_node, Var) ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! Thank you for working on it!
Removing the assertion does not fully fix coerced USING columns. NormalizeQuery does not flatten join aliases in WHERE or JOIN ... ON conditions: flatten_join_alias_var_optimizer intentionally skips quals.
For RelabelType, the translator allocates a new colid and records it in the mapping, but only CoalesceExpr is added to the computed projection. A condition can therefore reference a column that is never computed, causing GPORCA to fall back with: DXL-to-Expr Translation: Attribute number 21 not found in project list

Bug reproduction:

SET optimizer = on;
SET optimizer_trace_fallback = on;

CREATE TABLE using_coerce_t1(k varchar(10), f2 int)
DISTRIBUTED BY (f2);
CREATE TABLE using_coerce_t2(k text, f2 oid)
DISTRIBUTED BY (f2);

INSERT INTO using_coerce_t1 VALUES ('a', 1), ('b', 2);
INSERT INTO using_coerce_t2 VALUES ('a', 1), ('c', 3);

EXPLAIN (COSTS OFF)
SELECT k
FROM using_coerce_t1
LEFT JOIN using_coerce_t2 USING (k)
WHERE k = 'a';

output:

pr2148=# SET optimizer = on;
SET
pr2148=# SET optimizer_trace_fallback = on;
SET
pr2148=# EXPLAIN (COSTS OFF)
pr2148# SELECT k
pr2148# FROM using_coerce_t1
pr2148# LEFT JOIN using_coerce_t2 USING (k)
pr2148# WHERE k = 'a';
INFO:  GPORCA failed to produce a plan, falling back to Postgres-based planner
DETAIL:  DXL-to-Expr Translation: Attribute number 21 not found in project list
                          QUERY PLAN                           
---------------------------------------------------------------
 Gather Motion 3:1  (slice1; segments: 3)
   ->  Nested Loop Left Join
         ->  Seq Scan on using_coerce_t1
               Filter: ((k)::text = 'a'::text)
         ->  Materialize
               ->  Broadcast Motion 3:3  (slice2; segments: 3)
                     ->  Seq Scan on using_coerce_t2
                           Filter: (k = 'a'::text)
 Optimizer: Postgres query optimizer
(9 rows)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, you are right — and it is worse than the RelabelType case: the same hole already exists on main for FuncExpr.

TranslateExprToDXLProject reuses the input colid only for a plain Var; every other expression gets a fresh colid from m_colid_counter. LoadProjectElements then records that colid for <query level, rtindex, attno>, but only the CoalesceExpr branch pushed the proj element into project_list_computed_cols_dxlnode. So for any coerced merged column the mapping points at a column nothing produces, and as you say flatten_join_alias_var_optimizer deliberately skips quals, so a predicate on the merged column reaches it.

Pre-existing repro on main, no RelabelType involved (int vs bigint ⇒ FuncExpr):

CREATE TABLE uf1(k int, v int) DISTRIBUTED BY (v);
CREATE TABLE uf2(k bigint, v int) DISTRIBUTED BY (v);
EXPLAIN (COSTS OFF) SELECT k FROM uf1 LEFT JOIN uf2 USING (k) WHERE k = 1;
INFO:  GPORCA failed to produce a plan, falling back to Postgres-based planner
DETAIL:  DXL-to-Expr Translation: Attribute number 21 not found in project list

Fixed in 59f8233 by making the computed-columns projection cover every non-Var join alias instead of enumerating node tags:

		if (!IsA(join_alias_node, Var))
		{
			project_elem_dxlnode->AddRef();
			project_list_computed_cols_dxlnode->AddChild(project_elem_dxlnode);
		}

Both of your queries and the int/bigint shape now plan in GPORCA:

 Gather Motion 3:1  (slice1; segments: 3)
   ->  Hash Left Join
         Hash Cond: ((using_coerce_t1.k)::text = using_coerce_t2.k)
         ->  Seq Scan on using_coerce_t1
               Filter: ((k)::text = 'a'::text)
         ->  Hash
               ->  Broadcast Motion 3:3  (slice2; segments: 3)
                     ->  Seq Scan on using_coerce_t2
 Optimizer: GPORCA

join_gp is extended with predicates on the merged column for the varchar/text, int/oid and int/bigint shapes, and passes with optimizer=on and optimizer=off.

IsA(join_alias_node, FuncExpr) ||
IsA(join_alias_node, CoalesceExpr));
// A merged USING column is a plain Var when both inputs have the same
// type, a CoalesceExpr for a FULL JOIN, and the outer input coerced to
// the common type otherwise (FuncExpr, RelabelType, ...). Any
// expression the scalar translator understands is acceptable here;
// unsupported nodes still raise the regular "unsupported feature"
// exception from TranslateExprToDXL.
CHAR *col_name_char_array = strVal(lfirst(lc_col_name));

// create the DXL node holding the target list entry and add it to proj list
CDXLNode *project_elem_dxlnode = TranslateExprToDXLProject(
(Expr *) join_alias_node, col_name_char_array);
project_list_dxlnode->AddChild(project_elem_dxlnode);

if (IsA(join_alias_node, CoalesceExpr))
if (!IsA(join_alias_node, Var))
{
// add coalesce expression to the computed columns
// TranslateExprToDXLProject reuses the input colid for a plain
// Var but allocates a new one for any other expression, so the
// column only exists if the join computes it. Quals are left
// alone by flatten_join_alias_var_optimizer and still reference
// the merged column through the mapping loaded below, hence the
// projection is required and not merely an optimization.
project_elem_dxlnode->AddRef();
project_list_computed_cols_dxlnode->AddChild(project_elem_dxlnode);
}
Expand Down
130 changes: 130 additions & 0 deletions src/test/regress/expected/join_gp.out
Original file line number Diff line number Diff line change
Expand Up @@ -3672,3 +3672,133 @@ select * from generate_series(1,2) t1, lateral (select t3.i from lat_oq_t2 t2 jo

drop table lat_oq_t2;
drop table lat_oq_t3;
--
-- Test that ORCA can plan a USING join whose merged column is the left input
-- coerced with a binary-compatible cast (RelabelType), e.g. varchar vs text or
-- int vs oid. The query-to-DXL translator used to assert on such join alias
-- vars and fall back to the Postgres planner.
--
create table using_coerce_t1(k varchar(10), f2 int) distributed by (f2);
create table using_coerce_t2(k text, f2 oid) distributed by (f2);
insert into using_coerce_t1 values ('a', 1), ('b', 2);
insert into using_coerce_t2 values ('a', 1), ('c', 3);
explain (costs off) select k from using_coerce_t1 left join using_coerce_t2 using(k) order by 1;
QUERY PLAN
--------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
Merge Key: ((using_coerce_t1.k)::character varying)
-> Sort
Sort Key: ((using_coerce_t1.k)::character varying)
-> Hash Right Join
Hash Cond: (using_coerce_t2.k = (using_coerce_t1.k)::text)
-> Redistribute Motion 3:3 (slice2; segments: 3)
Hash Key: using_coerce_t2.k
-> Seq Scan on using_coerce_t2
-> Hash
-> Redistribute Motion 3:3 (slice3; segments: 3)
Hash Key: using_coerce_t1.k
-> Seq Scan on using_coerce_t1
Optimizer: Postgres query optimizer
(14 rows)

select k from using_coerce_t1 left join using_coerce_t2 using(k) order by 1;
k
---
a
b
(2 rows)

select f2, count(*) from using_coerce_t1 left join using_coerce_t2 using(f2) group by f2 order by 1;
f2 | count
----+-------
1 | 1
2 | 1
(2 rows)

select f2, count(*) from using_coerce_t1 x(x0,x1) left join (using_coerce_t1 left join using_coerce_t2 using(f2)) on (x0 = 'a') group by f2 order by 1;
f2 | count
----+-------
1 | 1
2 | 1
| 1
(3 rows)

-- Quals are deliberately not flattened by flatten_join_alias_var_optimizer, so
-- a predicate on the merged column references the computed join output column
-- directly. Without a projection producing it ORCA fell back with
-- "Attribute number N not found in project list".
explain (costs off) select k from using_coerce_t1 left join using_coerce_t2 using(k) where k = 'a';
QUERY PLAN
---------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Nested Loop Left Join
-> Seq Scan on using_coerce_t1
Filter: ((k)::text = 'a'::text)
-> Materialize
-> Broadcast Motion 3:3 (slice2; segments: 3)
-> Seq Scan on using_coerce_t2
Filter: (k = 'a'::text)
Optimizer: Postgres query optimizer
(9 rows)

select k from using_coerce_t1 left join using_coerce_t2 using(k) where k = 'a';
k
---
a
(1 row)

select f2 from using_coerce_t1 join using_coerce_t2 using(f2) where f2 = 1;
f2
----
1
(1 row)

select k from using_coerce_t1 full join using_coerce_t2 using(k) where k = 'c';
k
---
c
(1 row)

drop table using_coerce_t1;
drop table using_coerce_t2;
--
-- Same, for a merged column that needs a real cast function (FuncExpr) rather
-- than a binary-compatible relabel.
--
create table using_coerce_t3(k int, v int) distributed by (v);
create table using_coerce_t4(k bigint, v int) distributed by (v);
insert into using_coerce_t3 values (1, 1), (2, 2);
insert into using_coerce_t4 values (1, 1), (3, 3);
explain (costs off) select k from using_coerce_t3 left join using_coerce_t4 using(k) where k = 1;
QUERY PLAN
------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Hash Right Join
Hash Cond: (using_coerce_t4.k = using_coerce_t3.k)
-> Redistribute Motion 3:3 (slice2; segments: 3)
Hash Key: using_coerce_t4.k
-> Seq Scan on using_coerce_t4
-> Hash
-> Redistribute Motion 3:3 (slice3; segments: 3)
Hash Key: using_coerce_t3.k
-> Seq Scan on using_coerce_t3
Filter: ((k)::bigint = 1)
Optimizer: Postgres query optimizer
(12 rows)

select k from using_coerce_t3 left join using_coerce_t4 using(k) where k = 1;
k
---
1
(1 row)

select k from using_coerce_t3 full join using_coerce_t4 using(k) order by 1;
k
---
1
2
3
(3 rows)

drop table using_coerce_t3;
drop table using_coerce_t4;
128 changes: 128 additions & 0 deletions src/test/regress/expected/join_gp_optimizer.out
Original file line number Diff line number Diff line change
Expand Up @@ -3634,3 +3634,131 @@ select * from generate_series(1,2) t1, lateral (select t3.i from lat_oq_t2 t2 jo

drop table lat_oq_t2;
drop table lat_oq_t3;
--
-- Test that ORCA can plan a USING join whose merged column is the left input
-- coerced with a binary-compatible cast (RelabelType), e.g. varchar vs text or
-- int vs oid. The query-to-DXL translator used to assert on such join alias
-- vars and fall back to the Postgres planner.
--
create table using_coerce_t1(k varchar(10), f2 int) distributed by (f2);
create table using_coerce_t2(k text, f2 oid) distributed by (f2);
insert into using_coerce_t1 values ('a', 1), ('b', 2);
insert into using_coerce_t2 values ('a', 1), ('c', 3);
explain (costs off) select k from using_coerce_t1 left join using_coerce_t2 using(k) order by 1;
QUERY PLAN
--------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
Merge Key: ((using_coerce_t1.k)::character varying)
-> Sort
Sort Key: ((using_coerce_t1.k)::character varying)
-> Hash Left Join
Hash Cond: ((using_coerce_t1.k)::text = using_coerce_t2.k)
-> Seq Scan on using_coerce_t1
-> Hash
-> Broadcast Motion 3:3 (slice2; segments: 3)
-> Seq Scan on using_coerce_t2
Optimizer: GPORCA
(11 rows)

select k from using_coerce_t1 left join using_coerce_t2 using(k) order by 1;
k
---
a
b
(2 rows)

select f2, count(*) from using_coerce_t1 left join using_coerce_t2 using(f2) group by f2 order by 1;
f2 | count
----+-------
1 | 1
2 | 1
(2 rows)

select f2, count(*) from using_coerce_t1 x(x0,x1) left join (using_coerce_t1 left join using_coerce_t2 using(f2)) on (x0 = 'a') group by f2 order by 1;
f2 | count
----+-------
1 | 1
2 | 1
| 1
(3 rows)

-- Quals are deliberately not flattened by flatten_join_alias_var_optimizer, so
-- a predicate on the merged column references the computed join output column
-- directly. Without a projection producing it ORCA fell back with
-- "Attribute number N not found in project list".
explain (costs off) select k from using_coerce_t1 left join using_coerce_t2 using(k) where k = 'a';
QUERY PLAN
--------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Hash Left Join
Hash Cond: ((using_coerce_t1.k)::text = using_coerce_t2.k)
-> Seq Scan on using_coerce_t1
Filter: ((k)::text = 'a'::text)
-> Hash
-> Broadcast Motion 3:3 (slice2; segments: 3)
-> Seq Scan on using_coerce_t2
Optimizer: GPORCA
(9 rows)

select k from using_coerce_t1 left join using_coerce_t2 using(k) where k = 'a';
k
---
a
(1 row)

select f2 from using_coerce_t1 join using_coerce_t2 using(f2) where f2 = 1;
f2
----
1
(1 row)

select k from using_coerce_t1 full join using_coerce_t2 using(k) where k = 'c';
k
---
c
(1 row)

drop table using_coerce_t1;
drop table using_coerce_t2;
--
-- Same, for a merged column that needs a real cast function (FuncExpr) rather
-- than a binary-compatible relabel.
--
create table using_coerce_t3(k int, v int) distributed by (v);
create table using_coerce_t4(k bigint, v int) distributed by (v);
insert into using_coerce_t3 values (1, 1), (2, 2);
insert into using_coerce_t4 values (1, 1), (3, 3);
explain (costs off) select k from using_coerce_t3 left join using_coerce_t4 using(k) where k = 1;
QUERY PLAN
----------------------------------------------------------------------------
Result
-> Gather Motion 3:1 (slice1; segments: 3)
-> Hash Left Join
Hash Cond: ((using_coerce_t3.k)::bigint = using_coerce_t4.k)
-> Redistribute Motion 3:3 (slice2; segments: 3)
Hash Key: (using_coerce_t3.k)::bigint
-> Seq Scan on using_coerce_t3
Filter: ((k)::bigint = 1)
-> Hash
-> Redistribute Motion 3:3 (slice3; segments: 3)
Hash Key: using_coerce_t4.k
-> Seq Scan on using_coerce_t4
Optimizer: GPORCA
(13 rows)

select k from using_coerce_t3 left join using_coerce_t4 using(k) where k = 1;
k
---
1
(1 row)

select k from using_coerce_t3 full join using_coerce_t4 using(k) order by 1;
k
---
1
2
3
(3 rows)

drop table using_coerce_t3;
drop table using_coerce_t4;
44 changes: 44 additions & 0 deletions src/test/regress/sql/join_gp.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1332,3 +1332,47 @@ select * from generate_series(1,2) t1, lateral (select t3.i from lat_oq_t2 t2 jo

drop table lat_oq_t2;
drop table lat_oq_t3;

--
-- Test that ORCA can plan a USING join whose merged column is the left input
-- coerced with a binary-compatible cast (RelabelType), e.g. varchar vs text or
-- int vs oid. The query-to-DXL translator used to assert on such join alias
-- vars and fall back to the Postgres planner.
--
create table using_coerce_t1(k varchar(10), f2 int) distributed by (f2);
create table using_coerce_t2(k text, f2 oid) distributed by (f2);
insert into using_coerce_t1 values ('a', 1), ('b', 2);
insert into using_coerce_t2 values ('a', 1), ('c', 3);

explain (costs off) select k from using_coerce_t1 left join using_coerce_t2 using(k) order by 1;
select k from using_coerce_t1 left join using_coerce_t2 using(k) order by 1;
select f2, count(*) from using_coerce_t1 left join using_coerce_t2 using(f2) group by f2 order by 1;
select f2, count(*) from using_coerce_t1 x(x0,x1) left join (using_coerce_t1 left join using_coerce_t2 using(f2)) on (x0 = 'a') group by f2 order by 1;

-- Quals are deliberately not flattened by flatten_join_alias_var_optimizer, so
-- a predicate on the merged column references the computed join output column
-- directly. Without a projection producing it ORCA fell back with
-- "Attribute number N not found in project list".
explain (costs off) select k from using_coerce_t1 left join using_coerce_t2 using(k) where k = 'a';
select k from using_coerce_t1 left join using_coerce_t2 using(k) where k = 'a';
select f2 from using_coerce_t1 join using_coerce_t2 using(f2) where f2 = 1;
select k from using_coerce_t1 full join using_coerce_t2 using(k) where k = 'c';

drop table using_coerce_t1;
drop table using_coerce_t2;

--
-- Same, for a merged column that needs a real cast function (FuncExpr) rather
-- than a binary-compatible relabel.
--
create table using_coerce_t3(k int, v int) distributed by (v);
create table using_coerce_t4(k bigint, v int) distributed by (v);
insert into using_coerce_t3 values (1, 1), (2, 2);
insert into using_coerce_t4 values (1, 1), (3, 3);

explain (costs off) select k from using_coerce_t3 left join using_coerce_t4 using(k) where k = 1;
select k from using_coerce_t3 left join using_coerce_t4 using(k) where k = 1;
select k from using_coerce_t3 full join using_coerce_t4 using(k) order by 1;

drop table using_coerce_t3;
drop table using_coerce_t4;
Loading