blob: d172f37f35cc23baf30ff5134e988a8235b9ca37 [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.metadata;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.volcano.RelSubset;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.Exchange;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.Sort;
import org.apache.calcite.rel.core.TableModify;
import org.apache.calcite.rel.core.Union;
import org.apache.calcite.rel.core.Values;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.util.Bug;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.NumberUtil;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* RelMdDistinctRowCount supplies a default implementation of
* {@link RelMetadataQuery#getDistinctRowCount} for the standard logical
* algebra.
*/
public class RelMdDistinctRowCount
implements MetadataHandler<BuiltInMetadata.DistinctRowCount> {
public static final RelMetadataProvider SOURCE =
ReflectiveRelMetadataProvider.reflectiveSource(
new RelMdDistinctRowCount(), BuiltInMetadata.DistinctRowCount.Handler.class);
//~ Constructors -----------------------------------------------------------
protected RelMdDistinctRowCount() {}
//~ Methods ----------------------------------------------------------------
@Override public MetadataDef<BuiltInMetadata.DistinctRowCount> getDef() {
return BuiltInMetadata.DistinctRowCount.DEF;
}
/** Catch-all implementation for
* {@link BuiltInMetadata.DistinctRowCount#getDistinctRowCount(ImmutableBitSet, RexNode)},
* invoked using reflection.
*
* @see org.apache.calcite.rel.metadata.RelMetadataQuery#getDistinctRowCount(RelNode, ImmutableBitSet, RexNode)
*/
public @Nullable Double getDistinctRowCount(RelNode rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
// REVIEW zfong 4/19/06 - Broadbase code does not take into
// consideration selectivity of predicates passed in. Also, they
// assume the rows are unique even if the table is not
boolean uniq = RelMdUtil.areColumnsDefinitelyUnique(mq, rel, groupKey);
if (uniq) {
return NumberUtil.multiply(mq.getRowCount(rel),
mq.getSelectivity(rel, predicate));
}
return null;
}
public @Nullable Double getDistinctRowCount(Union rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
double rowCount = 0.0;
int[] adjustments = new int[rel.getRowType().getFieldCount()];
RexBuilder rexBuilder = rel.getCluster().getRexBuilder();
for (RelNode input : rel.getInputs()) {
// convert the predicate to reference the types of the union child
RexNode modifiedPred;
if (predicate == null) {
modifiedPred = null;
} else {
modifiedPred =
predicate.accept(
new RelOptUtil.RexInputConverter(
rexBuilder,
null,
input.getRowType().getFieldList(),
adjustments));
}
Double partialRowCount =
mq.getDistinctRowCount(input, groupKey, modifiedPred);
if (partialRowCount == null) {
return null;
}
rowCount += partialRowCount;
}
return rowCount;
}
public @Nullable Double getDistinctRowCount(Sort rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
return mq.getDistinctRowCount(rel.getInput(), groupKey, predicate);
}
public @Nullable Double getDistinctRowCount(TableModify rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
return mq.getDistinctRowCount(rel.getInput(), groupKey, predicate);
}
public @Nullable Double getDistinctRowCount(Exchange rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
return mq.getDistinctRowCount(rel.getInput(), groupKey, predicate);
}
public @Nullable Double getDistinctRowCount(Filter rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
if (predicate == null || predicate.isAlwaysTrue()) {
if (groupKey.isEmpty()) {
return 1D;
}
}
// REVIEW zfong 4/18/06 - In the Broadbase code, duplicates are not
// removed from the two filter lists. However, the code below is
// doing so.
RexNode unionPreds =
RelMdUtil.unionPreds(
rel.getCluster().getRexBuilder(),
predicate,
rel.getCondition());
return mq.getDistinctRowCount(rel.getInput(), groupKey, unionPreds);
}
public @Nullable Double getDistinctRowCount(Join rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
return RelMdUtil.getJoinDistinctRowCount(mq, rel, rel.getJoinType(),
groupKey, predicate, false);
}
public @Nullable Double getDistinctRowCount(Aggregate rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
if (predicate == null || predicate.isAlwaysTrue()) {
if (groupKey.isEmpty()) {
return 1D;
}
}
// determine which predicates can be applied on the child of the
// aggregate
final List<RexNode> notPushable = new ArrayList<>();
final List<RexNode> pushable = new ArrayList<>();
RelOptUtil.splitFilters(
rel.getGroupSet(),
predicate,
pushable,
notPushable);
final RexBuilder rexBuilder = rel.getCluster().getRexBuilder();
RexNode childPreds =
RexUtil.composeConjunction(rexBuilder, pushable, true);
// set the bits as they correspond to the child input
ImmutableBitSet.Builder childKey = ImmutableBitSet.builder();
RelMdUtil.setAggChildKeys(groupKey, rel, childKey);
Double distinctRowCount =
mq.getDistinctRowCount(rel.getInput(), childKey.build(), childPreds);
if (distinctRowCount == null) {
return null;
} else if (notPushable.isEmpty()) {
return distinctRowCount;
} else {
RexNode preds =
RexUtil.composeConjunction(rexBuilder, notPushable, true);
return distinctRowCount * RelMdUtil.guessSelectivity(preds);
}
}
public Double getDistinctRowCount(Values rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
if (predicate == null || predicate.isAlwaysTrue()) {
if (groupKey.isEmpty()) {
return 1D;
}
}
final Set<List<Comparable>> set = new HashSet<>();
final List<Comparable> values = new ArrayList<>(groupKey.cardinality());
for (ImmutableList<RexLiteral> tuple : rel.tuples) {
for (int column : groupKey) {
final RexLiteral literal = tuple.get(column);
Comparable value = literal.getValueAs(Comparable.class);
values.add(value == null ? NullSentinel.INSTANCE : value);
}
set.add(ImmutableList.copyOf(values));
values.clear();
}
double nRows = set.size();
if ((predicate == null) || predicate.isAlwaysTrue()) {
return nRows;
} else {
double selectivity = RelMdUtil.guessSelectivity(predicate);
return RelMdUtil.numDistinctVals(nRows, nRows * selectivity);
}
}
public @Nullable Double getDistinctRowCount(Project rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
if (predicate == null || predicate.isAlwaysTrue()) {
if (groupKey.isEmpty()) {
return 1D;
}
}
// try to remove const columns from the group keys, as they do not
// affect the distinct row count
ImmutableBitSet nonConstCols = RexUtil.getNonConstColumns(groupKey, rel.getProjects());
if (nonConstCols.cardinality() == 0) {
// all columns are constants, the distinct row count should be 1
return 1D;
}
if (nonConstCols.cardinality() < groupKey.cardinality()) {
// some const columns can be removed, call the method recursively
// with the trimmed columns
return getDistinctRowCount(rel, mq, nonConstCols, predicate);
}
ImmutableBitSet.Builder baseCols = ImmutableBitSet.builder();
ImmutableBitSet.Builder projCols = ImmutableBitSet.builder();
List<RexNode> projExprs = rel.getProjects();
RelMdUtil.splitCols(projExprs, groupKey, baseCols, projCols);
final List<RexNode> notPushable = new ArrayList<>();
final List<RexNode> pushable = new ArrayList<>();
RelOptUtil.splitFilters(
ImmutableBitSet.range(rel.getRowType().getFieldCount()),
predicate,
pushable,
notPushable);
final RexBuilder rexBuilder = rel.getCluster().getRexBuilder();
// get the distinct row count of the child input, passing in the
// columns and filters that only reference the child; convert the
// filter to reference the children projection expressions
RexNode childPred =
RexUtil.composeConjunction(rexBuilder, pushable, true);
RexNode modifiedPred;
if (childPred == null) {
modifiedPred = null;
} else {
modifiedPred = RelOptUtil.pushPastProject(childPred, rel);
}
Double distinctRowCount =
mq.getDistinctRowCount(rel.getInput(), baseCols.build(),
modifiedPred);
if (distinctRowCount == null) {
return null;
} else if (!notPushable.isEmpty()) {
RexNode preds =
RexUtil.composeConjunction(rexBuilder, notPushable, true);
distinctRowCount *= RelMdUtil.guessSelectivity(preds);
}
// No further computation required if the projection expressions
// are all column references
if (projCols.cardinality() == 0) {
return distinctRowCount;
}
// multiply by the cardinality of the non-child projection expressions
for (int bit : projCols.build()) {
Double subRowCount =
RelMdUtil.cardOfProjExpr(mq, rel, projExprs.get(bit));
if (subRowCount == null) {
return null;
}
distinctRowCount *= subRowCount;
}
return RelMdUtil.numDistinctVals(distinctRowCount, mq.getRowCount(rel));
}
public @Nullable Double getDistinctRowCount(RelSubset rel, RelMetadataQuery mq,
ImmutableBitSet groupKey, @Nullable RexNode predicate) {
final RelNode best = rel.getBest();
if (best != null) {
return mq.getDistinctRowCount(best, groupKey, predicate);
}
if (!Bug.CALCITE_1048_FIXED) {
return getDistinctRowCount((RelNode) rel, mq, groupKey, predicate);
}
Double d = null;
for (RelNode r2 : rel.getRels()) {
try {
Double d2 = mq.getDistinctRowCount(r2, groupKey, predicate);
d = NumberUtil.min(d, d2);
} catch (CyclicMetadataException e) {
// Ignore this relational expression; there will be non-cyclic ones
// in this set.
}
}
return d;
}
}