Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ public static IgniteRel optimize(SqlNode sqlNode, IgnitePlanner planner, IgniteL

rel = planner.transform(PlannerPhase.HEP_PROJECT_PUSH_DOWN, rel.getTraitSet(), rel);

rel = planner.transform(PlannerPhase.HEP_EMPTY_NODES_ELIMINATION, rel.getTraitSet(), rel);

rel = optimizeJoinsOrder(planner, rel, topHints);

RelTraitSet desired = rel.getCluster().traitSet()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
import org.apache.ignite.internal.processors.query.calcite.rule.logical.IgniteMultiJoinOptimizeRule;
import org.apache.ignite.internal.processors.query.calcite.rule.logical.LogicalOrToUnionRule;
import org.apache.ignite.internal.processors.query.calcite.rule.logical.ProjectScanMergeRule;
import org.apache.ignite.internal.processors.query.calcite.rule.logical.PruneTableModifyRule;

import static org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePrograms.cbo;
import static org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePrograms.hep;
Expand Down Expand Up @@ -151,11 +152,42 @@ public enum PlannerPhase {
}
},

/** */
HEP_EMPTY_NODES_ELIMINATION("Heuristic phase to eliminate empty nodes") {
/** {@inheritDoc} */
@Override public RuleSet getRules(PlanningContext ctx) {
return ctx.rules(RuleSets.ofList(
PruneEmptyRules.PROJECT_INSTANCE,
PruneEmptyRules.FILTER_INSTANCE,
PruneEmptyRules.SORT_INSTANCE,
PruneEmptyRules.AGGREGATE_INSTANCE,
PruneEmptyRules.JOIN_LEFT_INSTANCE,
PruneEmptyRules.JOIN_RIGHT_INSTANCE
));
}

/** {@inheritDoc} */
@Override public Program getProgram(PlanningContext ctx) {
return hep(getRules(ctx));
}
},

