diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java index b0310c799ce22..a99cb7cfb8daf 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.UUID; import java.util.function.Predicate; @@ -39,10 +40,16 @@ import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rel.type.RelDataTypeSystem; +import org.apache.calcite.rex.RexCall; +import org.apache.calcite.rex.RexLocalRef; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexShuttle; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.sql.SqlExplainLevel; +import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlNode; import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.util.ControlFlowException; import org.apache.calcite.util.ImmutableBitSet; import org.apache.calcite.util.Util; import org.apache.ignite.cluster.ClusterNode; @@ -68,6 +75,7 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan; +import org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilterableTableScan; import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema; import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution; import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory; @@ -619,6 +627,80 @@ protected Predicate input(Predicate predicate) { return input(0, predicate); } + /** + * Change localRef arrangement according lexographical ordering, i.e.
+ * AND(=($t1, 0), =($t0, 0), SEARCH($t2, Sarg[IS NOT NULL]))
+ * will become:
+ * AND(=($t0, 0), =($t1, 0), SEARCH($t2, Sarg[IS NOT NULL])) + */ + protected Predicate satisfyCondition(String condition) { + /** */ + return node -> { + /** Expressions. */ + List exprs = new ArrayList<>(); + + /** */ + RexShuttle shuttle = new RexShuttle() { + /** */ + boolean top = true; + + /** {@inheritDoc} */ + @Override public RexNode visitCall(RexCall call) { + if (call.isA(SqlKind.COMPARISON) || call.isA(SqlKind.SEARCH)) { + if (call.operandCount() == 2 && call.getOperands().get(0) instanceof RexLocalRef) { + exprs.add(call); + } + else + throw new ControlFlowException(); + } + else { + if (!top) + throw new ControlFlowException(); + } + + top = false; + + return super.visitCall(call); + } + }; + + try { + shuttle.apply(node.condition()); + } + catch (ControlFlowException ex) { + lastErrorMsg = "Unapplicable predicate expected: flat operands"; + return false; + } + + exprs.sort(new Comparator<>() { + @Override public int compare(RexCall o1, RexCall o2) { + RexLocalRef local1 = (RexLocalRef)o1.getOperands().get(0); + RexLocalRef local2 = (RexLocalRef)o2.getOperands().get(0); + + return Integer.compare(local1.getIndex(), local2.getIndex()); + } + }); + + try { + RexCall call = (RexCall)node.condition(); + RexNode normCond = node.getCluster().getRexBuilder().makeCall(call.getOperator(), exprs); + + if (!condition.equals(normCond.toString())) { + lastErrorMsg = "Unexpected condition [expected=" + condition + ", actual=" + normCond + ']'; + + return false; + } + } + catch (Throwable th) { + lastErrorMsg = th.getMessage(); + + return false; + } + + return true; + }; + } + /** * Predicate builder for "Operator has column names" condition. */ diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerUtilityTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerUtilityTest.java new file mode 100644 index 0000000000000..41892030a464e --- /dev/null +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerUtilityTest.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.query.calcite.planner; + +import java.math.BigDecimal; +import java.util.function.Predicate; +import com.google.common.collect.ImmutableRangeSet; +import com.google.common.collect.RangeSet; +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.prepare.RelOptTableImpl; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rex.RexBuilder; +import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.rex.RexLocalRef; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexUnknownAs; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.util.Sarg; +import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan; +import org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilterableTableScan; +import org.apache.ignite.internal.processors.query.calcite.util.Commons; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.mockito.Mockito.mock; + +/** {@link AbstractPlannerTest} inner utility methods test. */ +public class AbstractPlannerUtilityTest extends AbstractPlannerTest { + /** Tests correctness of {@link AbstractPlannerTest#satisfyCondition}. */ + @Test + public void testFlatConditionNormalizer() { + RelOptCluster cluster = Commons.emptyCluster(); + RexBuilder builder = cluster.getRexBuilder(); + + RelDataType type = cluster.getTypeFactory().createJavaType(long.class); + RexLiteral l0 = builder.makeExactLiteral(new BigDecimal(0)); + RexLocalRef lr0 = new RexLocalRef(0, type); + + RexLiteral l1 = builder.makeExactLiteral(new BigDecimal(0)); + RexLiteral l2 = builder.makeExactLiteral(new BigDecimal(2)); + RexLocalRef lr1 = new RexLocalRef(1, type); + RexLocalRef lr100 = new RexLocalRef(100, type); + + RexLocalRef lr3 = new RexLocalRef(2, type); + + final RangeSet<@NotNull Integer> setNone = ImmutableRangeSet.of(); + final RangeSet<@NotNull Integer> setAll = setNone.complement(); + final Sarg sarg = + Sarg.of(RexUnknownAs.FALSE, setAll); + RexNode rexNode = + builder.makeCall(SqlStdOperatorTable.SEARCH, lr3, + builder.makeSearchArgumentLiteral(sarg, cluster.getTypeFactory().createSqlType(SqlTypeName.INTEGER))); + + RexNode r0 = builder.makeCall(SqlStdOperatorTable.EQUALS, lr0, l0); + RexNode r1 = builder.makeCall(SqlStdOperatorTable.EQUALS, lr1, l1); + RexNode r2 = builder.makeCall(SqlStdOperatorTable.EQUALS, lr1, l2); + + { + RexNode filter = builder.makeCall(SqlStdOperatorTable.OR, r1, r2, r0); + assertEquals("OR(=($t1, 0), =($t1, 2), =($t0, 0))", filter.toString()); + + ProjectableFilterableTableScan scan = buildNode(cluster, filter); + + Predicate cond = + satisfyCondition("OR(=($t0, 0), =($t1, 0), =($t1, 2))"); + + boolean res = cond.test(scan); + assertTrue(lastErrorMsg, res); + } + + { + RexNode filter = builder.makeCall(SqlStdOperatorTable.AND, r1, r0, rexNode); + assertEquals("AND(=($t1, 0), =($t0, 0), SEARCH($t2, Sarg[IS NOT NULL]))", filter.toString()); + + ProjectableFilterableTableScan scan = buildNode(cluster, filter); + + Predicate cond = + satisfyCondition("AND(=($t0, 0), =($t1, 0), SEARCH($t2, Sarg[IS NOT NULL]))"); + + boolean res = cond.test(scan); + assertTrue(lastErrorMsg, res); + } + + { + RexNode r100 = builder.makeCall(SqlStdOperatorTable.EQUALS, lr100, l1); + RexNode filter = builder.makeCall(SqlStdOperatorTable.AND, r100, r0, rexNode); + + assertEquals("AND(=($t100, 0), =($t0, 0), SEARCH($t2, Sarg[IS NOT NULL]))", filter.toString()); + + ProjectableFilterableTableScan scan = buildNode(cluster, filter); + + Predicate cond = + satisfyCondition("AND(=($t0, 0), SEARCH($t2, Sarg[IS NOT NULL]), =($t100, 0))"); + + boolean res = cond.test(scan); + assertTrue(lastErrorMsg, res); + } + + { + RexNode orOp = builder.makeCall(SqlStdOperatorTable.OR, r0, r1); + RexNode filter = builder.makeCall(SqlStdOperatorTable.AND, orOp, rexNode); + + assertEquals("AND(OR(=($t0, 0), =($t1, 0)), SEARCH($t2, Sarg[IS NOT NULL]))", filter.toString()); + + ProjectableFilterableTableScan scan = buildNode(cluster, filter); + + Predicate cond = satisfyCondition("AND(=($t0, 0), =($t100, 0), SEARCH($t2, Sarg[0, 1, 2]))"); + boolean res = cond.test(scan); + + assertFalse(res); + assertEquals("Unapplicable predicate expected: flat operands", lastErrorMsg); + } + } + + /** */ + private ProjectableFilterableTableScan buildNode(RelOptCluster cluster, RexNode cond) { + RelDataType type = Commons.typeFactory().createType(int.class); + + return new IgniteIndexScan( + cluster, + RelTraitSet.createEmpty(), + mock(RelOptTableImpl.class), + null, + type, + null, + cond, + null, + null, + null + ); + } +} diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/RexSimplificationPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/RexSimplificationPlannerTest.java index 6e242bf8d1e9b..b85d969f1754b 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/RexSimplificationPlannerTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/RexSimplificationPlannerTest.java @@ -95,7 +95,7 @@ public void testExtractCommonDisjunctionPart() throws Exception { "(c1 = 0 and c2 = 0 and c3 = 1) or " + "(c1 = 0 and c2 = 0 and c3 = 2)", schema, isIndexScan("T1", "IDX1") - .and(s -> "AND(=($t0, 0), =($t1, 0), SEARCH($t2, Sarg[0, 1, 2]))".equals(s.condition().toString()))); + .and(satisfyCondition("AND(=($t0, 0), =($t1, 0), SEARCH($t2, Sarg[0, 1, 2]))"))); // Disjunction equality first operand removal. assertPlan("SELECT * FROM t1 WHERE " + @@ -119,7 +119,7 @@ public void testExtractCommonDisjunctionPart() throws Exception { "(c1 = 0 and c2 = 0 and c3 = 1) or " + "(c1 = 0 and c2 = 0)", schema, isIndexScan("T1", "IDX1") - .and(s -> "AND(=($t0, 0), =($t1, 0))".equals(s.condition().toString()))); + .and(satisfyCondition("AND(=($t0, 0), =($t1, 0))"))); // Disjunction all operands removal. assertPlan("SELECT * FROM t1 WHERE " + @@ -147,7 +147,7 @@ public void testExtractCommonDisjunctionPart() throws Exception { "(c1 = 0 and c2 = 0) or " + "(c2 = 0 and c1 = 0)", schema, isIndexScan("T1", "IDX1") - .and(s -> "AND(=($t0, 0), =($t1, 0))".equals(s.condition().toString()))); + .and(satisfyCondition("AND(=($t0, 0), =($t1, 0))"))); // Commutes and duplicates. assertPlan("SELECT * FROM t1 WHERE " + @@ -155,7 +155,7 @@ public void testExtractCommonDisjunctionPart() throws Exception { "(0 = c2 and c3 = 1 and c1 = 0 and c2 = 0) or " + "(c2 = 0 and c3 = 2 and c1 = 0 and c2 = 0)", schema, isIndexScan("T1", "IDX1") - .and(s -> "AND(=($t0, 0), =($t1, 0), SEARCH($t2, Sarg[0, 1, 2]))".equals(s.condition().toString()))); + .and(satisfyCondition("AND(=($t0, 0), =($t1, 0), SEARCH($t2, Sarg[0, 1, 2]))"))); // Simple join. assertPlan("SELECT * FROM t1 JOIN t2 ON (" + diff --git a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java index 30415c303720e..c9efde09f373d 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java +++ b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java @@ -17,6 +17,7 @@ package org.apache.ignite.testsuites; +import org.apache.ignite.internal.processors.query.calcite.planner.AbstractPlannerUtilityTest; import org.apache.ignite.internal.processors.query.calcite.planner.AggregateDistinctPlannerTest; import org.apache.ignite.internal.processors.query.calcite.planner.AggregatePlannerTest; import org.apache.ignite.internal.processors.query.calcite.planner.CorrelatedNestedLoopJoinPlannerTest; @@ -90,6 +91,7 @@ RexSimplificationPlannerTest.class, SerializationPlannerTest.class, UncollectPlannerTest.class, + AbstractPlannerUtilityTest.class, HintsTestSuite.class, })