com.hazelcast.org.apache.calcite.rel.core.Aggregate Maven / Gradle / Ivy
/*
* 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 com.hazelcast.com.liance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.com.hazelcast.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 com.hazelcast.org.apache.calcite.rel.core;
import com.hazelcast.org.apache.calcite.linq4j.Ord;
import com.hazelcast.org.apache.calcite.linq4j.function.Experimental;
import com.hazelcast.org.apache.calcite.plan.RelOptCluster;
import com.hazelcast.org.apache.calcite.plan.RelOptCost;
import com.hazelcast.org.apache.calcite.plan.RelOptPlanner;
import com.hazelcast.org.apache.calcite.plan.RelOptUtil;
import com.hazelcast.org.apache.calcite.plan.RelTraitSet;
import com.hazelcast.org.apache.calcite.rel.RelInput;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.RelWriter;
import com.hazelcast.org.apache.calcite.rel.SingleRel;
import com.hazelcast.org.apache.calcite.rel.hint.Hintable;
import com.hazelcast.org.apache.calcite.rel.hint.RelHint;
import com.hazelcast.org.apache.calcite.rel.metadata.RelMetadataQuery;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeFactory;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeField;
import com.hazelcast.org.apache.calcite.runtime.CalciteException;
import com.hazelcast.org.apache.calcite.runtime.Resources;
import com.hazelcast.org.apache.calcite.sql.SqlAggFunction;
import com.hazelcast.org.apache.calcite.sql.SqlOperatorBinding;
import com.hazelcast.org.apache.calcite.sql.SqlUtil;
import com.hazelcast.org.apache.calcite.sql.parser.SqlParserPos;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeName;
import com.hazelcast.org.apache.calcite.sql.validate.SqlValidatorException;
import com.hazelcast.org.apache.calcite.util.ImmutableBitSet;
import com.hazelcast.org.apache.calcite.util.Litmus;
import com.hazelcast.org.apache.calcite.util.Pair;
import com.hazelcast.org.apache.calcite.util.Util;
import com.hazelcast.com.google.com.hazelcast.com.on.base.Preconditions;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.ImmutableList;
import com.hazelcast.com.google.com.hazelcast.com.on.math.IntMath;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Relational operator that eliminates
* duplicates and com.hazelcast.com.utes totals.
*
* It corresponds to the {@code GROUP BY} operator in a SQL query
* statement, together with the aggregate functions in the {@code SELECT}
* clause.
*
*
Rules:
*
*
* - {@link com.hazelcast.org.apache.calcite.rel.rules.AggregateProjectPullUpConstantsRule}
*
- {@link com.hazelcast.org.apache.calcite.rel.rules.AggregateExpandDistinctAggregatesRule}
*
- {@link com.hazelcast.org.apache.calcite.rel.rules.AggregateReduceFunctionsRule}.
*
*/
public abstract class Aggregate extends SingleRel implements Hintable {
protected final ImmutableList hints;
public static boolean isSimple(Aggregate aggregate) {
return aggregate.getGroupType() == Group.SIMPLE;
}
@SuppressWarnings("Guava")
@Deprecated // to be converted to Java Predicate before 2.0
public static final com.hazelcast.com.google.com.hazelcast.com.on.base.Predicate IS_SIMPLE =
Aggregate::isSimple;
@SuppressWarnings("Guava")
@Deprecated // to be converted to Java Predicate before 2.0
public static final com.hazelcast.com.google.com.hazelcast.com.on.base.Predicate NO_INDICATOR =
Aggregate::noIndicator;
@SuppressWarnings("Guava")
@Deprecated // to be converted to Java Predicate before 2.0
public static final com.hazelcast.com.google.com.hazelcast.com.on.base.Predicate
IS_NOT_GRAND_TOTAL = Aggregate::isNotGrandTotal;
/** Used internally; will removed when {@link #indicator} is removed,
* before 2.0. */
@Experimental
public static void checkIndicator(boolean indicator) {
Preconditions.checkArgument(!indicator,
"indicator is no longer supported; use GROUPING function instead");
}
//~ Instance fields --------------------------------------------------------
@Deprecated // unused field, to be removed before 2.0
public final boolean indicator = false;
protected final List aggCalls;
protected final ImmutableBitSet groupSet;
public final ImmutableList groupSets;
//~ Constructors -----------------------------------------------------------
/**
* Creates an Aggregate.
*
* All members of {@code groupSets} must be sub-sets of {@code groupSet}.
* For a simple {@code GROUP BY}, {@code groupSets} is a singleton list
* containing {@code groupSet}.
*
*
If {@code GROUP BY} is not specified,
* or equivalently if {@code GROUP BY ()} is specified,
* {@code groupSet} will be the empty set,
* and {@code groupSets} will have one element, that empty set.
*
*
If {@code CUBE}, {@code ROLLUP} or {@code GROUPING SETS} are
* specified, {@code groupSets} will have additional elements,
* but they must each be a subset of {@code groupSet},
* and they must be sorted by inclusion:
* {@code (0, 1, 2), (1), (0, 2), (0), ()}.
*
* @param cluster Cluster
* @param traitSet Trait set
* @param hints Hints of this relational expression
* @param input Input relational expression
* @param groupSet Bit set of grouping fields
* @param groupSets List of all grouping sets; null for just {@code groupSet}
* @param aggCalls Collection of calls to aggregate functions
*/
protected Aggregate(
RelOptCluster cluster,
RelTraitSet traitSet,
List hints,
RelNode input,
ImmutableBitSet groupSet,
List groupSets,
List aggCalls) {
super(cluster, traitSet, input);
this.hints = ImmutableList.copyOf(hints);
this.aggCalls = ImmutableList.copyOf(aggCalls);
this.groupSet = Objects.requireNonNull(groupSet);
if (groupSets == null) {
this.groupSets = ImmutableList.of(groupSet);
} else {
this.groupSets = ImmutableList.copyOf(groupSets);
assert ImmutableBitSet.ORDERING.isStrictlyOrdered(groupSets) : groupSets;
for (ImmutableBitSet set : groupSets) {
assert groupSet.contains(set);
}
}
assert groupSet.length() <= input.getRowType().getFieldCount();
for (AggregateCall aggCall : aggCalls) {
assert typeMatchesInferred(aggCall, Litmus.THROW);
Preconditions.checkArgument(aggCall.filterArg < 0
|| isPredicate(input, aggCall.filterArg),
"filter must be BOOLEAN NOT NULL");
}
}
@Deprecated // to be removed before 2.0
protected Aggregate(
RelOptCluster cluster,
RelTraitSet traitSet,
RelNode input,
ImmutableBitSet groupSet,
List groupSets,
List aggCalls) {
this(cluster, traitSet, new ArrayList<>(), input, groupSet, groupSets, aggCalls);
}
@Deprecated // to be removed before 2.0
protected Aggregate(
RelOptCluster cluster,
RelTraitSet traits,
RelNode child,
boolean indicator,
ImmutableBitSet groupSet,
List groupSets,
List aggCalls) {
this(cluster, traits, ImmutableList.of(), child, groupSet, groupSets, aggCalls);
checkIndicator(indicator);
}
public static boolean isNotGrandTotal(Aggregate aggregate) {
return aggregate.getGroupCount() > 0;
}
@Deprecated // to be removed before 2.0
public static boolean noIndicator(Aggregate aggregate) {
return true;
}
private boolean isPredicate(RelNode input, int index) {
final RelDataType type =
input.getRowType().getFieldList().get(index).getType();
return type.getSqlTypeName() == SqlTypeName.BOOLEAN
&& !type.isNullable();
}
/**
* Creates an Aggregate by parsing serialized output.
*/
protected Aggregate(RelInput input) {
this(input.getCluster(), input.getTraitSet(), new ArrayList<>(),
input.getInput(), input.getBitSet("group"),
input.getBitSetList("groups"), input.getAggregateCalls("aggs"));
}
//~ Methods ----------------------------------------------------------------
@Override public final RelNode copy(RelTraitSet traitSet,
List inputs) {
return copy(traitSet, sole(inputs), groupSet, groupSets, aggCalls);
}
/** Creates a copy of this aggregate.
*
* @param traitSet Traits
* @param input Input
* @param groupSet Bit set of grouping fields
* @param groupSets List of all grouping sets; null for just {@code groupSet}
* @param aggCalls Collection of calls to aggregate functions
* @return New {@code Aggregate} if any parameter differs from the value of
* this {@code Aggregate}, or just {@code this} if all the parameters are
* the same
*
* @see #copy(com.hazelcast.org.apache.calcite.plan.RelTraitSet, java.util.List)
*/
public abstract Aggregate copy(RelTraitSet traitSet, RelNode input,
ImmutableBitSet groupSet,
List groupSets, List aggCalls);
@Deprecated // to be removed before 2.0
public Aggregate copy(RelTraitSet traitSet, RelNode input,
boolean indicator, ImmutableBitSet groupSet,
List groupSets, List aggCalls) {
checkIndicator(indicator);
return copy(traitSet, input, groupSet, groupSets, aggCalls);
}
/**
* Returns a list of calls to aggregate functions.
*
* @return list of calls to aggregate functions
*/
public List getAggCallList() {
return aggCalls;
}
/**
* Returns a list of calls to aggregate functions together with their output
* field names.
*
* @return list of calls to aggregate functions and their output field names
*/
public List> getNamedAggCalls() {
final int offset = getGroupCount();
return Pair.zip(aggCalls, Util.skip(getRowType().getFieldNames(), offset));
}
/**
* Returns the number of grouping fields.
* These grouping fields are the leading fields in both the input and output
* records.
*
* NOTE: The {@link #getGroupSet()} data structure allows for the
* grouping fields to not be on the leading edge. New code should, if
* possible, assume that grouping fields are in arbitrary positions in the
* input relational expression.
*
* @return number of grouping fields
*/
public int getGroupCount() {
return groupSet.cardinality();
}
/**
* Returns the number of indicator fields.
*
*
Always zero.
*
* @return number of indicator fields, always zero
*/
@Deprecated // to be removed before 2.0
public int getIndicatorCount() {
return 0;
}
/**
* Returns a bit set of the grouping fields.
*
* @return bit set of ordinals of grouping fields
*/
public ImmutableBitSet getGroupSet() {
return groupSet;
}
/**
* Returns the list of grouping sets com.hazelcast.com.uted by this Aggregate.
*
* @return List of all grouping sets; null for just {@code groupSet}
*/
public ImmutableList getGroupSets() {
return groupSets;
}
public RelWriter explainTerms(RelWriter pw) {
// We skip the "groups" element if it is a singleton of "group".
super.explainTerms(pw)
.item("group", groupSet)
.itemIf("groups", groupSets, getGroupType() != Group.SIMPLE)
.itemIf("aggs", aggCalls, pw.nest());
if (!pw.nest()) {
for (Ord ord : Ord.zip(aggCalls)) {
pw.item(Util.first(ord.e.name, "agg#" + ord.i), ord.e);
}
}
return pw;
}
@Override public double estimateRowCount(RelMetadataQuery mq) {
// Assume that each sort column has 50% of the value count.
// Therefore one sort column has .5 * rowCount,
// 2 sort columns give .75 * rowCount.
// Zero sort columns yields 1 row (or 0 if the input is empty).
final int groupCount = groupSet.cardinality();
if (groupCount == 0) {
return 1;
} else {
double rowCount = super.estimateRowCount(mq);
rowCount *= 1.0 - Math.pow(.5, groupCount);
return rowCount;
}
}
@Override public RelOptCost com.hazelcast.com.uteSelfCost(RelOptPlanner planner,
RelMetadataQuery mq) {
// REVIEW jvs 24-Aug-2008: This is bogus, but no more bogus
// than what's currently in Join.
double rowCount = mq.getRowCount(this);
// Aggregates with more aggregate functions cost a bit more
float multiplier = 1f + (float) aggCalls.size() * 0.125f;
for (AggregateCall aggCall : aggCalls) {
if (aggCall.getAggregation().getName().equals("SUM")) {
// Pretend that SUM costs a little bit more than $SUM0,
// to make things deterministic.
multiplier += 0.0125f;
}
}
return planner.getCostFactory().makeCost(rowCount * multiplier, 0, 0);
}
protected RelDataType deriveRowType() {
return deriveRowType(getCluster().getTypeFactory(), getInput().getRowType(),
false, groupSet, groupSets, aggCalls);
}
/**
* Computes the row type of an {@code Aggregate} before it exists.
*
* @param typeFactory Type factory
* @param inputRowType Input row type
* @param indicator Deprecated, always false
* @param groupSet Bit set of grouping fields
* @param groupSets List of all grouping sets; null for just {@code groupSet}
* @param aggCalls Collection of calls to aggregate functions
* @return Row type of the aggregate
*/
public static RelDataType deriveRowType(RelDataTypeFactory typeFactory,
final RelDataType inputRowType, boolean indicator,
ImmutableBitSet groupSet, List groupSets,
final List aggCalls) {
final List groupList = groupSet.asList();
assert groupList.size() == groupSet.cardinality();
final RelDataTypeFactory.Builder builder = typeFactory.builder();
final List fieldList = inputRowType.getFieldList();
final Set containedNames = new HashSet<>();
for (int groupKey : groupList) {
final RelDataTypeField field = fieldList.get(groupKey);
containedNames.add(field.getName());
builder.add(field);
if (groupSets != null && !allContain(groupSets, groupKey)) {
builder.nullable(true);
}
}
checkIndicator(indicator);
for (Ord aggCall : Ord.zip(aggCalls)) {
final String base;
if (aggCall.e.name != null) {
base = aggCall.e.name;
} else {
base = "$f" + (groupList.size() + aggCall.i);
}
String name = base;
int i = 0;
while (containedNames.contains(name)) {
name = base + "_" + i++;
}
containedNames.add(name);
builder.add(name, aggCall.e.type);
}
return builder.build();
}
private static boolean allContain(List groupSets,
int groupKey) {
for (ImmutableBitSet groupSet : groupSets) {
if (!groupSet.get(groupKey)) {
return false;
}
}
return true;
}
public boolean isValid(Litmus litmus, Context context) {
return super.isValid(litmus, context)
&& litmus.check(Util.isDistinct(getRowType().getFieldNames()),
"distinct field names: {}", getRowType());
}
/**
* Returns whether the inferred type of an {@link AggregateCall} matches the
* type it was given when it was created.
*
* @param aggCall Aggregate call
* @param litmus What to do if an error is detected (types do not match)
* @return Whether the inferred and declared types match
*/
private boolean typeMatchesInferred(
final AggregateCall aggCall,
final Litmus litmus) {
SqlAggFunction aggFunction = aggCall.getAggregation();
AggCallBinding callBinding = aggCall.createBinding(this);
RelDataType type = aggFunction.inferReturnType(callBinding);
RelDataType expectedType = aggCall.type;
return RelOptUtil.eq("aggCall type",
expectedType,
"inferred type",
type,
litmus);
}
/**
* Returns whether any of the aggregates are DISTINCT.
*
* @return Whether any of the aggregates are DISTINCT
*/
public boolean containsDistinctCall() {
for (AggregateCall call : aggCalls) {
if (call.isDistinct()) {
return true;
}
}
return false;
}
@Override public ImmutableList getHints() {
return hints;
}
/**
* Returns the type of roll-up.
*
* @return Type of roll-up
*/
public Group getGroupType() {
return Group.induce(groupSet, groupSets);
}
/** What kind of roll-up is it? */
public enum Group {
SIMPLE,
ROLLUP,
CUBE,
OTHER;
public static Group induce(ImmutableBitSet groupSet,
List groupSets) {
if (!ImmutableBitSet.ORDERING.isStrictlyOrdered(groupSets)) {
throw new IllegalArgumentException("must be sorted: " + groupSets);
}
if (groupSets.size() == 1 && groupSets.get(0).equals(groupSet)) {
return SIMPLE;
}
if (groupSets.size() == IntMath.pow(2, groupSet.cardinality())) {
return CUBE;
}
if (isRollup(groupSet, groupSets)) {
return ROLLUP;
}
return OTHER;
}
/** Returns whether a list of sets is a rollup.
*
* For example, if {@code groupSet} is {2, 4, 5}
, then
* [{2, 4, 5], {2, 5}, {5}, {}]
is a rollup. The first item is
* equal to {@code groupSet}, and each subsequent item is a subset with one
* fewer bit than the previous.
*
* @see #getRollup(List) */
public static boolean isRollup(ImmutableBitSet groupSet,
List groupSets) {
if (groupSets.size() != groupSet.cardinality() + 1) {
return false;
}
ImmutableBitSet g = null;
for (ImmutableBitSet bitSet : groupSets) {
if (g == null) {
// First item must equal groupSet
if (!bitSet.equals(groupSet)) {
return false;
}
} else {
// Each subsequent items must be a subset with one fewer bit than the
// previous item
if (!g.contains(bitSet)
|| g.except(bitSet).cardinality() != 1) {
return false;
}
}
g = bitSet;
}
assert g.isEmpty();
return true;
}
/** Returns the ordered list of bits in a rollup.
*
* For example, given a {@code groupSets} value
* [{2, 4, 5], {2, 5}, {5}, {}]
, returns the list
* {@code [5, 2, 4]}, which are the succession of bits
* added to each of the sets starting with the empty set.
*
* @see #isRollup(ImmutableBitSet, List) */
public static List getRollup(List groupSets) {
final Set set = new LinkedHashSet<>();
ImmutableBitSet g = null;
for (ImmutableBitSet bitSet : groupSets) {
if (g == null) {
// First item must equal groupSet
} else {
// Each subsequent items must be a subset with one fewer bit than the
// previous item
set.addAll(g.except(bitSet).toList());
}
g = bitSet;
}
return ImmutableList.copyOf(set).reverse();
}
}
//~ Inner Classes ----------------------------------------------------------
/**
* Implementation of the {@link SqlOperatorBinding} interface for an
* {@link AggregateCall aggregate call} applied to a set of operands in the
* context of a {@link com.hazelcast.org.apache.calcite.rel.logical.LogicalAggregate}.
*/
public static class AggCallBinding extends SqlOperatorBinding {
private final List operands;
private final int groupCount;
private final boolean filter;
/**
* Creates an AggCallBinding
*
* @param typeFactory Type factory
* @param aggFunction Aggregate function
* @param operands Data types of operands
* @param groupCount Number of columns in the GROUP BY clause
* @param filter Whether the aggregate function has a FILTER clause
*/
public AggCallBinding(RelDataTypeFactory typeFactory,
SqlAggFunction aggFunction, List operands, int groupCount,
boolean filter) {
super(typeFactory, aggFunction);
this.operands = operands;
this.groupCount = groupCount;
this.filter = filter;
assert operands != null
: "operands of aggregate call should not be null";
assert groupCount >= 0
: "number of group by columns should be greater than zero in "
+ "aggregate call. Got " + groupCount;
}
@Override public int getGroupCount() {
return groupCount;
}
@Override public boolean hasFilter() {
return filter;
}
public int getOperandCount() {
return operands.size();
}
public RelDataType getOperandType(int ordinal) {
return operands.get(ordinal);
}
public CalciteException newError(
Resources.ExInst e) {
return SqlUtil.newContextException(SqlParserPos.ZERO, e);
}
}
}