Iurii Gerzhedovich created CALCITE-7856:
-------------------------------------------

             Summary: MERGE with a correlated sub-query in the UPDATE SET 
clause resolves a target column to the same-named source column
                 Key: CALCITE-7856
                 URL: https://issues.apache.org/jira/browse/CALCITE-7856
             Project: Calcite
          Issue Type: Bug
          Components: core
    Affects Versions: 1.42.0, 1.43.0
            Reporter: Iurii Gerzhedovich


In a MERGE statement, a correlated sub-query in `WHEN MATCHED THEN UPDATE SET` 
that references a target column
whose name is also a column of the source silently reads the source column 
instead.

Reproducer (SqlToRelConverterTest style, `withExpand(false)`):

{code:sql}
merge into empnullables e
using (select * from emp) t
on e.empno = t.empno
when matched then update
set sal = (select d.deptno from dept d where d.deptno = e.deptno)
{code}

`e.deptno` must refer to the target (EMPNULLABLES.DEPTNO, field `DEPTNO0` of 
the join row), but the generated plan reads
the source field:

{noformat}
LogicalTableModify(table=[[CATALOG, SALES, EMPNULLABLES]], operation=[MERGE], 
updateColumnList=[[SAL]], flattened=[true])
  LogicalProject(EMPNO0=[$9], ..., $f9=[$SCALAR_QUERY({
LogicalProject(DEPTNO=[$0])
  LogicalFilter(condition=[=($0, $cor0.DEPTNO)])      <-- $cor0 is typed with 
the target row type only, index 7
    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
})])
    LogicalJoin(condition=[=($9, $0)], joinType=[inner])
      ...
{noformat}

Note that the project has no {{variablesSet}}, so {{$cor0}} is not bound to the 
join row; at index 7 of the join row
is the *source* DEPTNO. Expected: {{$cor0.DEPTNO0}} (index 16) and 
{{variablesSet=[[$cor0]]}}.

Same with plain tables:

{code:sql}
create table merge_t (id int primary key, d int, v int);
create table merge_s (id int primary key, d int, v int);
create table ref_t (k int primary key, val int);
-- merge_t: (1, 100, 10); merge_s: (1, 200, 20); ref_t: (100, 1), (200, 2)
merge into merge_t as t using merge_s as s on t.id = s.id
when matched then update set d = (select r.val from ref_t r where r.k = t.d)
-- expected merge_t.d = 1, actual plan reads s.d and yields 2
{code}

Root cause:

{{SqlValidatorImpl.rewriteMerge}} builds the select list of the MERGE source 
select by {{SqlNode.clone}}-ing the
select list of the UPDATE's own source select. The clone is shallow: the 
sub-query {{SqlSelect}} and its identifiers
are shared between the two selects. The validator keeps one scope per node, so 
the sub-query is registered twice and
the last registration wins, which is the scope of the UPDATE source select 
(target table only). While converting the
MERGE source select, {{e.deptno}} is therefore resolved against a scope that 
contains only the target table. The
correlation variable gets the target row type and index 7, and 
{{getCorrelationUse}} considers it as not being in the
current scope and does not set it. {{convertMerge}} also rebuilds the top 
projection with
{{relBuilder.project(projects)}}, which drops {{variablesSet}} even when it was 
set.

Fix: deep-copy the select list in {{rewriteMerge}}, and keep {{variablesSet}} 
of the source projection in
{{convertMerge}}.

Not covered: with {{expand = true}} the sub-query is expanded into a 
{{LogicalCorrelate}} and {{convertMerge}} fails
on its {{(LogicalJoin) mergeSourceRel.getInput(0)}} cast (without the fix it 
fails earlier with
"correlation id $cor0 not found in correlation list []"). This looks like a 
separate problem.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to