MinusToDistinctRule.java
/*
* 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.RelRule;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Minus;
import org.apache.calcite.rel.logical.LogicalMinus;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RelBuilderFactory;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
import org.immutables.value.Value;
import java.math.BigDecimal;
/**
* Planner rule that translates a distinct
* {@link org.apache.calcite.rel.core.Minus}
* (<code>all</code> = <code>false</code>)
* into a group of operators composed of
* {@link org.apache.calcite.rel.core.Union},
* {@link org.apache.calcite.rel.core.Aggregate},
* {@link org.apache.calcite.rel.core.Filter},etc.
*
* <p>For example, the query plan
* <blockquote><pre>{@code
* LogicalMinus(all=[false])
* LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2])
* LogicalFilter(condition=[=($7, 10)])
* LogicalTableScan(table=[[CATALOG, SALES, EMP]])
* LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2])
* LogicalFilter(condition=[=($7, 20)])
* LogicalTableScan(table=[[CATALOG, SALES, EMP]])
* }</pre></blockquote>
*
* <p> will convert to
*
* <blockquote><pre>{@code
* LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2])
* LogicalFilter(condition=[AND(>($3, 0), =($4, 0))])
* LogicalAggregate(group=[{0, 1, 2}], agg#0=[COUNT() FILTER $3], agg#1=[COUNT() FILTER $4])
* LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], $f3=[=($3, 0)], $f4=[=($3, 1)])
* LogicalUnion(all=[true])
* LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], $f3=[0])
* LogicalFilter(condition=[=($7, 10)])
* LogicalTableScan(table=[[CATALOG, SALES, EMP]])
* LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], $f3=[1])
* LogicalFilter(condition=[=($7, 20)])
* LogicalTableScan(table=[[CATALOG, SALES, EMP]])
* }</pre></blockquote>
*
* @see CoreRules#MINUS_TO_DISTINCT
*/
@Value.Enclosing
public class MinusToDistinctRule
extends RelRule<MinusToDistinctRule.Config>
implements TransformationRule {
protected MinusToDistinctRule(Config config) {
super(config);
}
@Deprecated // to be removed before 2.0
public MinusToDistinctRule(Class<? extends Minus> minusClass,
RelBuilderFactory relBuilderFactory) {
this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory)
.as(MinusToDistinctRule.Config.class)
.withOperandFor(minusClass));
}
@Override public void onMatch(RelOptRuleCall call) {
final Minus minus = call.rel(0);
if (minus.all) {
// Nothing we can do
return;
}
final RelOptCluster cluster = minus.getCluster();
final RelBuilder relBuilder = call.builder();
final RexBuilder rexBuilder = cluster.getRexBuilder();
final int branchCount = minus.getInputs().size();
// For each child branch in minus, add a column which indicates the branch index
//
// e.g., select EMPNO from emp -> select EMPNO, 0 from emp
// 0 indicates that it comes from the first child branch (operand) of the minus operator
for (int i = 0; i < branchCount; i++) {
relBuilder.push(minus.getInput(i));
relBuilder.projectPlus(relBuilder.literal(new BigDecimal(i)));
}
// create a union above all the branches
relBuilder.union(true, branchCount);
final RelNode union = relBuilder.peek();
final int originalFieldCnt = union.getRowType().getFieldCount() - 1;
ImmutableList.Builder<RexNode> projects = ImmutableList.builder();
// skip the branch index column
projects.addAll(Util.first(relBuilder.fields(), originalFieldCnt));
// On top of the Union, add a Project and add one boolean column per branch counter,
// where the i-th boolean column is true iff the tuple comes from the i-th branch
//
// e.g., LogicalProject(EMPNO=[$0], $f1=[=($1, 0)], $f2=[=($1, 1)], $f3=[=($1, 2)])
// $f1,$f2,$f3 are the boolean indicate whether it comes from the corresponding branch
for (int i = 0; i < branchCount; i++) {
projects.add(
relBuilder.equals(relBuilder.field(originalFieldCnt),
relBuilder.literal(new BigDecimal(i))));
}
relBuilder.project(projects.build());
// Add the count(*) filter $f1(..) for each branch
ImmutableList.Builder<RelBuilder.AggCall> aggCalls = ImmutableList.builder();
for (int i = 0; i < branchCount; i++) {
aggCalls.add(relBuilder.countStar(null).filter(relBuilder.field(originalFieldCnt + i)));
}
final ImmutableBitSet groupSet = ImmutableBitSet.range(originalFieldCnt);
relBuilder.aggregate(relBuilder.groupKey(groupSet), aggCalls.build());
ImmutableList.Builder<RexNode> filters = ImmutableList.builder();
for (int i = 0; i < branchCount; i++) {
SqlOperator operator =
i == 0 ? SqlStdOperatorTable.GREATER_THAN
: SqlStdOperatorTable.EQUALS;
filters.add(
rexBuilder.makeCall(operator, relBuilder.field(originalFieldCnt + i),
relBuilder.literal(new BigDecimal(0))));
}
relBuilder.filter(filters.build());
relBuilder.project(Util.first(relBuilder.fields(), originalFieldCnt));
call.transformTo(relBuilder.build());
}
/**
* Rule configuration.
*/
@Value.Immutable
public interface Config extends RelRule.Config {
Config DEFAULT = ImmutableMinusToDistinctRule.Config.of()
.withOperandFor(LogicalMinus.class);
@Override default MinusToDistinctRule toRule() {
return new MinusToDistinctRule(this);
}
default MinusToDistinctRule.Config withOperandFor(Class<? extends Minus> minusClass) {
return withOperandSupplier(b -> b.operand(minusClass).anyInputs())
.as(MinusToDistinctRule.Config.class);
}
}
}