/* * 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.reltype.*; import org.eigenbase.sql.*; import org.eigenbase.sql.type.SqlTypeUtil; import org.eigenbase.util.mapping.Mappings; import com.google.common.collect.ImmutableList; /** * Call to an aggregation function within an {@link AggregateRel}. */ public class AggregateCall { //~ Instance fields -------------------------------------------------------- private final Aggregation aggregation; private final boolean distinct; public final RelDataType type; public final String name; // We considered using ImmutableIntList but we would not save much memory: // since all values are small, ImmutableList uses cached Integer values. private final ImmutableList<Integer> argList; //~ Constructors ----------------------------------------------------------- /** * Creates an AggregateCall. * * @param aggregation Aggregation * @param distinct Whether distinct * @param argList List of ordinals of arguments * @param type Result type * @param name Name (may be null) */ public AggregateCall( Aggregation aggregation, boolean distinct, List<Integer> argList, RelDataType type, String name) { this.type = type; this.name = name; assert aggregation != null; assert argList != null; assert type != null; this.aggregation = aggregation; this.argList = ImmutableList.copyOf(argList); this.distinct = distinct; } //~ Methods ---------------------------------------------------------------- /** * Returns whether this AggregateCall is distinct, as in <code> * COUNT(DISTINCT empno)</code>. * * @return whether distinct */ public final boolean isDistinct() { return distinct; } /** * Returns the Aggregation. * * @return aggregation */ public final Aggregation getAggregation() { return aggregation; } /** * Returns the ordinals of the arguments to this call. * * <p>The list is immutable. * * @return list of argument ordinals */ public final List<Integer> getArgList() { return argList; } /** * Returns the result type. * * @return result type */ public final RelDataType getType() { return type; } /** * Returns the name. * * @return name */ public String getName() { return name; } /** * Creates an equivalent AggregateCall that has a new name. * * @param name New name (may be null) */ public AggregateCall rename(String name) { // no need to copy argList - already immutable return new AggregateCall(aggregation, distinct, argList, type, name); } public String toString() { StringBuilder buf = new StringBuilder(aggregation.getName()); buf.append("("); if (distinct) { buf.append((argList.size() == 0) ? "DISTINCT" : "DISTINCT "); } int i = -1; for (Integer arg : argList) { if (++i > 0) { buf.append(", "); } buf.append("$"); buf.append(arg); } buf.append(")"); return buf.toString(); } // override Object public boolean equals(Object o) { if (!(o instanceof AggregateCall)) { return false; } AggregateCall other = (AggregateCall) o; return aggregation.equals(other.aggregation) && (distinct == other.distinct) && argList.equals(other.argList); } // override Object public int hashCode() { return aggregation.hashCode() + argList.hashCode(); } /** * Creates a binding of this call in the context of an {@link AggregateRel}, * which can then be used to infer the return type. */ public AggregateRelBase.AggCallBinding createBinding( AggregateRelBase aggregateRelBase) { final RelDataType rowType = aggregateRelBase.getChild().getRowType(); return new AggregateRelBase.AggCallBinding( aggregateRelBase.getCluster().getTypeFactory(), (SqlAggFunction) aggregation, SqlTypeUtil.projectTypes(rowType, argList), aggregateRelBase.getGroupCount()); } /** * Creates an equivalent AggregateCall with new argument ordinals. * * @param args Arguments * @return AggregateCall that suits new inputs and GROUP BY columns */ public AggregateCall copy(List<Integer> args) { return new AggregateCall(aggregation, distinct, args, type, name); } /** * Creates equivalent AggregateCall that is adapted to a new input types * and/or number of columns in GROUP BY. * * @param input relation that will be used as a child of AggregateRel * @param aggArgs argument indices of the new call in the input * @param oldGroupKeyCount number of columns in GROUP BY of old AggregateRel * @param newGroupKeyCount number of columns in GROUP BY of new AggregateRel * @return AggregateCall that suits new inputs and GROUP BY columns */ public AggregateCall adaptTo(RelNode input, List<Integer> aggArgs, int oldGroupKeyCount, int newGroupKeyCount) { final SqlAggFunction sqlAgg = (SqlAggFunction) aggregation; // The return type of aggregate call need to be recomputed. // Since it might depend on the number of columns in GROUP BY. RelDataType newReturnType; if (oldGroupKeyCount == newGroupKeyCount) { newReturnType = getType(); } else { newReturnType = sqlAgg.inferReturnType( new AggregateRelBase.AggCallBinding( input.getCluster().getTypeFactory(), sqlAgg, SqlTypeUtil.projectTypes(input.getRowType(), aggArgs), newGroupKeyCount)); } return new AggregateCall( aggregation, isDistinct(), aggArgs, newReturnType, getName()); } /** Creates a copy of this aggregate call, applying a mapping to its * arguments. */ public AggregateCall transform(Mappings.TargetMapping mapping) { return copy(Mappings.permute(argList, mapping)); } } // End AggregateCall.java