You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@calcite.apache.org by "Vladimir Sitnikov (Jira)" <ji...@apache.org> on 2026/10/22 12:47:00 UTC

[jira] [Created] (CALCITE-7876) VolcanoPlanner fires logical transformation rules such as ProjectMergeRule on JDBC rels

Vladimir Sitnikov created CALCITE-7876:
------------------------------------------

             Summary: VolcanoPlanner fires logical transformation rules such as ProjectMergeRule on JDBC rels
                 Key: CALCITE-7876
                 URL: https://issues.apache.org/jira/browse/CALCITE-7876
             Project: Calcite
          Issue Type: Bug
          Components: core, jdbc-adapter
            Reporter: Vladimir Sitnikov


When I plan a query against a JDBC schema, {{VolcanoPlanner}} fires logical transformation rules on JDBC rels. {{ProjectMergeRule}} matches two {{JdbcProject}} rels and produces a {{LogicalProject}}; {{FilterProjectTransposeRule}} matches {{JdbcFilter}} over {{JdbcProject}} and produces a {{JdbcProject}}. The same rules do not fire on Enumerable rels.

*Expected*

The javadoc of {{TransformationRule}} says: "only logical operator can be rule operand, and only generate logical alternatives". So in {{VolcanoPlanner}}, {{ProjectMergeRule}}, {{FilterProjectTransposeRule}}, and {{SortRemoveConstantKeysRule}} (via {{SubstitutionRule}}) should have no attempts whose first operand is a {{JdbcRel}}, which is what happens for {{EnumerableRel}} today.

*Actual*

Run on main at 6ff13eaccd (1.43.0-SNAPSHOT), with the {{scott}} JDBC model from the test suite. Rule attempts whose first operand is a {{JdbcRel}}, with the operand classes:

{noformat}
JDBC_ATTEMPTS={EnumerableLimitRule on JdbcSort,=2, FilterProjectTransposeRule on JdbcFilter,JdbcProject,=1, JdbcToEnumerableConverterRule(in:JDBC.SCOTT,out:ENUMERABLE) on JdbcFilter,=2, JdbcToEnumerableConverterRule(in:JDBC.SCOTT,out:ENUMERABLE) on JdbcProject,=4, JdbcToEnumerableConverterRule(in:JDBC.SCOTT,out:ENUMERABLE) on JdbcSort,=2, JdbcToEnumerableConverterRule(in:JDBC.SCOTT,out:ENUMERABLE) on JdbcTableScan,=1, ProjectMergeRule on JdbcProject,JdbcProject,=1, SortRemoveConstantKeysRule on JdbcSort,=2, SortRemoveRule on JdbcSort,=2}
JDBC_PRODUCED={EnumerableLimitRule -> EnumerableLimit=1, FilterProjectTransposeRule -> JdbcProject=1, JdbcToEnumerableConverterRule(in:JDBC.SCOTT,out:ENUMERABLE) -> JdbcToEnumerableConverter=9, ProjectMergeRule -> LogicalProject=1, SortRemoveRule -> RelSubset=1}
{noformat}

The same probe on the {{hr}} reflective schema (Enumerable only) recorded one attempt on an {{EnumerableRel}}: {{EnumerableLimitRule}} on {{EnumerableSort}}, whose {{onMatch}} returns at once because the sort has no fetch.

I did not measure planning time, so I do not know how much these attempts cost on large queries.

*Reproducer* (a JUnit 5 test in {{core/src/test/java}})

{code:java}
@Test void transformationRulesOnJdbcRels() {
  Map<String, Integer> attempts = new TreeMap<>();
  Map<String, Integer> produced = new TreeMap<>();
  CalciteAssert.model(JdbcTest.SCOTT_MODEL)
      .query("select x + 1 as y from (select empno + deptno as x, ename"
          + " from scott.emp where deptno > 10) order by y limit 2")
      .withHook(Hook.PLANNER, (Consumer<RelOptPlanner>) planner ->
          planner.addListener(new RelOptListener() {
            @Override public void ruleAttempted(RuleAttemptedEvent e) {
              if (e.isBefore() && e.getRuleCall().rels[0] instanceof JdbcRel) {
                StringBuilder sb = new StringBuilder();
                for (RelNode r : e.getRuleCall().rels) {
                  sb.append(r.getClass().getSimpleName()).append(',');
                }
                attempts.merge(e.getRuleCall().getRule() + " on " + sb, 1,
                    Integer::sum);
              }
            }
            @Override public void ruleProductionSucceeded(RuleProductionEvent e) {
              if (!e.isBefore() && e.getRuleCall().rels[0] instanceof JdbcRel) {
                produced.merge(e.getRuleCall().getRule() + " -> "
                    + e.getRel().getClass().getSimpleName(), 1, Integer::sum);
              }
            }
            @Override public void relEquivalenceFound(RelEquivalenceEvent e) { }
            @Override public void relDiscarded(RelDiscardedEvent e) { }
            @Override public void relChosen(RelChosenEvent e) { }
          }))
      .returnsCount(2);
  System.out.println("JDBC_ATTEMPTS=" + attempts);
  System.out.println("JDBC_PRODUCED=" + produced);
}
{code}

*Analysis (a guess; the report above stands without it)*

{{VolcanoPlanner}} skips {{TransformationRule}} operands only for classes that implement {{PhysicalNode}} ({{VolcanoPlanner#addRule}} and {{VolcanoPlanner#onNewClass}}, added by CALCITE-3997). {{EnumerableRel}} extends {{PhysicalNode}}; {{JdbcRel}} and {{BindableRel}} extend {{RelNode}} only, and so do the rel interfaces of the other adapters. One possible fix is to make those interfaces extend {{PhysicalNode}}; I have not checked what else that changes, for example how {{passThrough}} and {{derive}} behave in top-down mode.

CALCITE-3455 reported this for Enumerable rels, and CALCITE-3997 fixed it there; this issue covers the conventions that CALCITE-3997 left out.




--
This message was sent by Atlassian Jira
(v10.3.26#10030026)