[ 
https://issues.apache.org/jira/browse/CALCITE-7616?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

leishp updated CALCITE-7616:
----------------------------
    Description: 
When a JDBC schema's dialect supports window functions (e.g. MySQL, 
PostgreSQL), a query with CTE and RANK() throws AssertionError during 
VolcanoPlanner optimization:

{code} AssertionError: Relational expression  LogicalWindow.JDBC.TEST.[]  has 
calling-convention JDBC.TEST but does not implement the required interface  
'interface org.apache.calcite.adapter.jdbc.JdbcRel' of that convention {code}

_Reproduction:_

A JDBC schema with MySQL dialect (\{{supportsWindowFunctions() = true}}), and a 
query with CTE + RANK():

{code:sql} WITH ranked AS (  SELECT customer_name, contract_amount,  RANK() 
OVER (ORDER BY contract_amount DESC) AS rnk  FROM sales_data ) SELECT 
customer_name FROM ranked WHERE rnk <= 3 {code}

_Root cause:_

{\{ProjectToLogicalProjectAndWindowRule}} has no convention restriction in its 
operand definition:

{code:java} // ProjectToWindowRule.java:196 .withOperandSupplier(b ->  
b.operand(Project.class)  .predicate(Project::containsOver)  .anyInputs()) // 
trait=null matches ANY convention {code}

When \{{JdbcProjectRule}} first converts a \{{LogicalProject}} with OVER to 
\{{JdbcProject}} (JDBC convention), and then 
\{{ProjectToLogicalProjectAndWindowRule}} fires on it, the JDBC traitSet 
propagates through \{{CalcRelSplitter}} to \{{LogicalWindow.create()}}, causing 
the AssertionError.

This is related to CALCITE-3352 (wrong collation on generated Window) — both 
bugs share the same root cause: \{{ProjectToWindowRule}} blindly propagates the 
parent's traitSet to the generated \{{LogicalWindow}}. Danny Chen commented on 
CALCITE-3352: "we should limit this rule to only match logical nodes because it 
is a plan rewrite."

_Fix:_

Add \{{.trait(Convention.NONE)}} to the operand:

{code:java} // ProjectToWindowRule.java .withOperandSupplier(b ->  
b.operand(Project.class)  .trait(Convention.NONE) // only match logical Project 
 .predicate(Project::containsOver)  .anyInputs()) {code}

_Impact:_
 * When dialect supports window functions: window function is pushed down to 
database via \{{JdbcProject}}. Correct behavior.

 * When dialect does NOT support window functions: 
\{{ProjectToLogicalProjectAndWindowRule}} fires on \{{LogicalProject(NONE)}} as 
before. No change.

_Scope:_ This is a targeted fix for the convention issue only. The collation 
issue (CALCITE-3352) is tracked separately.

_Test:_ A JUnit test \{{JdbcWindowConventionBugTest}} is provided in the PR, 
using HSQLDB in-memory database with MySQL dialect override to reproduce the 
bug without an external database.

_Workaround:_ {code:java} 
planner.removeRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW); {code}

  was:
{quote} When a JDBC schema's dialect supports window functions (e.g. MySQL, 
PostgreSQL), a query with CTE and RANK() throws AssertionError during 
VolcanoPlanner optimization:

{code} AssertionError: Relational expression  LogicalWindow.JDBC.TEST.[]  has 
calling-convention JDBC.TEST but does not implement the required interface  
'interface org.apache.calcite.adapter.jdbc.JdbcRel' of that convention {code}

_Reproduction:_

A JDBC schema with MySQL dialect (\{{supportsWindowFunctions() = true}}), and a 
query with CTE + RANK():

{code:sql} WITH ranked AS (  SELECT customer_name, contract_amount,  RANK() 
OVER (ORDER BY contract_amount DESC) AS rnk  FROM sales_data ) SELECT 
customer_name FROM ranked WHERE rnk <= 3 {code}

_Root cause:_

{\{ProjectToLogicalProjectAndWindowRule}} has no convention restriction in its 
operand definition:

{code:java} // ProjectToWindowRule.java:196 .withOperandSupplier(b ->  
b.operand(Project.class)  .predicate(Project::containsOver)  .anyInputs()) // 
trait=null matches ANY convention {code}

When \{{JdbcProjectRule}} first converts a \{{LogicalProject}} with OVER to 
\{{JdbcProject}} (JDBC convention), and then 
\{{ProjectToLogicalProjectAndWindowRule}} fires on it, the JDBC traitSet 
propagates through \{{CalcRelSplitter}} to \{{LogicalWindow.create()}}, causing 
the AssertionError.

