/* * 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.eigenbase.rel; import java.util.*; import org.eigenbase.rel.metadata.*; import org.eigenbase.relopt.*; import org.eigenbase.reltype.*; import org.eigenbase.resource.Resources; import org.eigenbase.sql.*; import org.eigenbase.sql.parser.*; import org.eigenbase.sql.validate.*; import org.eigenbase.util.*; import net.hydromatic.linq4j.Ord; import net.hydromatic.optiq.util.BitSets; import com.google.common.collect.ImmutableList; /** * <code>AggregateRelBase</code> is an abstract base class for implementations * of {@link AggregateRel}. */ public abstract class AggregateRelBase extends SingleRel { //~ Instance fields -------------------------------------------------------- protected final List<AggregateCall> aggCalls; protected final BitSet groupSet; //~ Constructors ----------------------------------------------------------- /** * Creates an AggregateRelBase. * * @param cluster Cluster * @param traits Traits * @param child Child * @param groupSet Bit set of grouping fields * @param aggCalls Collection of calls to aggregate functions */ protected AggregateRelBase( RelOptCluster cluster, RelTraitSet traits, RelNode child, BitSet groupSet, List<AggregateCall> aggCalls) { super(cluster, traits, child); assert aggCalls != null; this.aggCalls = ImmutableList.copyOf(aggCalls); this.groupSet = groupSet; assert groupSet != null; assert groupSet.isEmpty() == (groupSet.cardinality() == 0) : "See https://bugs.openjdk.java.net/browse/JDK-6222207, " + "BitSet internal invariants may be violated"; assert groupSet.length() <= child.getRowType().getFieldCount(); for (AggregateCall aggCall : aggCalls) { assert typeMatchesInferred(aggCall, true); } } /** * Creates an AggregateRelBase by parsing serialized output. */ protected AggregateRelBase(RelInput input) { this(input.getCluster(), input.getTraitSet(), input.getInput(), input.getBitSet("group"), input.getAggregateCalls("aggs")); } //~ Methods ---------------------------------------------------------------- @Override public final RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { return copy(traitSet, sole(inputs), groupSet, aggCalls); } /** Creates a copy of this aggregate. * * @see #copy(org.eigenbase.relopt.RelTraitSet, java.util.List) */ public abstract AggregateRelBase copy(RelTraitSet traitSet, RelNode input, BitSet groupSet, List<AggregateCall> aggCalls); // implement RelNode public boolean isDistinct() { // we never return duplicate rows return true; } /** * Returns a list of calls to aggregate functions. * * @return list of calls to aggregate functions */ public List<AggregateCall> getAggCallList() { return aggCalls; } /** * Returns the number of grouping fields. * These grouping fields are the leading fields in both the input and output * records. * * <p>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 a bitmap of the grouping fields. * * @return bitset of ordinals of grouping fields */ public BitSet getGroupSet() { return groupSet; } public RelWriter explainTerms(RelWriter pw) { super.explainTerms(pw) .item("group", groupSet) .itemIf("aggs", aggCalls, pw.nest()); if (!pw.nest()) { for (Ord<AggregateCall> ord : Ord.zip(aggCalls)) { pw.item(Util.first(ord.e.name, "agg#" + ord.i), ord.e); } } return pw; } // implement RelNode public double getRows() { // 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.getRows(); rowCount *= 1.0 - Math.pow(.5, groupCount); return rowCount; } } public RelOptCost computeSelfCost(RelOptPlanner planner) { // REVIEW jvs 24-Aug-2008: This is bogus, but no more bogus // than what's currently in JoinRelBase. double rowCount = RelMetadataQuery.getRowCount(this); return planner.getCostFactory().makeCost(rowCount, 0, 0); } protected RelDataType deriveRowType() { return deriveRowType( getCluster().getTypeFactory(), getChild().getRowType(), groupSet, aggCalls); } /** Computes the row type of an {@code AggregateRelBase} before it exists. */ public static RelDataType deriveRowType(RelDataTypeFactory typeFactory, final RelDataType inputRowType, BitSet groupSet, final List<AggregateCall> aggCalls) { final IntList groupList = BitSets.toList(groupSet); assert groupList.size() == groupSet.cardinality(); return typeFactory.createStructType( CompositeList.of( // fields derived from grouping columns new AbstractList<RelDataTypeField>() { public int size() { return groupList.size(); } public RelDataTypeField get(int index) { return inputRowType.getFieldList().get( groupList.get(index)); } }, // fields derived from aggregate calls new AbstractList<RelDataTypeField>() { public int size() { return aggCalls.size(); } public RelDataTypeField get(int index) { final AggregateCall aggCall = aggCalls.get(index); String name; if (aggCall.name != null) { name = aggCall.name; } else { name = "$f" + (groupList.size() + index); } return new RelDataTypeFieldImpl(name, index, aggCall.type); } })); } /** * Returns whether the inferred type of an {@link AggregateCall} matches the * type it was given when it was created. * * @param aggCall Aggregate call * @param fail Whether to fail if the types do not match * @return Whether the inferred and declared types match */ private boolean typeMatchesInferred( final AggregateCall aggCall, final boolean fail) { SqlAggFunction aggFunction = (SqlAggFunction) 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, fail); } /** * Returns whether any of the aggregates are DISTINCT. */ public boolean containsDistinctCall() { for (AggregateCall call : aggCalls) { if (call.isDistinct()) { return true; } } return false; } //~ 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 AggregateRel}. */ public static class AggCallBinding extends SqlOperatorBinding { private final List<RelDataType> operands; private final int groupCount; /** * Creates an AggCallBinding * * @param typeFactory Type factory * @param aggFunction Aggregation function * @param operands Data types of operands * @param groupCount Number of columns in the GROUP BY clause */ public AggCallBinding( RelDataTypeFactory typeFactory, SqlAggFunction aggFunction, List<RelDataType> operands, int groupCount) { super(typeFactory, aggFunction); this.operands = operands; this.groupCount = groupCount; 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; } public int getOperandCount() { return operands.size(); } public RelDataType getOperandType(int ordinal) { return operands.get(ordinal); } public EigenbaseException newError( Resources.ExInst<SqlValidatorException> e) { return SqlUtil.newContextException(SqlParserPos.ZERO, e); } } } // End AggregateRelBase.java