AggregateCall.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.core;
import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.sql.SqlAggFunction;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Optionality;
import org.apache.calcite.util.mapping.Mapping;
import org.apache.calcite.util.mapping.Mappings;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
/**
* Call to an aggregate function within an
* {@link org.apache.calcite.rel.core.Aggregate}.
*/
public class AggregateCall {
//~ Instance fields --------------------------------------------------------
/**
* Some aggregate calls may produce runtime errors. For these
* we need to keep around the original source position information
* so that the runtime can produce error messages pointing to
* the offending source operation. For "safe" aggregations
* this field may be ZERO.
*/
private final SqlParserPos pos;
private final SqlAggFunction aggFunction;
private final boolean distinct;
private final boolean approximate;
private final boolean ignoreNulls;
public final RelDataType type;
public final @Nullable String name;
public final List<RexNode> rexList;
// 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;
public final int filterArg;
public final @Nullable ImmutableBitSet distinctKeys;
public final RelCollation collation;
//~ Constructors -----------------------------------------------------------
/**
* Creates an AggregateCall.
*
* @param aggFunction Aggregate function
* @param distinct Whether distinct
* @param argList List of ordinals of arguments
* @param type Result type
* @param name Name (may be null)
*/
@Deprecated // to be removed before 2.0
public AggregateCall(
SqlAggFunction aggFunction,
boolean distinct,
List<Integer> argList,
RelDataType type,
String name) {
this(SqlParserPos.ZERO, aggFunction, distinct, false, false,
ImmutableList.of(), argList, -1, null,
RelCollations.EMPTY, type, name);
}
/**
* Creates an AggregateCall.
*
* @param pos Source position for this aggregate.
* Ideally it should only be ZERO when the aggregate
* can never fail at runtime.
* @param aggFunction Aggregate function
* @param distinct Whether distinct
* @param approximate Whether approximate
* @param rexList List of pre-arguments
* @param argList List of ordinals of arguments
* @param filterArg Ordinal of filter argument (the
* {@code FILTER (WHERE ...)} clause in SQL), or -1
* @param distinctKeys Ordinals of fields to make values distinct on before
* aggregating, or null
* @param collation How to sort values before aggregation (the
* {@code WITHIN GROUP} clause in SQL)
* @param type Result type
* @param name Name (may be null)
*/
private AggregateCall(SqlParserPos pos, SqlAggFunction aggFunction, boolean distinct,
boolean approximate, boolean ignoreNulls,
List<RexNode> rexList, List<Integer> argList,
int filterArg, @Nullable ImmutableBitSet distinctKeys,
RelCollation collation, RelDataType type, @Nullable String name) {
this.pos = pos;
this.type = requireNonNull(type, "type");
this.name = name;
this.aggFunction = requireNonNull(aggFunction, "aggFunction");
this.argList = ImmutableList.copyOf(argList);
this.rexList = ImmutableList.copyOf(rexList);
this.distinctKeys = distinctKeys;
this.filterArg = filterArg;
this.collation = requireNonNull(collation, "collation");
this.distinct = distinct;
this.approximate = approximate;
this.ignoreNulls = ignoreNulls;
checkArgument(aggFunction.getDistinctOptionality() != Optionality.IGNORED
|| !distinct,
"DISTINCT has no effect for this aggregate function, so must be false");
checkArgument(filterArg < 0 || aggFunction.allowsFilter());
}
//~ Methods ----------------------------------------------------------------
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, List<Integer> argList, int groupCount, RelNode input,
@Nullable RelDataType type, @Nullable String name) {
return create(aggFunction, distinct, false, false,
ImmutableList.of(), argList, -1,
null, RelCollations.EMPTY, groupCount, input, type, name);
}
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, List<Integer> argList, int filterArg, int groupCount,
RelNode input, @Nullable RelDataType type, @Nullable String name) {
return create(aggFunction, distinct, false, false,
ImmutableList.of(), argList, filterArg,
null, RelCollations.EMPTY, groupCount, input, type, name);
}
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, boolean approximate, List<Integer> argList,
int filterArg, int groupCount,
RelNode input, @Nullable RelDataType type, @Nullable String name) {
return create(aggFunction, distinct, approximate, false,
ImmutableList.of(), argList,
filterArg, null, RelCollations.EMPTY, groupCount, input, type, name);
}
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, boolean approximate, List<Integer> argList,
int filterArg, RelCollation collation, int groupCount,
RelNode input, @Nullable RelDataType type, @Nullable String name) {
return create(aggFunction, distinct, approximate, false,
ImmutableList.of(), argList, filterArg,
null, collation, groupCount, input, type, name);
}
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, boolean approximate, boolean ignoreNulls,
List<Integer> argList, int filterArg,
@Nullable ImmutableBitSet distinctKeys, RelCollation collation,
int groupCount,
RelNode input, @Nullable RelDataType type, @Nullable String name) {
return create(aggFunction, distinct, approximate, ignoreNulls,
ImmutableList.of(), argList, filterArg,
distinctKeys, collation, groupCount, input, type, name);
}
/** Creates an AggregateCall, inferring its type if {@code type} is null. */
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, boolean approximate, boolean ignoreNulls,
List<RexNode> rexList, List<Integer> argList, int filterArg,
@Nullable ImmutableBitSet distinctKeys, RelCollation collation,
int groupCount,
RelNode input, @Nullable RelDataType type, @Nullable String name) {
return create(SqlParserPos.ZERO, aggFunction, distinct, approximate,
ignoreNulls, rexList, argList, filterArg, distinctKeys, collation, groupCount,
input, type, name);
}
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlParserPos pos, SqlAggFunction aggFunction,
boolean distinct, boolean approximate, boolean ignoreNulls,
List<RexNode> rexList, List<Integer> argList, int filterArg,
@Nullable ImmutableBitSet distinctKeys, RelCollation collation,
int groupCount,
RelNode input, @Nullable RelDataType type, @Nullable String name) {
if (type == null) {
final RelDataTypeFactory typeFactory =
input.getCluster().getTypeFactory();
final List<RelDataType> preTypes = RexUtil.types(rexList);
final List<RelDataType> types =
SqlTypeUtil.projectTypes(input.getRowType(), argList);
final Aggregate.AggCallBinding callBinding =
new Aggregate.AggCallBinding(typeFactory, aggFunction, preTypes,
types, groupCount, filterArg >= 0);
type = aggFunction.inferReturnType(callBinding);
}
return create(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
/** Creates an AggregateCall, inferring its type if {@code type} is null. */
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, boolean approximate, boolean ignoreNulls,
List<RexNode> rexList, List<Integer> argList, int filterArg,
@Nullable ImmutableBitSet distinctKeys, RelCollation collation,
boolean hasEmptyGroup,
RelNode input, @Nullable RelDataType type, @Nullable String name) {
return create(SqlParserPos.ZERO, aggFunction, distinct, approximate,
ignoreNulls, rexList, argList, filterArg, distinctKeys, collation, hasEmptyGroup,
input, type, name);
}
public static AggregateCall create(SqlParserPos pos, SqlAggFunction aggFunction,
boolean distinct, boolean approximate, boolean ignoreNulls,
List<RexNode> rexList, List<Integer> argList, int filterArg,
@Nullable ImmutableBitSet distinctKeys, RelCollation collation,
boolean hasEmptyGroup,
RelNode input, @Nullable RelDataType type, @Nullable String name) {
if (type == null) {
final RelDataTypeFactory typeFactory =
input.getCluster().getTypeFactory();
final List<RelDataType> preTypes = RexUtil.types(rexList);
final List<RelDataType> types =
SqlTypeUtil.projectTypes(input.getRowType(), argList);
final Aggregate.AggCallBinding callBinding;
if (aggFunction.getKind() == SqlKind.PERCENTILE_DISC
|| aggFunction.getKind() == SqlKind.PERCENTILE_CONT) {
callBinding =
new Aggregate.PercentileDiscAggCallBinding(typeFactory, aggFunction,
SqlTypeUtil.projectTypes(input.getRowType(), argList),
SqlTypeUtil.projectTypes(input.getRowType(), collation.getKeys()).get(0),
hasEmptyGroup, filterArg >= 0);
} else {
callBinding =
new Aggregate.AggCallBinding(typeFactory, aggFunction, preTypes, types,
hasEmptyGroup, filterArg >= 0);
}
type = aggFunction.inferReturnType(callBinding);
}
return create(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, List<Integer> argList, int filterArg, RelDataType type,
@Nullable String name) {
return create(aggFunction, distinct, false, false,
ImmutableList.of(), argList, filterArg, null,
RelCollations.EMPTY, type, name);
}
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, boolean approximate, List<Integer> argList,
int filterArg, RelDataType type, @Nullable String name) {
return create(aggFunction, distinct, approximate, false,
ImmutableList.of(), argList, filterArg,
null, RelCollations.EMPTY, type, name);
}
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, boolean approximate, List<Integer> argList,
int filterArg, RelCollation collation, RelDataType type, @Nullable String name) {
return create(aggFunction, distinct, approximate, false,
ImmutableList.of(), argList, filterArg,
null, collation, type, name);
}
@Deprecated // to be removed before 2.0
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, boolean approximate, boolean ignoreNulls,
List<Integer> argList, int filterArg, RelCollation collation,
RelDataType type, @Nullable String name) {
return create(aggFunction, distinct, approximate, ignoreNulls,
ImmutableList.of(), argList,
filterArg, null, collation, type, name);
}
/** Creates an AggregateCall. */
public static AggregateCall create(SqlAggFunction aggFunction,
boolean distinct, boolean approximate, boolean ignoreNulls,
List<RexNode> rexList, List<Integer> argList, int filterArg,
@Nullable ImmutableBitSet distinctKeys, RelCollation collation,
RelDataType type, @Nullable String name) {
return create(SqlParserPos.ZERO, aggFunction, distinct, approximate,
ignoreNulls, rexList, argList, filterArg, distinctKeys, collation, type, name);
}
public static AggregateCall create(SqlParserPos pos, SqlAggFunction aggFunction,
boolean distinct, boolean approximate, boolean ignoreNulls,
List<RexNode> rexList, List<Integer> argList, int filterArg,
@Nullable ImmutableBitSet distinctKeys, RelCollation collation,
RelDataType type, @Nullable String name) {
final boolean distinct2 = distinct
&& (aggFunction.getDistinctOptionality() != Optionality.IGNORED);
return new AggregateCall(pos, aggFunction, distinct2, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
/**
* Returns whether this AggregateCall is distinct, as in <code>
* COUNT(DISTINCT empno)</code>.
*
* @return whether distinct
*/
public final boolean isDistinct() {
return distinct;
}
/** Withs {@link #isDistinct()}. */
public AggregateCall withDistinct(boolean distinct) {
return distinct == this.distinct ? this
: new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
/**
* Returns whether this AggregateCall is approximate, as in <code>
* APPROX_COUNT_DISTINCT(empno)</code>.
*
* @return whether approximate
*/
public final boolean isApproximate() {
return approximate;
}
/** Withs {@link #isApproximate()}. */
public AggregateCall withApproximate(boolean approximate) {
return approximate == this.approximate ? this
: new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
/**
* Returns whether this AggregateCall ignores nulls.
*
* @return whether ignore nulls
*/
public final boolean ignoreNulls() {
return ignoreNulls;
}
/** Withs {@link #ignoreNulls()}. */
public AggregateCall withIgnoreNulls(boolean ignoreNulls) {
return ignoreNulls == this.ignoreNulls ? this
: new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
/**
* Returns the aggregate function.
*
* @return aggregate function
*/
public final SqlAggFunction getAggregation() {
return aggFunction;
}
/**
* Returns the aggregate ordering definition (the {@code WITHIN GROUP} clause
* in SQL), or the empty list if not specified.
*
* @return ordering definition
*/
public RelCollation getCollation() {
return collation;
}
/** Withs {@link #getCollation()}. */
public AggregateCall withCollation(RelCollation collation) {
return collation.equals(this.collation) ? this
: new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
/**
* 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;
}
/** Withs {@link #getArgList()}. */
public AggregateCall withArgList(List<Integer> argList) {
return argList.equals(this.argList) ? this
: new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
/** Withs {@link #distinctKeys}. */
public AggregateCall withDistinctKeys(
@Nullable ImmutableBitSet distinctKeys) {
return Objects.equals(distinctKeys, this.distinctKeys) ? this
: new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
/**
* Returns the result type.
*
* @return result type
*/
public final RelDataType getType() {
return type;
}
/**
* Returns the name.
*
* @return name
*/
public @Nullable String getName() {
return name;
}
/** Withs {@link #name}. */
public AggregateCall withName(@Nullable String name) {
return Objects.equals(name, this.name) ? this
: new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
@Deprecated // to be removed before 2.0
public AggregateCall rename(@Nullable String name) {
return withName(name);
}
@Override public String toString() {
StringBuilder buf = new StringBuilder(aggFunction.toString());
buf.append("(");
if (approximate) {
buf.append("APPROXIMATE ");
}
if (distinct) {
buf.append(argList.isEmpty() ? "DISTINCT" : "DISTINCT ");
}
int i = -1;
for (RexNode rexNode : rexList) {
if (++i > 0) {
buf.append(", ");
}
buf.append(rexNode);
}
for (Integer arg : argList) {
if (++i > 0) {
buf.append(", ");
}
buf.append("$");
buf.append(arg);
}
buf.append(")");
if (distinctKeys != null) {
buf.append(" WITHIN DISTINCT (");
for (Ord<Integer> key : Ord.zip(distinctKeys)) {
buf.append(key.i > 0 ? ", $" : "$");
buf.append(key.e);
}
buf.append(")");
}
if (hasCollation()) {
buf.append(" WITHIN GROUP (");
buf.append(collation);
buf.append(")");
}
if (hasFilter()) {
buf.append(" FILTER $");
buf.append(filterArg);
}
return buf.toString();
}
/** Returns whether this AggregateCall has a filter argument. */
public boolean hasFilter() {
return filterArg >= 0;
}
/** Returns true if this AggregateCall has a non-empty collation. Returns false otherwise. */
public boolean hasCollation() {
return !collation.equals(RelCollations.EMPTY);
}
/** Withs {@link #filterArg}. */
public AggregateCall withFilter(int filterArg) {
return filterArg == this.filterArg ? this
: new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
public SqlParserPos getParserPosition() {
return this.pos;
}
@Override public boolean equals(@Nullable Object o) {
// Intentionally ignore the position
return o == this
|| o instanceof AggregateCall
&& aggFunction.equals(((AggregateCall) o).aggFunction)
&& distinct == ((AggregateCall) o).distinct
&& approximate == ((AggregateCall) o).approximate
&& ignoreNulls == ((AggregateCall) o).ignoreNulls
&& argList.equals(((AggregateCall) o).argList)
&& filterArg == ((AggregateCall) o).filterArg
&& Objects.equals(distinctKeys, ((AggregateCall) o).distinctKeys)
&& collation.equals(((AggregateCall) o).collation);
}
@Override public int hashCode() {
// Ignore the position!
return Objects.hash(aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation);
}
/**
* Creates a binding of this call in the context of an
* {@link org.apache.calcite.rel.logical.LogicalAggregate},
* which can then be used to infer the return type.
*/
public Aggregate.AggCallBinding createBinding(
Aggregate aggregateRelBase) {
final RelDataType rowType = aggregateRelBase.getInput().getRowType();
final RelDataTypeFactory typeFactory =
aggregateRelBase.getCluster().getTypeFactory();
if (aggFunction.getKind() == SqlKind.PERCENTILE_DISC
|| aggFunction.getKind() == SqlKind.PERCENTILE_CONT) {
assert collation.getKeys().size() == 1;
return new Aggregate.PercentileDiscAggCallBinding(typeFactory,
aggFunction, SqlTypeUtil.projectTypes(rowType, argList),
SqlTypeUtil.projectTypes(rowType, collation.getKeys()).get(0),
aggregateRelBase.hasEmptyGroup(), hasFilter());
}
return new Aggregate.AggCallBinding(typeFactory, aggFunction,
RexUtil.types(rexList), SqlTypeUtil.projectTypes(rowType, argList),
aggregateRelBase.hasEmptyGroup(), hasFilter());
}
/**
* Creates an equivalent AggregateCall with new argument ordinals.
*
* @see #transform(Mappings.TargetMapping)
*
* @param argList Arguments
* @return AggregateCall that suits new inputs and GROUP BY columns
*/
@Deprecated // to be removed before 2.0
public AggregateCall copy(List<Integer> argList, int filterArg,
@Nullable ImmutableBitSet distinctKeys, RelCollation collation) {
return new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
@Deprecated // to be removed before 2.0
public AggregateCall copy(List<Integer> argList, int filterArg,
RelCollation collation) {
// ignoring distinctKeys is error-prone
return new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
@Deprecated // to be removed before 2.0
public AggregateCall copy(List<Integer> argList, int filterArg) {
// ignoring distinctKeys, collation is error-prone
return new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
@Deprecated // to be removed before 2.0
public AggregateCall copy(List<Integer> argList) {
// ignoring filterArg, distinctKeys, collation is error-prone
return new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation, type, name);
}
/**
* Creates an equivalent AggregateCall that is adapted to a new input types
* and/or number of columns in GROUP BY.
*
* @param input Relation that will be input of Aggregate
* @param argList Argument indices of the new call in the input
* @param filterArg Index of the filter, or -1
* @param oldGroupKeyCount number of columns in GROUP BY of old aggregate
* @param newGroupKeyCount number of columns in GROUP BY of new aggregate
* @return AggregateCall that suits new inputs and GROUP BY columns
*/
@Deprecated // to be removed before 2.0
public AggregateCall adaptTo(RelNode input, List<Integer> argList,
int filterArg, int oldGroupKeyCount, int newGroupKeyCount) {
// The return type of aggregate call need to be recomputed.
// Since it might depend on the number of columns in GROUP BY.
final RelDataType newType =
oldGroupKeyCount == newGroupKeyCount
&& argList.equals(this.argList)
&& filterArg == this.filterArg
? type
: null;
return create(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation,
newGroupKeyCount, input, newType, getName());
}
/**
* Creates an equivalent AggregateCall that is adapted to a new input types
* and/or number of columns in GROUP BY.
*
* @param input Relation that will be input of Aggregate
* @param argList Argument indices of the new call in the input
* @param filterArg Index of the filter, or -1
* @param oldHasEmptyGroup Whether old aggregate contains empty group
* @param newHasEmptyGroup Whether new aggregate contains empty group
* @return AggregateCall that suits new inputs and GROUP BY columns
*/
public AggregateCall adaptTo(RelNode input, List<Integer> argList,
int filterArg, boolean oldHasEmptyGroup, boolean newHasEmptyGroup) {
// The return type of aggregate call need to be recomputed.
// Since it might depend on the number of columns in GROUP BY.
final RelDataType newType =
oldHasEmptyGroup == newHasEmptyGroup
&& argList.equals(this.argList)
&& filterArg == this.filterArg
? type
: null;
return create(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, argList, filterArg, distinctKeys, collation,
newHasEmptyGroup, input, newType, getName());
}
/** Creates a copy of this aggregate call, applying a mapping to its
* arguments. */
public AggregateCall transform(Mappings.TargetMapping mapping) {
return new AggregateCall(pos, aggFunction, distinct, approximate, ignoreNulls,
rexList, Mappings.apply2((Mapping) mapping, argList),
hasFilter() ? Mappings.apply(mapping, filterArg) : -1,
distinctKeys == null ? null : distinctKeys.permute(mapping),
RelCollations.permute(collation, mapping), type, name);
}
}