blob: 7910c6d6848ad8771a9c0e9008a79c21aff5c8b8 [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.RelOptCluster;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptRuleOperand;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelRule;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelCollationTraitDef;
import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.Sort;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexCallBinding;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.tools.RelBuilderFactory;
import org.apache.calcite.util.mapping.Mappings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.immutables.value.Value;
import java.util.Map;
import java.util.Objects;
/**
* Planner rule that pushes
* a {@link org.apache.calcite.rel.core.Sort}
* past a {@link org.apache.calcite.rel.core.Project}.
*
* @see CoreRules#SORT_PROJECT_TRANSPOSE
*/
@Value.Enclosing
public class SortProjectTransposeRule
extends RelRule<SortProjectTransposeRule.Config>
implements TransformationRule {
/** Creates a SortProjectTransposeRule. */
protected SortProjectTransposeRule(Config config) {
super(config);
}
@Deprecated // to be removed before 2.0
public SortProjectTransposeRule(
Class<? extends Sort> sortClass,
Class<? extends Project> projectClass) {
this(Config.DEFAULT.withOperandFor(sortClass, projectClass));
}
@Deprecated // to be removed before 2.0
public SortProjectTransposeRule(
Class<? extends Sort> sortClass,
Class<? extends Project> projectClass,
String description) {
this(Config.DEFAULT.withDescription(description)
.as(Config.class)
.withOperandFor(sortClass, projectClass));
}
@Deprecated // to be removed before 2.0
public SortProjectTransposeRule(
Class<? extends Sort> sortClass,
Class<? extends Project> projectClass,
RelBuilderFactory relBuilderFactory, String description) {
this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory)
.withDescription(description)
.as(Config.class)
.withOperandFor(sortClass, projectClass));
}
@Deprecated // to be removed before 2.0
protected SortProjectTransposeRule(RelOptRuleOperand operand,
RelBuilderFactory relBuilderFactory, String description) {
this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory)
.withDescription(description)
.withOperandSupplier(b -> b.exactly(operand))
.as(Config.class));
}
@Deprecated // to be removed before 2.0
protected SortProjectTransposeRule(RelOptRuleOperand operand) {
this(Config.DEFAULT
.withOperandSupplier(b -> b.exactly(operand))
.as(Config.class));
}
//~ Methods ----------------------------------------------------------------
@Override public void onMatch(RelOptRuleCall call) {
final Sort sort = call.rel(0);
final Project project = call.rel(1);
final RelOptCluster cluster = project.getCluster();
if (sort.getConvention() != project.getConvention()) {
return;
}
// Determine mapping between project input and output fields. If sort
// relies on non-trivial expressions, we can't push.
final Mappings.TargetMapping map =
RelOptUtil.permutationIgnoreCast(
project.getProjects(), project.getInput().getRowType());
for (RelFieldCollation fc : sort.getCollation().getFieldCollations()) {
if (map.getTargetOpt(fc.getFieldIndex()) < 0) {
return;
}
final RexNode node = project.getProjects().get(fc.getFieldIndex());
if (node.isA(SqlKind.CAST)) {
// Check whether it is a monotonic preserving cast, otherwise we cannot push
final RexCall cast = (RexCall) node;
RelFieldCollation newFc = Objects.requireNonNull(RexUtil.apply(map, fc));
final RexCallBinding binding =
RexCallBinding.create(cluster.getTypeFactory(), cast,
ImmutableList.of(RelCollations.of(newFc)));
if (cast.getOperator().getMonotonicity(binding) == SqlMonotonicity.NOT_MONOTONIC) {
return;
}
}
}
final RelCollation newCollation =
cluster.traitSet().canonize(
RexUtil.apply(map, sort.getCollation()));
final Sort newSort =
sort.copy(
sort.getTraitSet().replace(newCollation),
project.getInput(),
newCollation,
sort.offset,
sort.fetch);
RelNode newProject =
project.copy(
sort.getTraitSet(),
ImmutableList.of(newSort));
// Not only is newProject equivalent to sort;
// newSort is equivalent to project's input
// (but only if the sort is not also applying an offset/limit).
Map<RelNode, RelNode> equiv;
if (sort.offset == null
&& sort.fetch == null
&& cluster.getPlanner().getRelTraitDefs()
.contains(RelCollationTraitDef.INSTANCE)) {
equiv = ImmutableMap.of(newSort, project.getInput());
} else {
equiv = ImmutableMap.of();
}
call.transformTo(newProject, equiv);
}
/** Rule configuration. */
@Value.Immutable
public interface Config extends RelRule.Config {
Config DEFAULT = ImmutableSortProjectTransposeRule.Config.of()
.withOperandFor(Sort.class, LogicalProject.class);
@Override default SortProjectTransposeRule toRule() {
return new SortProjectTransposeRule(this);
}
/** Defines an operand tree for the given classes. */
default Config withOperandFor(Class<? extends Sort> sortClass,
Class<? extends Project> projectClass) {
return withOperandSupplier(b0 ->
b0.operand(sortClass).oneInput(b1 ->
b1.operand(projectClass)
.predicate(p -> !p.containsOver()).anyInputs()))
.as(Config.class);
}
/** Defines an operand tree for the given classes. */
default Config withOperandFor(Class<? extends Sort> sortClass,
Class<? extends Project> projectClass,
Class<? extends RelNode> inputClass) {
return withOperandSupplier(b0 ->
b0.operand(sortClass).oneInput(b1 ->
b1.operand(projectClass)
.predicate(p -> !p.containsOver())
.oneInput(b2 ->
b2.operand(inputClass).anyInputs())))
.as(Config.class);
}
}
}