Lino Rosa created CALCITE-6691: ---------------------------------- Summary: Qualify expressions not updated when merging two Project Key: CALCITE-6691 URL: https://issues.apache.org/jira/browse/CALCITE-6691 Project: Calcite Issue Type: Bug Reporter: Lino Rosa
Given a query like the below {code:sql} WITH t0 AS ( SELECT name, salary FROM mytable ), t1 AS ( SELECT t0.name FROM t0 qualify row_number() over (partition by t0.name order by t0.salary desc) = 1 ) select name from t1 {code} We end up with the *wrong* query plan below: {noformat} [Logical plan] LogicalProject(name=[$0]) LogicalFilter(condition=[$1]) LogicalProject(name=[$5], QualifyExpression=[=(ROW_NUMBER() OVER (PARTITION BY $0 ORDER BY $1 DESC), 1)]) LogicalTableScan(table=[[mytable]]) {noformat} Under {{PARTITION BY $0}}, the {{$0}} should be referencing {{t0.name}} which on the select list on the same node is {{name=[$5]}}. So something is off. ---- After some debugging I can trace back the issue. Here is the query plan *unsquashed*: {noformat} [Logical plan] LogicalProject(name=[$0]) LogicalProject(name=[$0]) LogicalFilter(condition=[$1]) LogicalProject(name=[$0], QualifyExpression=[=(ROW_NUMBER() OVER (PARTITION BY $0 ORDER BY $1 DESC), 1)]) LogicalProject(name=[$5], salary=[$6]) LogicalTableScan(table=[[mytable]]) {noformat} And here it is *squashed*: {noformat} [Logical plan] LogicalProject(name=[$0]) LogicalFilter(condition=[$1]) LogicalProject(name=[$5], QualifyExpression=[=(ROW_NUMBER() OVER (PARTITION BY $0 ORDER BY $1 DESC), 1)]) LogicalTableScan(table=[[mytable]]) {noformat} This is the relevant section before squashing: {noformat} (1) LogicalProject(name=[$0], QualifyExpression=[=(ROW_NUMBER() OVER (PARTITION BY $0 ORDER BY $1 DESC), 1)]) (2) LogicalProject(name=[$5], salary=[$6]) (3) LogicalTableScan(table=[[mytable]]) {noformat} When it's generating {{(1)}} from {{SqlToRelConverter}}, Calcite realizes it has an opportunity to squash {{(1)}} and {{(2)}} together. It will keep {{(1)}} and delete {{(2)}}. But to do that it will first have to replace references from {{(1)}} and rewrite them to push past the parent project {{(2)}} (See RelOptUtil#pushPastProject). It does this in steps, first it goes over the projected columns (in this case only {{name}}) then over any qualify expressions. The rough logic is straightforward. It takes {{name=[$0]}} from {{(1)}} and replaces that with whatever sits on index {{$0}} on {{(2)}}. So {{name=[$0]}} becomes {{name=[$5]}}. Then it removes {{(2)}} from the query plan. At this point we have this: {noformat} (1) LogicalProject(name=[$5], QualifyExpression=[=(ROW_NUMBER() OVER (PARTITION BY $0 ORDER BY $1 DESC), 1)]) (3) LogicalTableScan(table=[[mytable]]) {noformat} Now Calcite will do the same for the {{QualifyExpression}} which still has the wrong indexes. This is where it goes wrong. It eagerly removed {{(2)}} from the plan so there's no way for it to even know it there was another projection in there. It won't do anything and the indexes for {{QualifyExpression}} are now all wrong. More specifically right in this section inside {{RelBuilder#Project}}, {{frame.rel}} will not be an instance of {{Project}} because we just removed that project (it was {{(2)}}). It will be an instance of {{TableScan}} at this point. {code:java} // Do not merge projection when top projection has correlation variables bloat: if (frame.rel instanceof Project && config.bloat() >= 0 && variables.isEmpty()) { final Project project = (Project) frame.rel; {code} ---- I suppose the fix would be to delay removing the parent {{Project}} from the plan to after we've processed the entire {{Project}}. But I don't know that much about the project to try it. I'm happy to give it a shot if any of the maintainers point me in the right direction. -- This message was sent by Atlassian Jira (v8.20.10#820010)