This is related to CALCITE-3352 (wrong collation on generated Window) — both 
bugs share the same root cause: \{{ProjectToWindowRule}} blindly propagates the 
parent's traitSet to the generated \{{LogicalWindow}}. Danny Chen commented on 
CALCITE-3352: "we should limit this rule to only match logical nodes because it 
is a plan rewrite."

_Fix:_

Add \{{.trait(Convention.NONE)}} to the operand:

{code:java} // ProjectToWindowRule.java .withOperandSupplier(b ->  
b.operand(Project.class)  .trait(Convention.NONE) // only match logical Project 
 .predicate(Project::containsOver)  .anyInputs()) {code}

_Impact:_
 * When dialect supports window functions: window function is pushed down to 
database via \{{JdbcProject}}. Correct behavior.

 * When dialect does NOT support window functions: 
\{{ProjectToLogicalProjectAndWindowRule}} fires on \{{LogicalProject(NONE)}} as 
before. No change.

_Scope:_ This is a targeted fix for the convention issue only. The collation 
issue (CALCITE-3352) is tracked separately.

_Test:_ A JUnit test \{{JdbcWindowConventionBugTest}} is provided in the PR, 
using HSQLDB in-memory database with MySQL dialect override to reproduce the 
bug without an external database.

_Workaround:_ {code:java} 
planner.removeRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW); {code} 
{quote}


> ProjectToLogicalProjectAndWindowRule should not match non-logical Project 
> nodes with JDBC convention
> ----------------------------------------------------------------------------------------------------
>
>                 Key: CALCITE-7616
>                 URL: https://issues.apache.org/jira/browse/CALCITE-7616
>             Project: Calcite
>          Issue Type: Bug
>          Components: core
>            Reporter: leishp
>            Priority: Major
>
> When a JDBC schema's dialect supports window functions (e.g. MySQL, 
> PostgreSQL), a query with CTE and RANK() throws AssertionError during 
> VolcanoPlanner optimization:
> {code} AssertionError: Relational expression  LogicalWindow.JDBC.TEST.[]  has 
> calling-convention JDBC.TEST but does not implement the required interface  
> 'interface org.apache.calcite.adapter.jdbc.JdbcRel' of that convention {code}
> _Reproduction:_
> A JDBC schema with MySQL dialect (\{{supportsWindowFunctions() = true}}), and 
> a query with CTE + RANK():
> {code:sql} WITH ranked AS (  SELECT customer_name, contract_amount,  RANK() 
> OVER (ORDER BY contract_amount DESC) AS rnk  FROM sales_data ) SELECT 
> customer_name FROM ranked WHERE rnk <= 3 {code}
> _Root cause:_
> {\{ProjectToLogicalProjectAndWindowRule}} has no convention restriction in 
> its operand definition:
> {code:java} // ProjectToWindowRule.java:196 .withOperandSupplier(b ->  
> b.operand(Project.class)  .predicate(Project::containsOver)  .anyInputs()) // 
> trait=null matches ANY convention {code}
> When \{{JdbcProjectRule}} first converts a \{{LogicalProject}} with OVER to 
> \{{JdbcProject}} (JDBC convention), and then 
> \{{ProjectToLogicalProjectAndWindowRule}} fires on it, the JDBC traitSet 
> propagates through \{{CalcRelSplitter}} to \{{LogicalWindow.create()}}, 
> causing the AssertionError.
> This is related to CALCITE-3352 (wrong collation on generated Window) — both 
> bugs share the same root cause: \{{ProjectToWindowRule}} blindly propagates 
> the parent's traitSet to the generated \{{LogicalWindow}}. Danny Chen 
> commented on CALCITE-3352: "we should limit this rule to only match logical 
> nodes because it is a plan rewrite."
> _Fix:_
> Add \{{.trait(Convention.NONE)}} to the operand:
> {code:java} // ProjectToWindowRule.java .withOperandSupplier(b ->  
> b.operand(Project.class)  .trait(Convention.NONE) // only match logical 
> Project  .predicate(Project::containsOver)  .anyInputs()) {code}
> _Impact:_
>  * When dialect supports window functions: window function is pushed down to 
> database via \{{JdbcProject}}. Correct behavior.
>  * When dialect does NOT support window functions: 
> \{{ProjectToLogicalProjectAndWindowRule}} fires on \{{LogicalProject(NONE)}} 
> as before. No change.
> _Scope:_ This is a targeted fix for the convention issue only. The collation 
> issue (CALCITE-3352) is tracked separately.
> _Test:_ A JUnit test \{{JdbcWindowConventionBugTest}} is provided in the PR, 
> using HSQLDB in-memory database with MySQL dialect override to reproduce the 
> bug without an external database.
> _Workaround:_ {code:java} 
> planner.removeRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW); {code}



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

Reply via email to