diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableConvention.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableConvention.java index 41bb8e551175..c07bb0b9b801 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableConvention.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableConvention.java @@ -62,6 +62,6 @@ public boolean canConvertConvention(Convention toConvention) { public boolean useAbstractConvertersForConversion(RelTraitSet fromTraits, RelTraitSet toTraits) { - return false; + return true; } } diff --git a/core/src/main/java/org/apache/calcite/plan/Convention.java b/core/src/main/java/org/apache/calcite/plan/Convention.java index 5ad184790f61..e4df9209147c 100644 --- a/core/src/main/java/org/apache/calcite/plan/Convention.java +++ b/core/src/main/java/org/apache/calcite/plan/Convention.java @@ -44,7 +44,9 @@ public interface Convention extends RelTrait { * @param toConvention Desired convention to convert to * @return Whether we should convert from this convention to toConvention */ - boolean canConvertConvention(Convention toConvention); + default boolean canConvertConvention(Convention toConvention) { + return false; + } /** * Returns whether we should convert from this trait set to the other trait @@ -59,8 +61,10 @@ public interface Convention extends RelTrait { * @param toTraits Target traits * @return Whether we should add converters */ - boolean useAbstractConvertersForConversion(RelTraitSet fromTraits, - RelTraitSet toTraits); + default boolean useAbstractConvertersForConversion(RelTraitSet fromTraits, + RelTraitSet toTraits) { + return true; + } /** * Default implementation. diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/AbstractConverter.java b/core/src/main/java/org/apache/calcite/plan/volcano/AbstractConverter.java index 0afdd6965268..f1ef0b9b9969 100644 --- a/core/src/main/java/org/apache/calcite/plan/volcano/AbstractConverter.java +++ b/core/src/main/java/org/apache/calcite/plan/volcano/AbstractConverter.java @@ -81,6 +81,10 @@ public RelWriter explainTerms(RelWriter pw) { return pw; } + @Override public boolean isEnforcer() { + return true; + } + //~ Inner Classes ---------------------------------------------------------- /** diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java b/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java index 03143452643a..94770946dd99 100644 --- a/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java +++ b/core/src/main/java/org/apache/calcite/plan/volcano/RelSet.java @@ -16,6 +16,7 @@ */ package org.apache.calcite.plan.volcano; +import org.apache.calcite.plan.Convention; import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptListener; import org.apache.calcite.plan.RelOptUtil; @@ -38,6 +39,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * A RelSet is an equivalence-set of expressions; that is, a set of @@ -160,107 +162,106 @@ void obliterateRelNode(RelNode rel) { public RelSubset add(RelNode rel) { assert equivalentSet == null : "adding to a dead set"; final RelTraitSet traitSet = rel.getTraitSet().simplify(); - final RelSubset subset = getOrCreateSubset(rel.getCluster(), traitSet); + final RelSubset subset = getOrCreateSubset( + rel.getCluster(), traitSet, rel.isEnforcer()); subset.add(rel); return subset; } + /** + * If the subset is required, convert derived subsets to this subset. + * Otherwise, convert this subset to required subsets in this RelSet. + * The subset can be both required and derived. + */ private void addAbstractConverters( - VolcanoPlanner planner, RelOptCluster cluster, RelSubset subset, boolean subsetToOthers) { - // Converters from newly introduced subset to all the remaining one (vice versa), only if - // we can convert. No point adding converters if it is not possible. - for (RelSubset other : subsets) { + RelOptCluster cluster, RelSubset subset, boolean required) { + List others = subsets.stream().filter( + n -> required ? n.isDerived() : n.isRequired()) + .collect(Collectors.toList()); + for (RelSubset other : others) { assert other.getTraitSet().size() == subset.getTraitSet().size(); + RelSubset from = subset; + RelSubset to = other; + + if (required) { + from = other; + to = subset; + } - if ((other == subset) - || (subsetToOthers - && !subset.getConvention().useAbstractConvertersForConversion( - subset.getTraitSet(), other.getTraitSet())) - || (!subsetToOthers - && !other.getConvention().useAbstractConvertersForConversion( - other.getTraitSet(), subset.getTraitSet()))) { + if (from == to || !from.getConvention() + .useAbstractConvertersForConversion( + from.getTraitSet(), to.getTraitSet())) { continue; } final ImmutableList difference = - subset.getTraitSet().difference(other.getTraitSet()); + to.getTraitSet().difference(from.getTraitSet()); - boolean addAbstractConverter = true; - int numTraitNeedConvert = 0; - - for (RelTrait curOtherTrait : difference) { - RelTraitDef traitDef = curOtherTrait.getTraitDef(); - RelTrait curRelTrait = subset.getTraitSet().getTrait(traitDef); - - if (curRelTrait == null) { - addAbstractConverter = false; - break; - } + boolean needsConverter = false; - assert curRelTrait.getTraitDef() == traitDef; - - boolean canConvert = false; - boolean needConvert = false; - if (subsetToOthers) { - // We can convert from subset to other. So, add converter with subset as child and - // traitset as the other's traitset. - canConvert = traitDef.canConvert( - cluster.getPlanner(), curRelTrait, curOtherTrait, subset); - needConvert = !curRelTrait.satisfies(curOtherTrait); - } else { - // We can convert from others to subset. - canConvert = traitDef.canConvert( - cluster.getPlanner(), curOtherTrait, curRelTrait, other); - needConvert = !curOtherTrait.satisfies(curRelTrait); - } + for (RelTrait fromTrait : difference) { + RelTraitDef traitDef = fromTrait.getTraitDef(); + RelTrait toTrait = to.getTraitSet().getTrait(traitDef); - if (!canConvert) { - addAbstractConverter = false; + if (toTrait == null || !traitDef.canConvert( + cluster.getPlanner(), fromTrait, toTrait, from)) { + needsConverter = false; break; } - if (needConvert) { - numTraitNeedConvert++; + if (!fromTrait.satisfies(toTrait)) { + needsConverter = true; } } - if (addAbstractConverter && numTraitNeedConvert > 0) { - if (subsetToOthers) { - final AbstractConverter converter = - new AbstractConverter(cluster, subset, null, other.getTraitSet()); - planner.register(converter, other); - } else { - final AbstractConverter converter = - new AbstractConverter(cluster, other, null, subset.getTraitSet()); - planner.register(converter, subset); - } + if (needsConverter) { + final AbstractConverter converter = + new AbstractConverter(cluster, from, null, to.getTraitSet()); + cluster.getPlanner().register(converter, to); } } } + RelSubset getOrCreateSubset(RelOptCluster cluster, RelTraitSet traits) { + return getOrCreateSubset(cluster, traits, false); + } + RelSubset getOrCreateSubset( - RelOptCluster cluster, - RelTraitSet traits) { + RelOptCluster cluster, RelTraitSet traits, boolean required) { + boolean needsConverter = false; RelSubset subset = getSubset(traits); + if (subset == null) { + needsConverter = true; subset = new RelSubset(cluster, this, traits); - final VolcanoPlanner planner = - (VolcanoPlanner) cluster.getPlanner(); - - addAbstractConverters(planner, cluster, subset, true); - - // Need to first add to subset before adding the abstract converters (for others->subset) - // since otherwise during register() the planner will try to add this subset again. + // Need to first add to subset before adding the abstract + // converters (for others->subset), since otherwise during + // register() the planner will try to add this subset again. subsets.add(subset); - addAbstractConverters(planner, cluster, subset, false); - + final VolcanoPlanner planner = (VolcanoPlanner) cluster.getPlanner(); if (planner.listener != null) { postEquivalenceEvent(planner, subset); } + } else if ((required && !subset.isRequired()) + || (!required && !subset.isDerived())) { + needsConverter = true; + } + + if (subset.getConvention() == Convention.NONE) { + needsConverter = false; + } else if (required) { + subset.setRequired(); + } else { + subset.setDerived(); + } + + if (needsConverter) { + addAbstractConverters(cluster, subset, required); } + return subset; } @@ -327,7 +328,8 @@ void mergeWith( assert otherSet.equivalentSet == null; LOGGER.trace("Merge set#{} into set#{}", otherSet.id, id); otherSet.equivalentSet = this; - RelMetadataQuery mq = rel.getCluster().getMetadataQuery(); + RelOptCluster cluster = rel.getCluster(); + RelMetadataQuery mq = cluster.getMetadataQuery(); // remove from table boolean existed = planner.allSets.remove(otherSet); @@ -337,10 +339,20 @@ void mergeWith( // merge subsets for (RelSubset otherSubset : otherSet.subsets) { - RelSubset subset = - getOrCreateSubset( - otherSubset.getCluster(), - otherSubset.getTraitSet()); + RelSubset subset = null; + RelTraitSet otherTraits = otherSubset.getTraitSet(); + + // If it is logical or derived physical traitSet + if (otherSubset.isDerived() || !otherSubset.isRequired()) { + subset = getOrCreateSubset(cluster, otherTraits, false); + } + + // It may be required only, or both derived and required, + // in which case, register again. + if (otherSubset.isRequired()) { + subset = getOrCreateSubset(cluster, otherTraits, true); + } + // collect RelSubset instances, whose best should be changed if (otherSubset.bestCost.isLt(subset.bestCost)) { changedSubsets.put(subset, otherSubset.best); diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/RelSubset.java b/core/src/main/java/org/apache/calcite/plan/volcano/RelSubset.java index 80bbab8bc1df..03759550d426 100644 --- a/core/src/main/java/org/apache/calcite/plan/volcano/RelSubset.java +++ b/core/src/main/java/org/apache/calcite/plan/volcano/RelSubset.java @@ -74,6 +74,8 @@ public class RelSubset extends AbstractRelNode { //~ Static fields/initializers --------------------------------------------- private static final Logger LOGGER = CalciteTrace.getPlannerTracer(); + private static final int DERIVED = 1; + private static final int REQUIRED = 2; //~ Instance fields -------------------------------------------------------- @@ -98,10 +100,13 @@ public class RelSubset extends AbstractRelNode { long timestamp; /** - * Flag indicating whether this RelSubset's importance was artificially - * boosted. + * Physical property state of current subset + * 0: logical operators, NONE convention is neither DERIVED nor REQUIRED + * 1: traitSet DERIVED from child operators or itself + * 2: traitSet REQUIRED from parent operators + * 3: both DERIVED and REQUIRED */ - boolean boosted; + private int state = 0; //~ Constructors ----------------------------------------------------------- @@ -111,7 +116,6 @@ public class RelSubset extends AbstractRelNode { RelTraitSet traits) { super(cluster, traits); this.set = set; - this.boosted = false; assert traits.allSimple(); computeBestCost(cluster.getPlanner()); recomputeDigest(); @@ -144,6 +148,22 @@ private void computeBestCost(RelOptPlanner planner) { } } + void setDerived() { + state |= DERIVED; + } + + void setRequired() { + state |= REQUIRED; + } + + public boolean isDerived() { + return (state & DERIVED) == DERIVED; + } + + public boolean isRequired() { + return (state & REQUIRED) == REQUIRED; + } + public RelNode getBest() { return best; } diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java index ab21319bec3f..ef631d528c48 100644 --- a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java +++ b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java @@ -495,7 +495,8 @@ public RelNode changeTraits(final RelNode rel, RelTraitSet toTraits) { return rel2; } - return rel2.set.getOrCreateSubset(rel.getCluster(), toTraits.simplify()); + return rel2.set.getOrCreateSubset( + rel.getCluster(), toTraits, true); } public RelOptPlanner chooseDelegate() { diff --git a/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java b/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java index 703a782df413..8240c60fddd3 100644 --- a/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java +++ b/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java @@ -248,6 +248,10 @@ public void collectVariablesUsed(Set variableSet) { // for default case, nothing to do } + public boolean isEnforcer() { + return false; + } + public void collectVariablesSet(Set variableSet) { } diff --git a/core/src/main/java/org/apache/calcite/rel/RelNode.java b/core/src/main/java/org/apache/calcite/rel/RelNode.java index 362e885c7bd3..11c35efb2335 100644 --- a/core/src/main/java/org/apache/calcite/rel/RelNode.java +++ b/core/src/main/java/org/apache/calcite/rel/RelNode.java @@ -407,6 +407,17 @@ RelNode copy( */ void register(RelOptPlanner planner); + /** + * Indicates whether it is an enforcer operator, e.g. PhysicalSort, + * PhysicalHashDistribute, etc. As an enforcer, the operator must be + * created only when required traitSet is not satisfied by its input. + * + * @return Whether it is an enforcer operator + */ + default boolean isEnforcer() { + return false; + } + /** * Returns whether the result of this relational expression is uniquely * identified by this columns with the given ordinals. diff --git a/core/src/main/java/org/apache/calcite/rel/core/Sort.java b/core/src/main/java/org/apache/calcite/rel/core/Sort.java index 86112404bb1f..4bbcdd803b42 100644 --- a/core/src/main/java/org/apache/calcite/rel/core/Sort.java +++ b/core/src/main/java/org/apache/calcite/rel/core/Sort.java @@ -157,6 +157,11 @@ public RelNode accept(RexShuttle shuttle) { return copy(traitSet, getInput(), collation, offset, fetch); } + @Override public boolean isEnforcer() { + return offset == null && fetch == null + && collation.getFieldCollations().size() > 0; + } + /** * Returns the array of {@link RelFieldCollation}s asked for by the sort * specification, from most significant to least significant. diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java index b83df053acc6..5b7b8ba2ec4a 100644 --- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java +++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java @@ -20,6 +20,7 @@ import org.apache.calcite.adapter.enumerable.EnumerableHashJoin; import org.apache.calcite.adapter.enumerable.EnumerableMergeJoin; import org.apache.calcite.adapter.enumerable.EnumerableNestedLoopJoin; +import org.apache.calcite.adapter.jdbc.JdbcToEnumerableConverter; import org.apache.calcite.linq4j.Ord; import org.apache.calcite.plan.RelOptTable; import org.apache.calcite.plan.hep.HepRelVertex; @@ -203,6 +204,11 @@ public ImmutableList collations(Values values, values(mq, values.getRowType(), values.getTuples())); } + public ImmutableList collations(JdbcToEnumerableConverter rel, + RelMetadataQuery mq) { + return mq.collations(rel.getInput()); + } + public ImmutableList collations(HepRelVertex rel, RelMetadataQuery mq) { return mq.collations(rel.getCurrentRel()); diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java index ca923d17674b..c234e9e9f229 100644 --- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java +++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java @@ -1129,11 +1129,13 @@ private void checkResultSetMetaData(Connection connection, String sql) + "and p.\"brand_name\" = 'Washington'") .explainMatches("including all attributes ", CalciteAssert.checkMaskedResultContains("" - + "EnumerableHashJoin(condition=[=($0, $38)], joinType=[inner]): rowcount = 7.050660528307499E8, cumulative cost = {1.0640240216183146E9 rows, 777302.0 cpu, 0.0 io}\n" - + " EnumerableHashJoin(condition=[=($2, $8)], joinType=[inner]): rowcount = 2.0087351932499997E7, cumulative cost = {2.117504719375143E7 rows, 724261.0 cpu, 0.0 io}\n" - + " EnumerableTableScan(table=[[foodmart2, sales_fact_1997]]): rowcount = 86837.0, cumulative cost = {86837.0 rows, 86838.0 cpu, 0.0 io}\n" - + " EnumerableCalc(expr#0..28=[{inputs}], expr#29=['San Francisco':VARCHAR(30)], expr#30=[=($t9, $t29)], proj#0..28=[{exprs}], $condition=[$t30]): rowcount = 1542.1499999999999, cumulative cost = {11823.15 rows, 637423.0 cpu, 0.0 io}\n" - + " EnumerableTableScan(table=[[foodmart2, customer]]): rowcount = 10281.0, cumulative cost = {10281.0 rows, 10282.0 cpu, 0.0 io}\n" + + "EnumerableMergeJoin(condition=[=($0, $38)], joinType=[inner]): rowcount = 7.050660528307499E8, cumulative cost = {7.656040129282498E8 rows, 5.0023949296644424E10 cpu, 0.0 io}\n" + + " EnumerableSort(sort0=[$0], dir0=[ASC]): rowcount = 2.0087351932499997E7, cumulative cost = {4.044858016499999E7 rows, 5.0023896255644424E10 cpu, 0.0 io}\n" + + " EnumerableMergeJoin(condition=[=($2, $8)], joinType=[inner]): rowcount = 2.0087351932499997E7, cumulative cost = {2.0361228232499994E7 rows, 3.232400376004586E7 cpu, 0.0 io}\n" + + " EnumerableSort(sort0=[$2], dir0=[ASC]): rowcount = 86837.0, cumulative cost = {173674.0 rows, 3.168658076004586E7 cpu, 0.0 io}\n" + + " EnumerableTableScan(table=[[foodmart2, sales_fact_1997]]): rowcount = 86837.0, cumulative cost = {86837.0 rows, 86838.0 cpu, 0.0 io}\n" + + " EnumerableCalc(expr#0..28=[{inputs}], expr#29=['San Francisco':VARCHAR(30)], expr#30=[=($t9, $t29)], proj#0..28=[{exprs}], $condition=[$t30]): rowcount = 1542.1499999999999, cumulative cost = {11823.15 rows, 637423.0 cpu, 0.0 io}\n" + + " EnumerableTableScan(table=[[foodmart2, customer]]): rowcount = 10281.0, cumulative cost = {10281.0 rows, 10282.0 cpu, 0.0 io}\n" + " EnumerableCalc(expr#0..14=[{inputs}], expr#15=['Washington':VARCHAR(60)], expr#16=[=($t2, $t15)], proj#0..14=[{exprs}], $condition=[$t16]): rowcount = 234.0, cumulative cost = {1794.0 rows, 53041.0 cpu, 0.0 io}\n" + " EnumerableTableScan(table=[[foodmart2, product]]): rowcount = 1560.0, cumulative cost = {1560.0 rows, 1561.0 cpu, 0.0 io}\n")); } @@ -1836,7 +1838,7 @@ private void checkResultSetMetaData(Connection connection, String sql) // 13 116 - OOM did not complete checkJoinNWay(1); checkJoinNWay(3); - checkJoinNWay(6); + checkJoinNWay(13); } private static void checkJoinNWay(int n) { @@ -2737,11 +2739,13 @@ private void checkNullableTimestamp(CalciteAssert.Config config) { + " join \"hr\".\"depts\" using (\"deptno\")") .explainContains("" + "EnumerableCalc(expr#0..3=[{inputs}], empid=[$t0], deptno=[$t2], name=[$t3])\n" - + " EnumerableHashJoin(condition=[=($1, $2)], joinType=[inner])\n" - + " EnumerableCalc(expr#0..4=[{inputs}], proj#0..1=[{exprs}])\n" - + " EnumerableTableScan(table=[[hr, emps]])\n" - + " EnumerableCalc(expr#0..3=[{inputs}], proj#0..1=[{exprs}])\n" - + " EnumerableTableScan(table=[[hr, depts]])") + + " EnumerableMergeJoin(condition=[=($1, $2)], joinType=[inner])\n" + + " EnumerableSort(sort0=[$1], dir0=[ASC])\n" + + " EnumerableCalc(expr#0..4=[{inputs}], proj#0..1=[{exprs}])\n" + + " EnumerableTableScan(table=[[hr, emps]])\n" + + " EnumerableSort(sort0=[$0], dir0=[ASC])\n" + + " EnumerableCalc(expr#0..3=[{inputs}], proj#0..1=[{exprs}])\n" + + " EnumerableTableScan(table=[[hr, depts]])") .returns("empid=100; deptno=10; name=Sales\n" + "empid=150; deptno=10; name=Sales\n" + "empid=110; deptno=10; name=Sales\n"); diff --git a/core/src/test/java/org/apache/calcite/test/StreamTest.java b/core/src/test/java/org/apache/calcite/test/StreamTest.java index 076c218c83c6..11271d72fde2 100644 --- a/core/src/test/java/org/apache/calcite/test/StreamTest.java +++ b/core/src/test/java/org/apache/calcite/test/StreamTest.java @@ -286,15 +286,17 @@ private static String schemaFor(String name, Class clazz + " LogicalTableScan(table=[[STREAM_JOINS, PRODUCTS]])\n") .explainContains("" + "EnumerableCalc(expr#0..6=[{inputs}], proj#0..1=[{exprs}], SUPPLIERID=[$t6])\n" - + " EnumerableHashJoin(condition=[=($4, $5)], joinType=[inner])\n" - + " EnumerableCalc(expr#0..3=[{inputs}], expr#4=[CAST($t2):VARCHAR(32) NOT NULL], proj#0..4=[{exprs}])\n" - + " EnumerableInterpreter\n" - + " BindableTableScan(table=[[STREAM_JOINS, ORDERS, (STREAM)]])\n" - + " EnumerableTableScan(table=[[STREAM_JOINS, PRODUCTS]])") + + " EnumerableMergeJoin(condition=[=($4, $5)], joinType=[inner])\n" + + " EnumerableSort(sort0=[$4], dir0=[ASC])\n" + + " EnumerableCalc(expr#0..3=[{inputs}], expr#4=[CAST($t2):VARCHAR(32) NOT NULL], proj#0..4=[{exprs}])\n" + + " EnumerableInterpreter\n" + + " BindableTableScan(table=[[STREAM_JOINS, ORDERS, (STREAM)]])\n" + + " EnumerableSort(sort0=[$0], dir0=[ASC])\n" + + " EnumerableTableScan(table=[[STREAM_JOINS, PRODUCTS]])\n") .returns( - startsWith("ROWTIME=2015-02-15 10:15:00; ORDERID=1; SUPPLIERID=1", - "ROWTIME=2015-02-15 10:24:15; ORDERID=2; SUPPLIERID=0", - "ROWTIME=2015-02-15 10:24:45; ORDERID=3; SUPPLIERID=1")); + startsWith("ROWTIME=2015-02-15 10:24:45; ORDERID=3; SUPPLIERID=1", + "ROWTIME=2015-02-15 10:15:00; ORDERID=1; SUPPLIERID=1", + "ROWTIME=2015-02-15 10:58:00; ORDERID=4; SUPPLIERID=1")); } @Disabled diff --git a/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableCorrelateTest.java b/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableCorrelateTest.java index 4d80e098a81a..11aa4aac4717 100644 --- a/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableCorrelateTest.java +++ b/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableCorrelateTest.java @@ -93,6 +93,7 @@ class EnumerableCorrelateTest { // instead of EnumerableHashJoin(SEMI) planner.addRule(JoinToCorrelateRule.INSTANCE); planner.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE); + planner.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE); }) .explainContains("" + "EnumerableCalc(expr#0..3=[{inputs}], empid=[$t1], name=[$t3])\n" @@ -122,6 +123,7 @@ class EnumerableCorrelateTest { planner.addRule(JoinToCorrelateRule.INSTANCE); planner.addRule(FilterCorrelateRule.INSTANCE); planner.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE); + planner.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE); }) .explainContains("" + "EnumerableCalc(expr#0..3=[{inputs}], empid=[$t1], name=[$t3])\n" diff --git a/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableHashJoinTest.java b/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableHashJoinTest.java index afc86d9d6091..8870f6588321 100644 --- a/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableHashJoinTest.java +++ b/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableHashJoinTest.java @@ -16,15 +16,20 @@ */ package org.apache.calcite.test.enumerable; +import org.apache.calcite.adapter.enumerable.EnumerableRules; import org.apache.calcite.adapter.java.ReflectiveSchema; import org.apache.calcite.config.CalciteConnectionProperty; import org.apache.calcite.config.Lex; +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.runtime.Hook; import org.apache.calcite.sql.fun.SqlStdOperatorTable; import org.apache.calcite.test.CalciteAssert; import org.apache.calcite.test.JdbcTest; import org.junit.jupiter.api.Test; +import java.util.function.Consumer; + /** * Unit test for * {@link org.apache.calcite.adapter.enumerable.EnumerableHashJoin}. @@ -36,6 +41,8 @@ class EnumerableHashJoinTest { .query( "select e.empid, e.name, d.name as dept from emps e join depts " + "d on e.deptno=d.deptno") + .withHook(Hook.PLANNER, (Consumer) planner -> + planner.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)) .explainContains("EnumerableCalc(expr#0..4=[{inputs}], empid=[$t0], " + "name=[$t2], dept=[$t4])\n" + " EnumerableHashJoin(condition=[=($1, $3)], joinType=[inner])\n" diff --git a/core/src/test/resources/sql/misc.iq b/core/src/test/resources/sql/misc.iq index a142fbf51be6..dfd07e4b3f83 100644 --- a/core/src/test/resources/sql/misc.iq +++ b/core/src/test/resources/sql/misc.iq @@ -291,11 +291,13 @@ and e."name" <> d."name"; !ok EnumerableCalc(expr#0..4=[{inputs}], empid=[$t0], name=[$t4], name0=[$t2]) - EnumerableHashJoin(condition=[AND(=($1, $3), <>(CAST($2):VARCHAR, CAST($4):VARCHAR))], joinType=[inner]) - EnumerableCalc(expr#0..4=[{inputs}], proj#0..2=[{exprs}]) - EnumerableTableScan(table=[[hr, emps]]) - EnumerableCalc(expr#0..3=[{inputs}], proj#0..1=[{exprs}]) - EnumerableTableScan(table=[[hr, depts]]) + EnumerableMergeJoin(condition=[AND(=($1, $3), <>(CAST($2):VARCHAR, CAST($4):VARCHAR))], joinType=[inner]) + EnumerableSort(sort0=[$1], dir0=[ASC]) + EnumerableCalc(expr#0..4=[{inputs}], proj#0..2=[{exprs}]) + EnumerableTableScan(table=[[hr, emps]]) + EnumerableSort(sort0=[$0], dir0=[ASC]) + EnumerableCalc(expr#0..3=[{inputs}], proj#0..1=[{exprs}]) + EnumerableTableScan(table=[[hr, depts]]) !plan # Same query, expressed using WHERE. @@ -315,11 +317,13 @@ and e."name" <> d."name"; !ok EnumerableCalc(expr#0..4=[{inputs}], empid=[$t0], name=[$t4], name0=[$t2]) - EnumerableHashJoin(condition=[AND(=($1, $3), <>(CAST($2):VARCHAR, CAST($4):VARCHAR))], joinType=[inner]) - EnumerableCalc(expr#0..4=[{inputs}], proj#0..2=[{exprs}]) - EnumerableTableScan(table=[[hr, emps]]) - EnumerableCalc(expr#0..3=[{inputs}], proj#0..1=[{exprs}]) - EnumerableTableScan(table=[[hr, depts]]) + EnumerableMergeJoin(condition=[AND(=($1, $3), <>(CAST($2):VARCHAR, CAST($4):VARCHAR))], joinType=[inner]) + EnumerableSort(sort0=[$1], dir0=[ASC]) + EnumerableCalc(expr#0..4=[{inputs}], proj#0..2=[{exprs}]) + EnumerableTableScan(table=[[hr, emps]]) + EnumerableSort(sort0=[$0], dir0=[ASC]) + EnumerableCalc(expr#0..3=[{inputs}], proj#0..1=[{exprs}]) + EnumerableTableScan(table=[[hr, depts]]) !plan # Un-correlated EXISTS @@ -660,11 +664,13 @@ from "sales_fact_1997" as s join "customer" as c on s."customer_id" = c."customer_id" join "product" as p on s."product_id" = p."product_id" where c."city" = 'San Francisco'; -EnumerableHashJoin(condition=[=($0, $38)], joinType=[inner]) - EnumerableHashJoin(condition=[=($2, $8)], joinType=[inner]) - EnumerableTableScan(table=[[foodmart2, sales_fact_1997]]) - EnumerableCalc(expr#0..28=[{inputs}], expr#29=['San Francisco':VARCHAR(30)], expr#30=[=($t9, $t29)], proj#0..28=[{exprs}], $condition=[$t30]) - EnumerableTableScan(table=[[foodmart2, customer]]) +EnumerableMergeJoin(condition=[=($0, $38)], joinType=[inner]) + EnumerableSort(sort0=[$0], dir0=[ASC]) + EnumerableMergeJoin(condition=[=($2, $8)], joinType=[inner]) + EnumerableSort(sort0=[$2], dir0=[ASC]) + EnumerableTableScan(table=[[foodmart2, sales_fact_1997]]) + EnumerableCalc(expr#0..28=[{inputs}], expr#29=['San Francisco':VARCHAR(30)], expr#30=[=($t9, $t29)], proj#0..28=[{exprs}], $condition=[$t30]) + EnumerableTableScan(table=[[foodmart2, customer]]) EnumerableTableScan(table=[[foodmart2, product]]) !plan @@ -689,8 +695,9 @@ EnumerableCalc(expr#0..56=[{inputs}], product_id0=[$t20], time_id=[$t21], custom EnumerableCalc(expr#0..4=[{inputs}], expr#5=['Snacks':VARCHAR(30)], expr#6=[=($t3, $t5)], proj#0..4=[{exprs}], $condition=[$t6]) EnumerableTableScan(table=[[foodmart2, product_class]]) EnumerableTableScan(table=[[foodmart2, product]]) - EnumerableHashJoin(condition=[=($2, $8)], joinType=[inner]) - EnumerableTableScan(table=[[foodmart2, sales_fact_1997]]) + EnumerableMergeJoin(condition=[=($2, $8)], joinType=[inner]) + EnumerableSort(sort0=[$2], dir0=[ASC]) + EnumerableTableScan(table=[[foodmart2, sales_fact_1997]]) EnumerableCalc(expr#0..28=[{inputs}], expr#29=['San Francisco':VARCHAR(30)], expr#30=[=($t9, $t29)], proj#0..28=[{exprs}], $condition=[$t30]) EnumerableTableScan(table=[[foodmart2, customer]]) !plan diff --git a/file/src/test/java/org/apache/calcite/adapter/file/SqlTest.java b/file/src/test/java/org/apache/calcite/adapter/file/SqlTest.java index 971878c621ea..444f225e81a2 100644 --- a/file/src/test/java/org/apache/calcite/adapter/file/SqlTest.java +++ b/file/src/test/java/org/apache/calcite/adapter/file/SqlTest.java @@ -405,7 +405,8 @@ Fluent returnsUnordered(String... expectedLines) { + " NAME,\n" + " \"DATE\".JOINEDAT\n" + " from \"DATE\"\n" - + "join emps on emps.empno = \"DATE\".EMPNO limit 3"; + + "join emps on emps.empno = \"DATE\".EMPNO\n" + + "order by empno, name, joinedat limit 3"; final String[] lines = { "EMPNO=100; NAME=Fred; JOINEDAT=1996-08-03", "EMPNO=110; NAME=Eric; JOINEDAT=2001-01-01", diff --git a/pig/src/test/java/org/apache/calcite/test/PigAdapterTest.java b/pig/src/test/java/org/apache/calcite/test/PigAdapterTest.java index 88ff742df779..aa8dc9836951 100644 --- a/pig/src/test/java/org/apache/calcite/test/PigAdapterTest.java +++ b/pig/src/test/java/org/apache/calcite/test/PigAdapterTest.java @@ -16,6 +16,9 @@ */ package org.apache.calcite.test; +import org.apache.calcite.adapter.enumerable.EnumerableRules; +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.runtime.Hook; import org.apache.calcite.util.Sources; import com.google.common.collect.ImmutableMap; @@ -145,6 +148,8 @@ class PigAdapterTest extends AbstractPigTest { CalciteAssert.that() .with(MODEL) .query("select * from \"t\" join \"s\" on \"tc1\"=\"sc0\"") + .withHook(Hook.PLANNER, (Consumer) planner -> + planner.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)) .explainContains("PigToEnumerableConverter\n" + " PigJoin(condition=[=($1, $2)], joinType=[inner])\n" + " PigTableScan(table=[[PIG, t]])\n"