blob: 1656ce9de3c716d26568bc03037ce27d60a99d09 [file] [log] [blame]
/*
* 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.calcite.rel.rules;
import org.apache.calcite.plan.RelOptPredicateList;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptRuleOperand;
import org.apache.calcite.plan.RelRule;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Values;
import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.logical.LogicalValues;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.tools.RelBuilderFactory;
import org.apache.calcite.util.ImmutableBeans;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteTrace;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.immutables.value.Value;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.requireNonNull;
/**
* Planner rule that folds projections and filters into an underlying
* {@link org.apache.calcite.rel.logical.LogicalValues}.
*
* <p>Returns a simplified {@code Values}, perhaps containing zero tuples
* if all rows are filtered away.
*
* <p>For example,</p>
*
* <blockquote><code>select a - b from (values (1, 2), (3, 5), (7, 11)) as t (a,
* b) where a + b &gt; 4</code></blockquote>
*
* <p>becomes</p>
*
* <blockquote><code>select x from (values (-2), (-4))</code></blockquote>
*
* <p>Ignores an empty {@code Values}; this is better dealt with by
* {@link PruneEmptyRules}.
*
* @see CoreRules#FILTER_VALUES_MERGE
* @see CoreRules#PROJECT_VALUES_MERGE
* @see CoreRules#PROJECT_FILTER_VALUES_MERGE
*/
@Value.Enclosing
public class ValuesReduceRule
extends RelRule<ValuesReduceRule.Config>
implements TransformationRule {
private static final Logger LOGGER = CalciteTrace.getPlannerTracer();
/** Creates a ValuesReduceRule. */
protected ValuesReduceRule(Config config) {
super(config);
Util.discard(LOGGER);
}
@Deprecated // to be removed before 2.0
public ValuesReduceRule(RelOptRuleOperand operand,
RelBuilderFactory relBuilderFactory, String desc) {
this(ImmutableValuesReduceRule.Config.builder().withRelBuilderFactory(relBuilderFactory)
.withDescription(desc)
.withOperandSupplier(b -> b.exactly(operand))
.withMatchHandler((u, v) -> {
throw new IllegalArgumentException("Match handler not set.");
})
.build());
throw new IllegalArgumentException("cannot guess matchHandler");
}
private static void matchProjectFilter(ValuesReduceRule rule,
RelOptRuleCall call) {
LogicalProject project = call.rel(0);
LogicalFilter filter = call.rel(1);
LogicalValues values = call.rel(2);
rule.apply(call, project, filter, values);
}
private static void matchProject(ValuesReduceRule rule, RelOptRuleCall call) {
LogicalProject project = call.rel(0);
LogicalValues values = call.rel(1);
rule.apply(call, project, null, values);
}
private static void matchFilter(ValuesReduceRule rule, RelOptRuleCall call) {
LogicalFilter filter = call.rel(0);
LogicalValues values = call.rel(1);
rule.apply(call, null, filter, values);
}
//~ Methods ----------------------------------------------------------------
@Override public void onMatch(RelOptRuleCall call) {
config.matchHandler().accept(this, call);
}
/**
* Does the work.
*
* @param call Rule call
* @param project Project, may be null
* @param filter Filter, may be null
* @param values Values rel to be reduced
*/
protected void apply(RelOptRuleCall call, @Nullable LogicalProject project,
@Nullable LogicalFilter filter, LogicalValues values) {
assert values != null;
assert filter != null || project != null;
final RexNode conditionExpr =
(filter == null) ? null : filter.getCondition();
final List<RexNode> projectExprs =
(project == null) ? null : project.getProjects();
RexBuilder rexBuilder = values.getCluster().getRexBuilder();
// Find reducible expressions.
final List<RexNode> reducibleExps = new ArrayList<>();
final MyRexShuttle shuttle = new MyRexShuttle();
for (final List<RexLiteral> literalList : values.getTuples()) {
shuttle.literalList = literalList;
if (conditionExpr != null) {
RexNode c = conditionExpr.accept(shuttle);
reducibleExps.add(c);
}
if (projectExprs != null) {
requireNonNull(project, "project");
int k = -1;
for (RexNode projectExpr : projectExprs) {
++k;
RexNode e = projectExpr.accept(shuttle);
if (RexLiteral.isNullLiteral(e)) {
e = rexBuilder.makeAbstractCast(
project.getRowType().getFieldList().get(k).getType(),
e);
}
reducibleExps.add(e);
}
}
}
int fieldsPerRow =
((conditionExpr == null) ? 0 : 1)
+ ((projectExprs == null) ? 0 : projectExprs.size());
assert fieldsPerRow > 0;
assert reducibleExps.size() == (values.getTuples().size() * fieldsPerRow);
// Compute the values they reduce to.
final RelOptPredicateList predicates = RelOptPredicateList.EMPTY;
ReduceExpressionsRule.reduceExpressions(values, reducibleExps, predicates,
false, true);
int changeCount = 0;
final ImmutableList.Builder<ImmutableList<RexLiteral>> tuplesBuilder =
ImmutableList.builder();
for (int row = 0; row < values.getTuples().size(); ++row) {
int i = 0;
if (conditionExpr != null) {
final RexNode reducedValue =
reducibleExps.get((row * fieldsPerRow) + i);
++i;
if (!reducedValue.isAlwaysTrue()) {
++changeCount;
continue;
}
}
final ImmutableList<RexLiteral> valuesList;
if (projectExprs != null) {
++changeCount;
final ImmutableList.Builder<RexLiteral> tupleBuilder =
ImmutableList.builder();
for (; i < fieldsPerRow; ++i) {
final RexNode reducedValue =
reducibleExps.get((row * fieldsPerRow) + i);
if (reducedValue instanceof RexLiteral) {
tupleBuilder.add((RexLiteral) reducedValue);
} else if (RexUtil.isNullLiteral(reducedValue, true)) {
tupleBuilder.add(rexBuilder.makeNullLiteral(reducedValue.getType()));
} else {
return;
}
}
valuesList = tupleBuilder.build();
} else {
valuesList = values.getTuples().get(row);
}
tuplesBuilder.add(valuesList);
}
if (changeCount > 0) {
final RelDataType rowType;
if (projectExprs != null) {
rowType = requireNonNull(project, "project").getRowType();
} else {
rowType = values.getRowType();
}
final RelNode newRel =
LogicalValues.create(values.getCluster(), rowType,
tuplesBuilder.build());
call.transformTo(newRel);
} else {
// Filter had no effect, so we can say that Filter(Values) ==
// Values.
call.transformTo(values);
}
// New plan is absolutely better than old plan. (Moreover, if
// changeCount == 0, we've proved that the filter was trivial, and that
// can send the volcano planner into a loop; see dtbug 2070.)
if (filter != null) {
call.getPlanner().prune(filter);
}
}
//~ Inner Classes ----------------------------------------------------------
/** Shuttle that converts inputs to literals. */
private static class MyRexShuttle extends RexShuttle {
private @Nullable List<RexLiteral> literalList;
@Override public RexNode visitInputRef(RexInputRef inputRef) {
requireNonNull(literalList, "literalList");
return literalList.get(inputRef.getIndex());
}
}
/** Rule configuration. */
@Value.Immutable(singleton = false)
public interface Config extends RelRule.Config {
Config FILTER = ImmutableValuesReduceRule.Config.builder()
.withDescription("ValuesReduceRule(Filter)")
.withOperandSupplier(b0 ->
b0.operand(LogicalFilter.class).oneInput(b1 ->
b1.operand(LogicalValues.class)
.predicate(Values::isNotEmpty).noInputs()))
.withMatchHandler(ValuesReduceRule::matchFilter)
.build();
Config PROJECT = ImmutableValuesReduceRule.Config.builder()
.withDescription("ValuesReduceRule(Project)")
.withOperandSupplier(b0 ->
b0.operand(LogicalProject.class).oneInput(b1 ->
b1.operand(LogicalValues.class)
.predicate(Values::isNotEmpty).noInputs()))
.withMatchHandler(ValuesReduceRule::matchProject)
.build();
Config PROJECT_FILTER = ImmutableValuesReduceRule.Config.builder()
.withDescription("ValuesReduceRule(Project-Filter)")
.withOperandSupplier(b0 ->
b0.operand(LogicalProject.class).oneInput(b1 ->
b1.operand(LogicalFilter.class).oneInput(b2 ->
b2.operand(LogicalValues.class)
.predicate(Values::isNotEmpty).noInputs())))
.withMatchHandler(ValuesReduceRule::matchProjectFilter)
.build();
@Override default ValuesReduceRule toRule() {
return new ValuesReduceRule(this);
}
/** Forwards a call to {@link #onMatch(RelOptRuleCall)}. */
@ImmutableBeans.Property
@Value.Parameter
MatchHandler<ValuesReduceRule> matchHandler();
/** Sets {@link #matchHandler()}. */
Config withMatchHandler(MatchHandler<ValuesReduceRule> matchHandler);
/** Defines an operand tree for the given classes. */
default Config withOperandFor(Class<? extends RelNode> relClass) {
return withOperandSupplier(b -> b.operand(relClass).anyInputs())
.as(Config.class);
}
}
}