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)