/** */
HEP_OPTIMIZE_JOIN_ORDER("Heuristic phase to optimize joins order") {
/** {@inheritDoc} */
@Override public RuleSet getRules(PlanningContext ctx) {
return ctx.rules(RuleSets.ofList(IgniteMultiJoinOptimizeRule.INSTANCE));
return ctx.rules(RuleSets.ofList(
IgniteMultiJoinOptimizeRule.INSTANCE,

CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES,

PruneEmptyRules.PROJECT_INSTANCE,
PruneEmptyRules.FILTER_INSTANCE,
PruneEmptyRules.SORT_INSTANCE,
PruneEmptyRules.AGGREGATE_INSTANCE,
PruneEmptyRules.JOIN_LEFT_INSTANCE,
PruneEmptyRules.JOIN_RIGHT_INSTANCE
));
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -253,6 +285,7 @@ public enum PlannerPhase {

PruneEmptyRules.CORRELATE_LEFT_INSTANCE,
PruneEmptyRules.CORRELATE_RIGHT_INSTANCE,
PruneTableModifyRule.INSTANCE,

// Useful of this rule is not clear now.
// CoreRules.AGGREGATE_REDUCE_FUNCTIONS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalValues;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
Expand Down Expand Up @@ -121,6 +122,21 @@ private FilterScanMergeRule(Config config) {
// We need to replace RexInputRef with RexLocalRef because TableScan doesn't have inputs.
condition = RexUtils.replaceInputRefs(condition);

// Eliminate scan if always false condition found.
if (condition.isAlwaysFalse()) {
call.transformTo(LogicalValues.createEmpty(cluster, scan.getRowType()));
call.getPlanner().prune(filter);
call.getPlanner().prune(scan);
return;
}

// Eliminate always true condition.
if (condition.isAlwaysTrue()) {
call.transformTo(scan);
call.getPlanner().prune(filter);
return;
}

// Set default traits, real traits will be calculated for physical node.
RelTraitSet trait = cluster.traitSet();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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.rule.logical;

import java.util.Collections;
import java.util.List;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelRule;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.TableModify;
import org.apache.calcite.rel.core.Values;
import org.apache.calcite.rel.rules.SubstitutionRule;
import org.apache.calcite.rex.RexLiteral;
import org.immutables.value.Value;

/**
* Rule that eliminates table modify node if it doesn't have any source rows.
*/
@Value.Enclosing
public class PruneTableModifyRule extends RelRule<PruneTableModifyRule.Config> implements SubstitutionRule {
/** */
public static final RelOptRule INSTANCE = Config.DEFAULT.toRule();

/**
* Constructor.
*
* @param config Rule configuration.
*/
private PruneTableModifyRule(PruneTableModifyRule.Config config) {
super(config);
}

/** {@inheritDoc} */
@Override public void onMatch(RelOptRuleCall call) {
TableModify singleRel = call.rel(0);

// TODO https://issues.apache.org/jira/browse/IGNITE-23512: Default Calcite RexBuilder ignores field type and extract type from

Check warning on line 53 in modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/PruneTableModifyRule.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=apache_ignite&issues=AZ2WKdvpOpW3NkCOyCCm&open=AZ2WKdvpOpW3NkCOyCCm&pullRequest=13039
// the given value. E.g. for zero value RexBuilder creates INT literal. Use simple way create `singleValue` after fixing the issue.
// RelNode singleValue = call.builder().values(singleRel.getRowType(), 0L).build();

Check warning on line 55 in modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/PruneTableModifyRule.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=apache_ignite&issues=AZ2WKdvpOpW3NkCOyCCl&open=AZ2WKdvpOpW3NkCOyCCl&pullRequest=13039
RexLiteral zeroLiteral =
singleRel.getCluster().getRexBuilder().makeLiteral(0L, singleRel.getRowType().getFieldList().get(0).getType());
RelNode singleVal = call.builder().values(List.of(List.of(zeroLiteral)), singleRel.getRowType()).build();

singleVal = singleVal.copy(singleRel.getCluster().traitSet(), Collections.emptyList());
call.transformTo(singleVal);
}

/** Rule configuration. */
@Value.Immutable(singleton = false)
public interface Config extends RuleFactoryConfig<Config> {
/** */
Config DEFAULT = ImmutablePruneTableModifyRule.Config.builder()
.withDescription("PruneTableModify")
.withRuleFactory(PruneTableModifyRule::new)
.withOperandSupplier(b0 ->
b0.operand(TableModify.class).oneInput(b1 ->
b1.operand(Values.class).predicate(Values::isEmpty).noInputs()))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.testframework.GridTestUtils;
import org.hamcrest.CoreMatchers;
import org.junit.Test;

/** */
Expand Down Expand Up @@ -672,6 +673,18 @@ public void testInsertMultiRowValues() {
assertQuery("SELECT * FROM test").resultSize(rowsCnt).check();
}

/** */
@Test
public void insertFromSelectWithAlwaysFalseCondition() {
sql("CREATE TABLE test (id INT PRIMARY KEY, val REAL)");
sql("CREATE TABLE test2 (id INT PRIMARY KEY, val REAL)");

assertQuery("INSERT INTO test2 SELECT id, val FROM test WHERE val > 1 AND val < 0")
.matches(CoreMatchers.not(QueryChecker.containsSubPlan("IgniteTableModify")))
.returns(0L)
.check();
}

/** */
private void checkDefaultValue(String sqlType, String sqlVal, Object expectedVal) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ protected <T extends RelNode> void assertPlan(
) throws Exception {
IgniteRel plan = physicalPlan(plannerCtx(sql, schemas, planLsnr, disabledRules));

System.out.println("plan = " + RelOptUtil.toString(plan, SqlExplainLevel.ALL_ATTRIBUTES));

checkSplitAndSerialization(plan, schemas);

if (!predicate.test((T)plan)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@

import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteValues;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
import org.junit.Before;
Expand Down Expand Up @@ -235,6 +238,80 @@ public void testFilterIdentityFilterMerge() throws Exception {
"ProjectFilterTransposeRule", "FilterProjectTransposeRule");
}

/** */
@Test
public void testAlwaysTrueFilterPruning() throws Exception {
String sql = "SELECT a, c FROM tbl WHERE a > 1 OR a < 3 OR a IS NULL";

assertPlan(sql, publicSchema, isInstanceOf(IgniteTableScan.class)
.and(scan -> scan.projects() == null)
.and(scan -> scan.condition() == null)
.and(scan -> ImmutableBitSet.of(0, 2).equals(scan.requiredColumns())),
"ProjectFilterTransposeRule", "FilterProjectTransposeRule");
}

/** */
@Test
public void testAlwaysFalseFilterPruning() throws Exception {
Predicate<IgniteValues> hasEmptyValuesOnly = hasEmptyValuesOnlyPredicate();

// Table scan elimination.
String sql = "SELECT a, c FROM tbl WHERE a > 1 AND a < 0";
assertPlan(sql, publicSchema, hasEmptyValuesOnly);

sql = "SELECT a, c FROM (SELECT a, c FROM tbl WHERE a > 1) WHERE c = 1 AND c IS NULL";
assertPlan(sql, publicSchema, hasEmptyValuesOnly,
"ProjectFilterTransposeRule", "FilterProjectTransposeRule");

sql = "SELECT a, c FROM (SELECT a, c FROM tbl WHERE a > 1) WHERE a < 0";
assertPlan(sql, publicSchema, hasEmptyValuesOnly,
"ProjectFilterTransposeRule", "FilterProjectTransposeRule");

// JOIN branch elimination.
sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 LEFT JOIN tbl AS t2 ON t1.a = t2.a WHERE t2.a = 1 AND t2.a IS NULL AND t1.c = 1";
assertPlan(sql, publicSchema, hasEmptyValuesOnly);

sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t2.a = 1 AND t2.a IS NULL";
assertPlan(sql, publicSchema, hasEmptyValuesOnly);

sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a WHERE t1.a = 1 AND t2.a = 2";
assertPlan(sql, publicSchema, hasEmptyValuesOnly);
}

/** */
@Test
public void testJoinWithAlwaysFalseConditionPruning() throws Exception {
String sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 LEFT JOIN tbl AS t2 ON (t1.a = t2.a AND t2.a = 1 AND t2.a = 2) WHERE t1.c = 1";
assertPlan(sql, publicSchema, isInstanceOf(IgniteTableScan.class)
.and(scan -> scan.projects() != null)
.and(scan -> scan.condition() != null)
.and(scan -> "=($t1, 1)".equals(scan.condition().toString()))
);

sql = "SELECT t1.a, t2.a, t1.c FROM tbl AS t1 INNER JOIN tbl AS t2 ON t1.a = t2.a AND t2.a = 1 AND t2.a = 2";
assertPlan(sql, publicSchema, hasEmptyValuesOnlyPredicate());
}

/** */
@Test
public void testAlwaysFalseFilterPruningWithDml() throws Exception {
Predicate<IgniteValues> zeroDmlResultPredicate = isInstanceOf(IgniteValues.class)
.and(values -> values.getTuples().size() == 1) // single row
.and(values -> values.getTuples().get(0).size() == 1) // row of single column
.and(values -> RexLiteral.intValue(values.getTuples().get(0).get(0)) == 0L);

String sql = "INSERT INTO tbl (a, c) SELECT a, b FROM tbl WHERE a > 1 AND a < 0";
assertPlan(sql, publicSchema, zeroDmlResultPredicate);

sql = "INSERT INTO tbl (a, c) (SELECT a, c FROM (SELECT a, c FROM tbl WHERE a > 1) WHERE a < 0)";
assertPlan(sql, publicSchema, zeroDmlResultPredicate);
}

/** */
private Predicate<IgniteValues> hasEmptyValuesOnlyPredicate() {
return isInstanceOf(IgniteValues.class).and(values -> values.getTuples().isEmpty());
}

/** */
@Test
public void testFilterFilterMerge() throws Exception {
Expand Down
Loading