Cheng Wang created CALCITE-7883:
-----------------------------------
Summary: RelToSqlConverter strips outer qualifier in
visitAntiOrSemiJoin, so INTERSECT/EXCEPT results compare against the wrong
column
Key: CALCITE-7883
URL: https://issues.apache.org/jira/browse/CALCITE-7883
Project: Calcite
Issue Type: Bug
Components: core
Affects Versions: 1.42.0
Environment: macOS / Linux, JDK 17/21, Calcite 1.42.0 and main branch
Reporter: Cheng Wang
h3. Description
When CoreRules.INTERSECT_TO_SEMI_JOIN or CoreRules.MINUS_TO_ANTI_JOIN rewrite a
set operation into a semi/anti join and RelToSqlConverter unparses it,
visitAntiOrSemiJoin (RelToSqlConverter.java:200-232) runs
AliasReplacementShuttle (RelToSqlConverter.java:387-392) over the join
condition.
The shuttle strips the outer table alias ("t") from the correlated references,
so an unqualified "DEPTNO" inside the generated EXISTS / NOT EXISTS resolves to
the inner relation ("t0"."DEPTNO") instead of the outer one. The resulting SQL
compares each outer column with the wrong inner column, and the enclosing GROUP
BY does not repair it.
h3. Reproducer (Calcite 1.42.0 and current main)
Schema and data:
* EMP(EMPNO, ENAME, JOB, MGR, SAL, COMM, DEPTNO) with rows:
(1, 'ALICE', 'CLERK', null, 100, null, 10),
(2, 'ALICE', 'ANALYST', 1, 150, 0, 10),
(3, 'CAROL', 'MGR', null, 200, null, 20),
(4, 'DAVE', null, 3, 300, 50, null)
* DEPT(DEPTNO, NAME) with rows:
(10, 'ACC'), (10, 'ACC_DUP'), (20, 'RES'), (null, 'NULL_DEPT')
Queries:
{code:sql}
-- INTERSECT case
SELECT DEPTNO, MGR FROM EMP INTERSECT SELECT DEPTNO, DEPTNO FROM DEPT;
-- EXCEPT case
SELECT DEPTNO, MGR FROM EMP EXCEPT SELECT DEPTNO, DEPTNO FROM DEPT;
{code}
Apply CoreRules.INTERSECT_TO_SEMI_JOIN / CoreRules.MINUS_TO_ANTI_JOIN, then
unparse using RelToSqlConverter.
h3. Expected vs Actual
* INTERSECT:
Original RelNode result: [] (correct)
Generated SQL round-trip result: [[10, null], [20, null]] (WRONG: unqualified
"DEPTNO" binds to "t0"."DEPTNO")
* EXCEPT:
Original RelNode result: [[10, 1], [10, null], [20, null], [null, 3]]
(correct)
Generated SQL round-trip result: [[10, 1], [null, 3]] (WRONG: rows silently
dropped)
Generated SQL for INTERSECT:
{code:sql}
SELECT "DEPTNO", "MGR"
FROM "EMP" AS "t"
WHERE EXISTS (SELECT 1
FROM (SELECT "DEPTNO", "DEPTNO" AS "DEPTNO0" FROM "DEPT") AS "t0"
WHERE "DEPTNO" IS NOT DISTINCT FROM "t0"."DEPTNO"
AND "MGR" IS NOT DISTINCT FROM "t0"."DEPTNO0")
GROUP BY "DEPTNO", "MGR"
{code}
h3. Root Cause
AliasReplacementShuttle replaces qualified references with unqualified aliases
from the inner subquery (RelToSqlConverter.java:387-392). visitAntiOrSemiJoin
emits the condition without re-qualifying the outer side, causing variable
capture.
This is distinct from closed issues CALCITE-5394 / CALCITE-5395 / CALCITE-6804
(which covered SELECT * and corrupted NOT EXISTS condition for a subquery
alias, not qualifier stripping in set-op rewrites).
h3. Suggested Fix
Do not strip a qualifier that becomes ambiguous inside the generated subquery;
retain the outer alias (e.g. "t"."DEPTNO") or assign the inner relation fresh
aliases before running AliasReplacementShuttle.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)