/*
* 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.sql2rel;
import java.math.BigDecimal;
import java.util.*;
import org.eigenbase.rel.*;
import org.eigenbase.rel.rules.RemoveTrivialProjectRule;
import org.eigenbase.rel.rules.SemiJoinRel;
import org.eigenbase.relopt.*;
import org.eigenbase.reltype.*;
import org.eigenbase.rex.*;
import org.eigenbase.sql.validate.SqlValidator;
import org.eigenbase.util.*;
import org.eigenbase.util.mapping.*;
import net.hydromatic.linq4j.Ord;
import net.hydromatic.optiq.util.BitSets;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
/**
* Transformer that walks over a tree of relational expressions, replacing each
* {@link RelNode} with a 'slimmed down' relational expression that projects
* only the columns required by its consumer.
*
* <p>Uses multi-methods to fire the right rule for each type of relational
* expression. This allows the transformer to be extended without having to
* add a new method to RelNode, and without requiring a collection of rule
* classes scattered to the four winds.
*
* <p>REVIEW: jhyde, 2009/7/28: Is sql2rel the correct package for this class?
* Trimming fields is not an essential part of SQL-to-Rel translation, and
* arguably belongs in the optimization phase. But this transformer does not
* obey the usual pattern for planner rules; it is difficult to do so, because
* each {@link RelNode} needs to return a different set of fields after
* trimming.
*
* <p>TODO: Change 2nd arg of the {@link #trimFields} method from BitSet to
* Mapping. Sometimes it helps the consumer if you return the columns in a
* particular order. For instance, it may avoid a project at the top of the
* tree just for reordering. Could ease the transition by writing methods that
* convert BitSet to Mapping and vice versa.
*/
public class RelFieldTrimmer implements ReflectiveVisitor {
//~ Static fields/initializers ---------------------------------------------
//~ Instance fields --------------------------------------------------------
private final ReflectUtil.MethodDispatcher<TrimResult> trimFieldsDispatcher;
private final RelFactories.ProjectFactory projectFactory;
private final RelFactories.FilterFactory filterFactory;
private final RelFactories.JoinFactory joinFactory;
private final RelFactories.SemiJoinFactory semiJoinFactory;
private final RelFactories.SortFactory sortFactory;
private final RelFactories.AggregateFactory aggregateFactory;
private final RelFactories.SetOpFactory setOpFactory;
//~ Constructors -----------------------------------------------------------
/**
* Creates a RelFieldTrimmer.
*
* @param validator Validator
*/
public RelFieldTrimmer(SqlValidator validator) {
this(validator,
RelFactories.DEFAULT_PROJECT_FACTORY,
RelFactories.DEFAULT_FILTER_FACTORY,
RelFactories.DEFAULT_JOIN_FACTORY,
RelFactories.DEFAULT_SEMI_JOIN_FACTORY,
RelFactories.DEFAULT_SORT_FACTORY,
RelFactories.DEFAULT_AGGREGATE_FACTORY,
RelFactories.DEFAULT_SET_OP_FACTORY);
}
/**
* Creates a RelFieldTrimmer.
*
* @param validator Validator
*/
public RelFieldTrimmer(SqlValidator validator,
RelFactories.ProjectFactory projectFactory,
RelFactories.FilterFactory filterFactory,
RelFactories.JoinFactory joinFactory,
RelFactories.SemiJoinFactory semiJoinFactory,
RelFactories.SortFactory sortFactory,
RelFactories.AggregateFactory aggregateFactory,
RelFactories.SetOpFactory setOpFactory) {
Util.discard(validator); // may be useful one day
this.trimFieldsDispatcher =
ReflectUtil.createMethodDispatcher(
TrimResult.class,
this,
"trimFields",
RelNode.class,
BitSet.class,
Set.class);
this.projectFactory = Preconditions.checkNotNull(projectFactory);
this.filterFactory = Preconditions.checkNotNull(filterFactory);
this.joinFactory = Preconditions.checkNotNull(joinFactory);
this.semiJoinFactory = Preconditions.checkNotNull(semiJoinFactory);
this.sortFactory = Preconditions.checkNotNull(sortFactory);
this.aggregateFactory = Preconditions.checkNotNull(aggregateFactory);
this.setOpFactory = Preconditions.checkNotNull(setOpFactory);
}
//~ Methods ----------------------------------------------------------------
/**
* Trims unused fields from a relational expression.
*
* <p>We presume that all fields of the relational expression are wanted by
* its consumer, so only trim fields that are not used within the tree.
*
* @param root Root node of relational expression
* @return Trimmed relational expression
*/
public RelNode trim(RelNode root) {
final int fieldCount = root.getRowType().getFieldCount();
final BitSet fieldsUsed = BitSets.range(fieldCount);
final Set<RelDataTypeField> extraFields = Collections.emptySet();
final TrimResult trimResult =
dispatchTrimFields(root, fieldsUsed, extraFields);
if (!trimResult.right.isIdentity()) {
throw new IllegalArgumentException();
}
return trimResult.left;
}
/**
* Trims the fields of an input relational expression.
*
* @param rel Relational expression
* @param input Input relational expression, whose fields to trim
* @param fieldsUsed Bitmap of fields needed by the consumer
* @return New relational expression and its field mapping
*/
protected TrimResult trimChild(
RelNode rel,
RelNode input,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
Util.discard(rel);
if (input.getClass().getName().endsWith("MedMdrClassExtentRel")) {
// MedMdrJoinRule cannot handle Join of Project of
// MedMdrClassExtentRel, only naked MedMdrClassExtentRel.
// So, disable trimming.
fieldsUsed = BitSets.range(input.getRowType().getFieldCount());
}
return dispatchTrimFields(input, fieldsUsed, extraFields);
}
/**
* Trims a child relational expression, then adds back a dummy project to
* restore the fields that were removed.
*
* <p>Sounds pointless? It causes unused fields to be removed
* further down the tree (towards the leaves), but it ensure that the
* consuming relational expression continues to see the same fields.
*
* @param rel Relational expression
* @param input Input relational expression, whose fields to trim
* @param fieldsUsed Bitmap of fields needed by the consumer
* @return New relational expression and its field mapping
*/
protected TrimResult trimChildRestore(
RelNode rel,
RelNode input,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
TrimResult trimResult = trimChild(rel, input, fieldsUsed, extraFields);
if (trimResult.right.isIdentity()) {
return trimResult;
}
final RelDataType rowType = input.getRowType();
List<RelDataTypeField> fieldList = rowType.getFieldList();
final List<RexNode> exprList = new ArrayList<RexNode>();
final List<String> nameList = rowType.getFieldNames();
RexBuilder rexBuilder = rel.getCluster().getRexBuilder();
assert trimResult.right.getSourceCount() == fieldList.size();
for (int i = 0; i < fieldList.size(); i++) {
int source = trimResult.right.getTargetOpt(i);
RelDataTypeField field = fieldList.get(i);
exprList.add(
source < 0
? rexBuilder.makeZeroLiteral(field.getType())
: rexBuilder.makeInputRef(field.getType(), source));
}
RelNode project =
projectFactory.createProject(
trimResult.left, exprList, nameList);
return new TrimResult(
project,
Mappings.createIdentity(fieldList.size()));
}
/**
* Invokes {@link #trimFields}, or the appropriate method for the type
* of the rel parameter, using multi-method dispatch.
*
* @param rel Relational expression
* @param fieldsUsed Bitmap of fields needed by the consumer
* @return New relational expression and its field mapping
*/
protected final TrimResult dispatchTrimFields(
RelNode rel,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
final TrimResult trimResult =
trimFieldsDispatcher.invoke(rel, fieldsUsed, extraFields);
final RelNode newRel = trimResult.left;
final Mapping mapping = trimResult.right;
final int fieldCount = rel.getRowType().getFieldCount();
assert mapping.getSourceCount() == fieldCount
: "source: " + mapping.getSourceCount() + " != " + fieldCount;
final int newFieldCount = newRel.getRowType().getFieldCount();
assert mapping.getTargetCount() + extraFields.size() == newFieldCount
|| Bug.TODO_FIXED
: "target: " + mapping.getTargetCount()
+ " + " + extraFields.size()
+ " != " + newFieldCount;
if (Bug.TODO_FIXED) {
assert newFieldCount > 0 : "rel has no fields after trim: " + rel;
}
if (newRel.equals(rel)) {
return new TrimResult(rel, mapping);
}
return trimResult;
}
/**
* Visit method, per {@link org.eigenbase.util.ReflectiveVisitor}.
*
* <p>This method is invoked reflectively, so there may not be any apparent
* calls to it. The class (or derived classes) may contain overloads of
* this method with more specific types for the {@code rel} parameter.
*
* <p>Returns a pair: the relational expression created, and the mapping
* between the original fields and the fields of the newly created
* relational expression.
*
* @param rel Relational expression
* @param fieldsUsed Fields needed by the consumer
* @return relational expression and mapping
*/
public TrimResult trimFields(
RelNode rel,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
// We don't know how to trim this kind of relational expression, so give
// it back intact.
Util.discard(fieldsUsed);
return new TrimResult(
rel,
Mappings.createIdentity(
rel.getRowType().getFieldCount()));
}
/**
* Variant of {@link #trimFields(RelNode, BitSet, Set)} for
* {@link ProjectRel}.
*/
public TrimResult trimFields(
ProjectRelBase project,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
final RelDataType rowType = project.getRowType();
final int fieldCount = rowType.getFieldCount();
final RelNode input = project.getChild();
final RelDataType inputRowType = input.getRowType();
// Which fields are required from the input?
BitSet inputFieldsUsed = new BitSet(inputRowType.getFieldCount());
final Set<RelDataTypeField> inputExtraFields =
new LinkedHashSet<RelDataTypeField>(extraFields);
RelOptUtil.InputFinder inputFinder =
new RelOptUtil.InputFinder(inputFieldsUsed, inputExtraFields);
for (Ord<RexNode> ord : Ord.zip(project.getProjects())) {
if (fieldsUsed.get(ord.i)) {
ord.e.accept(inputFinder);
}
}
// Create input with trimmed columns.
TrimResult trimResult =
trimChild(project, input, inputFieldsUsed, inputExtraFields);
RelNode newInput = trimResult.left;
final Mapping inputMapping = trimResult.right;
// If the input is unchanged, and we need to project all columns,
// there's nothing we can do.
if (newInput == input
&& fieldsUsed.cardinality() == fieldCount) {
return new TrimResult(
project,
Mappings.createIdentity(fieldCount));
}
// Some parts of the system can't handle rows with zero fields, so
// pretend that one field is used.
if (fieldsUsed.cardinality() == 0) {
return dummyProject(fieldCount, newInput);
}
// Build new project expressions, and populate the mapping.
List<RexNode> newProjectExprList =
new ArrayList<RexNode>();
final RexVisitor<RexNode> shuttle =
new RexPermuteInputsShuttle(
inputMapping, newInput);
final Mapping mapping =
Mappings.create(
MappingType.INVERSE_SURJECTION,
fieldCount,
fieldsUsed.cardinality());
for (Ord<RexNode> ord : Ord.zip(project.getProjects())) {
if (fieldsUsed.get(ord.i)) {
mapping.set(ord.i, newProjectExprList.size());
RexNode newProjectExpr = ord.e.accept(shuttle);
newProjectExprList.add(newProjectExpr);
}
}
final RelDataType newRowType =
RelOptUtil.permute(project.getCluster().getTypeFactory(), rowType,
mapping);
final RelNode newProject;
if (RemoveTrivialProjectRule.isIdentity(
newProjectExprList,
newRowType,
newInput.getRowType())) {
// The new project would be the identity. It is equivalent to return
// its child.
newProject = newInput;
} else {
newProject = projectFactory.createProject(newInput, newProjectExprList,
newRowType.getFieldNames());
assert newProject.getClass() == project.getClass();
}
return new TrimResult(newProject, mapping);
}
/** Creates a project with a dummy column, to protect the parts of the system
* that cannot handle a relational expression with no columns.
*
* @param fieldCount Number of fields in the original relational expression
* @param input Trimmed input
* @return Dummy project, or null if no dummy is required
*/
private TrimResult dummyProject(int fieldCount, RelNode input) {
final RelOptCluster cluster = input.getCluster();
final Mapping mapping =
Mappings.create(MappingType.INVERSE_SURJECTION, fieldCount, 1);
if (input.getRowType().getFieldCount() == 1) {
// Input already has one field (and may in fact be a dummy project we
// created for the child). We can't do better.
return new TrimResult(input, mapping);
}
final RexLiteral expr =
cluster.getRexBuilder().makeExactLiteral(BigDecimal.ZERO);
final RelNode newProject = projectFactory.createProject(input,
ImmutableList.<RexNode>of(expr), ImmutableList.of("DUMMY"));
return new TrimResult(newProject, mapping);
}
/**
* Variant of {@link #trimFields(RelNode, BitSet, Set)} for
* {@link FilterRel}.
*/
public TrimResult trimFields(
FilterRelBase filter,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
final RelDataType rowType = filter.getRowType();
final int fieldCount = rowType.getFieldCount();
final RexNode conditionExpr = filter.getCondition();
final RelNode input = filter.getChild();
// We use the fields used by the consumer, plus any fields used in the
// filter.
BitSet inputFieldsUsed = (BitSet) fieldsUsed.clone();
final Set<RelDataTypeField> inputExtraFields =
new LinkedHashSet<RelDataTypeField>(extraFields);
RelOptUtil.InputFinder inputFinder =
new RelOptUtil.InputFinder(inputFieldsUsed, inputExtraFields);
conditionExpr.accept(inputFinder);
// Create input with trimmed columns.
TrimResult trimResult =
trimChild(filter, input, inputFieldsUsed, inputExtraFields);
RelNode newInput = trimResult.left;
final Mapping inputMapping = trimResult.right;
// If the input is unchanged, and we need to project all columns,
// there's nothing we can do.
if (newInput == input
&& fieldsUsed.cardinality() == fieldCount) {
return new TrimResult(
filter,
Mappings.createIdentity(fieldCount));
}
// Build new project expressions, and populate the mapping.
final RexVisitor<RexNode> shuttle =
new RexPermuteInputsShuttle(inputMapping, newInput);
RexNode newConditionExpr =
conditionExpr.accept(shuttle);
final RelNode newFilter = filterFactory.createFilter(newInput,
newConditionExpr);
// The result has the same mapping as the input gave us. Sometimes we
// return fields that the consumer didn't ask for, because the filter
// needs them for its condition.
return new TrimResult(newFilter, inputMapping);
}
/**
* Variant of {@link #trimFields(RelNode, BitSet, Set)} for
* {@link SortRel}.
*/
public TrimResult trimFields(
SortRel sort,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
final RelDataType rowType = sort.getRowType();
final int fieldCount = rowType.getFieldCount();
final RelCollation collation = sort.getCollation();
final RelNode input = sort.getChild();
// We use the fields used by the consumer, plus any fields used as sort
// keys.
BitSet inputFieldsUsed = (BitSet) fieldsUsed.clone();
for (RelFieldCollation field : collation.getFieldCollations()) {
inputFieldsUsed.set(field.getFieldIndex());
}
// Create input with trimmed columns.
final Set<RelDataTypeField> inputExtraFields = Collections.emptySet();
TrimResult trimResult =
trimChild(sort, input, inputFieldsUsed, inputExtraFields);
RelNode newInput = trimResult.left;
final Mapping inputMapping = trimResult.right;
// If the input is unchanged, and we need to project all columns,
// there's nothing we can do.
if (newInput == input
&& inputMapping.isIdentity()
&& fieldsUsed.cardinality() == fieldCount) {
return new TrimResult(
sort,
Mappings.createIdentity(fieldCount));
}
final RelCollation newCollation =
sort.getTraitSet().canonize(RexUtil.apply(inputMapping, collation));
final RelTraitSet newTraitSet = sort.getTraitSet().replace(newCollation);
final RelNode newSort = sortFactory.createSort(newTraitSet, newInput,
newCollation, sort.offset, sort.fetch);
// The result has the same mapping as the input gave us. Sometimes we
// return fields that the consumer didn't ask for, because the filter
// needs them for its condition.
return new TrimResult(newSort, inputMapping);
}
/**
* Variant of {@link #trimFields(RelNode, BitSet, Set)} for
* {@link JoinRel}.
*/
public TrimResult trimFields(
JoinRelBase join,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
final int fieldCount = join.getSystemFieldList().size()
+ join.getLeft().getRowType().getFieldCount()
+ join.getRight().getRowType().getFieldCount();
final RexNode conditionExpr = join.getCondition();
final int systemFieldCount = join.getSystemFieldList().size();
// Add in fields used in the condition.
BitSet fieldsUsedPlus = (BitSet) fieldsUsed.clone();
final Set<RelDataTypeField> combinedInputExtraFields =
new LinkedHashSet<RelDataTypeField>(extraFields);
RelOptUtil.InputFinder inputFinder =
new RelOptUtil.InputFinder(
fieldsUsedPlus, combinedInputExtraFields);
conditionExpr.accept(inputFinder);
// If no system fields are used, we can remove them.
int systemFieldUsedCount = 0;
for (int i = 0; i < systemFieldCount; ++i) {
if (fieldsUsed.get(i)) {
++systemFieldUsedCount;
}
}
final int newSystemFieldCount;
if (systemFieldUsedCount == 0) {
newSystemFieldCount = 0;
} else {
newSystemFieldCount = systemFieldCount;
}
int offset = systemFieldCount;
int changeCount = 0;
int newFieldCount = newSystemFieldCount;
List<RelNode> newInputs = new ArrayList<RelNode>(2);
List<Mapping> inputMappings = new ArrayList<Mapping>();
List<Integer> inputExtraFieldCounts = new ArrayList<Integer>();
for (RelNode input : join.getInputs()) {
final RelDataType inputRowType = input.getRowType();
final int inputFieldCount = inputRowType.getFieldCount();
// Compute required mapping.
BitSet inputFieldsUsed = new BitSet(inputFieldCount);
for (int bit : BitSets.toIter(fieldsUsedPlus)) {
if (bit >= offset && bit < offset + inputFieldCount) {
inputFieldsUsed.set(bit - offset);
}
}
// If there are system fields, we automatically use the
// corresponding field in each input.
if (newSystemFieldCount > 0) {
// calling with newSystemFieldCount == 0 should be safe but hits
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6222207
inputFieldsUsed.set(0, newSystemFieldCount);
}
// FIXME: We ought to collect extra fields for each input
// individually. For now, we assume that just one input has
// on-demand fields.
Set<RelDataTypeField> inputExtraFields =
RelDataTypeImpl.extra(inputRowType) == null
? Collections.<RelDataTypeField>emptySet()
: combinedInputExtraFields;
inputExtraFieldCounts.add(inputExtraFields.size());
TrimResult trimResult =
trimChild(join, input, inputFieldsUsed, inputExtraFields);
newInputs.add(trimResult.left);
if (trimResult.left != input) {
++changeCount;
}
final Mapping inputMapping = trimResult.right;
inputMappings.add(inputMapping);
// Move offset to point to start of next input.
offset += inputFieldCount;
newFieldCount +=
inputMapping.getTargetCount() + inputExtraFields.size();
}
Mapping mapping =
Mappings.create(
MappingType.INVERSE_SURJECTION,
fieldCount,
newFieldCount);
for (int i = 0; i < newSystemFieldCount; ++i) {
mapping.set(i, i);
}
offset = systemFieldCount;
int newOffset = newSystemFieldCount;
for (int i = 0; i < inputMappings.size(); i++) {
Mapping inputMapping = inputMappings.get(i);
for (IntPair pair : inputMapping) {
mapping.set(pair.source + offset, pair.target + newOffset);
}
offset += inputMapping.getSourceCount();
newOffset += inputMapping.getTargetCount()
+ inputExtraFieldCounts.get(i);
}
if (changeCount == 0
&& mapping.isIdentity()) {
return new TrimResult(join, Mappings.createIdentity(fieldCount));
}
// Build new join.
final RexVisitor<RexNode> shuttle =
new RexPermuteInputsShuttle(
mapping, newInputs.get(0), newInputs.get(1));
RexNode newConditionExpr =
conditionExpr.accept(shuttle);
final RelNode newJoin;
if (join instanceof SemiJoinRel) {
newJoin = semiJoinFactory.createSemiJoin(newInputs.get(0),
newInputs.get(1), newConditionExpr);
// For SemiJoins only map fields from the left-side
Mapping inputMapping = inputMappings.get(0);
mapping = Mappings.create(MappingType.INVERSE_SURJECTION,
join.getRowType().getFieldCount(),
newSystemFieldCount + inputMapping.getTargetCount());
for (int i = 0; i < newSystemFieldCount; ++i) {
mapping.set(i, i);
}
offset = systemFieldCount;
newOffset = newSystemFieldCount;
for (IntPair pair : inputMapping) {
mapping.set(pair.source + offset, pair.target + newOffset);
}
} else {
newJoin = joinFactory.createJoin(newInputs.get(0), newInputs.get(1),
newConditionExpr, join.getJoinType(), join.getVariablesStopped(),
join.isSemiJoinDone());
}
return new TrimResult(newJoin, mapping);
}
/**
* Variant of {@link #trimFields(RelNode, BitSet, Set)} for
* {@link SetOpRel} (including UNION and UNION ALL).
*/
public TrimResult trimFields(
SetOpRel setOp,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
final RelDataType rowType = setOp.getRowType();
final int fieldCount = rowType.getFieldCount();
int changeCount = 0;
// Fennel abhors an empty row type, so pretend that the parent rel
// wants the last field. (The last field is the least likely to be a
// system field.)
if (fieldsUsed.isEmpty()) {
fieldsUsed.set(rowType.getFieldCount() - 1);
}
// Compute the desired field mapping. Give the consumer the fields they
// want, in the order that they appear in the bitset.
final Mapping mapping = createMapping(fieldsUsed, fieldCount);
// Create input with trimmed columns.
final List<RelNode> newInputs = new ArrayList<RelNode>();
for (RelNode input : setOp.getInputs()) {
TrimResult trimResult =
trimChild(setOp, input, fieldsUsed, extraFields);
RelNode newInput = trimResult.left;
final Mapping inputMapping = trimResult.right;
// We want "mapping", the input gave us "inputMapping", compute
// "remaining" mapping.
// | | |
// |---------------- mapping ---------->|
// |-- inputMapping -->| |
// | |-- remaining -->|
//
// For instance, suppose we have columns [a, b, c, d],
// the consumer asked for mapping = [b, d],
// and the transformed input has columns inputMapping = [d, a, b].
// remaining will permute [b, d] to [d, a, b].
Mapping remaining = Mappings.divide(mapping, inputMapping);
// Create a projection; does nothing if remaining is identity.
newInput = RelOptUtil.projectMapping(newInput, remaining, null,
projectFactory);
if (input != newInput) {
++changeCount;
}
newInputs.add(newInput);
}
// If the input is unchanged, and we need to project all columns,
// there's to do.
if (changeCount == 0
&& mapping.isIdentity()) {
return new TrimResult(
setOp,
mapping);
}
RelNode newSetOp =
setOpFactory.createSetOp(setOp.kind, newInputs, setOp.all);
return new TrimResult(newSetOp, mapping);
}
/**
* Variant of {@link #trimFields(RelNode, BitSet, Set)} for
* {@link AggregateRel}.
*/
public TrimResult trimFields(
AggregateRelBase aggregate,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
// Fields:
//
// | sys fields | group fields | agg functions |
//
// Two kinds of trimming:
//
// 1. If agg rel has system fields but none of these are used, create an
// agg rel with no system fields.
//
// 2. If aggregate functions are not used, remove them.
//
// But grouping fields stay, even if they are not used.
final RelDataType rowType = aggregate.getRowType();
// Compute which input fields are used.
BitSet inputFieldsUsed = new BitSet();
// 1. group fields are always used
for (int i : BitSets.toIter(aggregate.getGroupSet())) {
inputFieldsUsed.set(i);
}
// 2. agg functions
for (AggregateCall aggCall : aggregate.getAggCallList()) {
for (int i : aggCall.getArgList()) {
inputFieldsUsed.set(i);
}
}
// Create input with trimmed columns.
final RelNode input = aggregate.getInput(0);
final Set<RelDataTypeField> inputExtraFields = Collections.emptySet();
final TrimResult trimResult =
trimChild(aggregate, input, inputFieldsUsed, inputExtraFields);
final RelNode newInput = trimResult.left;
final Mapping inputMapping = trimResult.right;
// If the input is unchanged, and we need to project all columns,
// there's nothing to do.
if (input == newInput
&& fieldsUsed.equals(BitSets.range(rowType.getFieldCount()))) {
return new TrimResult(
aggregate,
Mappings.createIdentity(rowType.getFieldCount()));
}
// Which agg calls are used by our consumer?
final int groupCount = aggregate.getGroupSet().cardinality();
int j = groupCount;
int usedAggCallCount = 0;
for (int i = 0; i < aggregate.getAggCallList().size(); i++) {
if (fieldsUsed.get(j++)) {
++usedAggCallCount;
}
}
// Offset due to the number of system fields having changed.
Mapping mapping =
Mappings.create(
MappingType.INVERSE_SURJECTION,
rowType.getFieldCount(),
groupCount
+ usedAggCallCount);
final BitSet newGroupSet =
Mappings.apply(inputMapping, aggregate.getGroupSet());
// Populate mapping of where to find the fields. System and grouping
// fields first.
for (IntPair pair : inputMapping) {
if (pair.source < groupCount) {
mapping.set(pair.source, pair.target);
}
}
// Now create new agg calls, and populate mapping for them.
final List<AggregateCall> newAggCallList =
new ArrayList<AggregateCall>();
j = groupCount;
for (AggregateCall aggCall : aggregate.getAggCallList()) {
if (fieldsUsed.get(j)) {
AggregateCall newAggCall =
aggCall.copy(Mappings.apply2(inputMapping, aggCall.getArgList()));
if (newAggCall.equals(aggCall)) {
newAggCall = aggCall; // immutable -> canonize to save space
}
mapping.set(
j,
groupCount
+ newAggCallList.size());
newAggCallList.add(newAggCall);
}
++j;
}
RelNode newAggregate = aggregateFactory.createAggregate(newInput,
newGroupSet, newAggCallList);
assert newAggregate.getClass() == aggregate.getClass();
return new TrimResult(newAggregate, mapping);
}
/**
* Variant of {@link #trimFields(RelNode, BitSet, Set)} for
* {@link TableModificationRel}.
*/
public TrimResult trimFields(
TableModificationRel modifier,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
// Ignore what consumer wants. We always project all columns.
Util.discard(fieldsUsed);
final RelDataType rowType = modifier.getRowType();
final int fieldCount = rowType.getFieldCount();
RelNode input = modifier.getChild();
// We want all fields from the child.
final int inputFieldCount = input.getRowType().getFieldCount();
BitSet inputFieldsUsed = BitSets.range(inputFieldCount);
// Create input with trimmed columns.
final Set<RelDataTypeField> inputExtraFields = Collections.emptySet();
TrimResult trimResult =
trimChild(modifier, input, inputFieldsUsed, inputExtraFields);
RelNode newInput = trimResult.left;
final Mapping inputMapping = trimResult.right;
if (!inputMapping.isIdentity()) {
// We asked for all fields. Can't believe that the child decided
// to permute them!
throw Util.newInternal(
"Expected identity mapping, got " + inputMapping);
}
TableModificationRel newModifier = modifier;
if (newInput != input) {
newModifier =
modifier.copy(
modifier.getTraitSet(),
Collections.singletonList(newInput));
}
assert newModifier.getClass() == modifier.getClass();
// Always project all fields.
Mapping mapping = Mappings.createIdentity(fieldCount);
return new TrimResult(newModifier, mapping);
}
/**
* Variant of {@link #trimFields(RelNode, BitSet, Set)} for
* {@link TableFunctionRel}.
*/
public TrimResult trimFields(
TableFunctionRel tabFun,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
final RelDataType rowType = tabFun.getRowType();
final int fieldCount = rowType.getFieldCount();
List<RelNode> newInputs = new ArrayList<RelNode>();
for (RelNode input : tabFun.getInputs()) {
final int inputFieldCount = input.getRowType().getFieldCount();
BitSet inputFieldsUsed = BitSets.range(inputFieldCount);
// Create input with trimmed columns.
final Set<RelDataTypeField> inputExtraFields =
Collections.emptySet();
TrimResult trimResult =
trimChildRestore(
tabFun, input, inputFieldsUsed, inputExtraFields);
assert trimResult.right.isIdentity();
newInputs.add(trimResult.left);
}
TableFunctionRel newTabFun = tabFun;
if (!tabFun.getInputs().equals(newInputs)) {
newTabFun = tabFun.copy(tabFun.getTraitSet(), newInputs);
}
assert newTabFun.getClass() == tabFun.getClass();
// Always project all fields.
Mapping mapping = Mappings.createIdentity(fieldCount);
return new TrimResult(newTabFun, mapping);
}
/**
* Variant of {@link #trimFields(RelNode, BitSet, Set)} for
* {@link org.eigenbase.rel.ValuesRel}.
*/
public TrimResult trimFields(
ValuesRel values,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
final RelDataType rowType = values.getRowType();
final int fieldCount = rowType.getFieldCount();
// If they are asking for no fields, we can't give them what they want,
// because zero-column records are illegal. Give them the last field,
// which is unlikely to be a system field.
if (fieldsUsed.isEmpty()) {
fieldsUsed = BitSets.range(fieldCount - 1, fieldCount);
}
// If all fields are used, return unchanged.
if (fieldsUsed.equals(BitSets.range(fieldCount))) {
Mapping mapping = Mappings.createIdentity(fieldCount);
return new TrimResult(values, mapping);
}
List<List<RexLiteral>> newTuples = new ArrayList<List<RexLiteral>>();
for (List<RexLiteral> tuple : values.getTuples()) {
List<RexLiteral> newTuple = new ArrayList<RexLiteral>();
for (int field : BitSets.toIter(fieldsUsed)) {
newTuple.add(tuple.get(field));
}
newTuples.add(newTuple);
}
final Mapping mapping = createMapping(fieldsUsed, fieldCount);
final RelDataType newRowType =
RelOptUtil.permute(values.getCluster().getTypeFactory(), rowType,
mapping);
final ValuesRel newValues =
new ValuesRel(values.getCluster(), newRowType, newTuples);
return new TrimResult(newValues, mapping);
}
private Mapping createMapping(BitSet fieldsUsed, int fieldCount) {
final Mapping mapping =
Mappings.create(
MappingType.INVERSE_SURJECTION,
fieldCount,
fieldsUsed.cardinality());
int i = 0;
for (int field : BitSets.toIter(fieldsUsed)) {
mapping.set(field, i++);
}
return mapping;
}
/**
* Variant of {@link #trimFields(RelNode, BitSet, Set)} for
* {@link org.eigenbase.rel.TableAccessRel}.
*/
public TrimResult trimFields(
final TableAccessRelBase tableAccessRel,
BitSet fieldsUsed,
Set<RelDataTypeField> extraFields) {
final int fieldCount = tableAccessRel.getRowType().getFieldCount();
if (fieldsUsed.equals(BitSets.range(fieldCount)) && extraFields.isEmpty()) {
// if there is nothing to project or if we are projecting everything
// then no need to introduce another RelNode
return trimFields(
(RelNode) tableAccessRel, fieldsUsed, extraFields);
}
final RelNode newTableAccessRel =
tableAccessRel.project(fieldsUsed, extraFields, this.projectFactory);
// Some parts of the system can't handle rows with zero fields, so
// pretend that one field is used.
if (fieldsUsed.cardinality() == 0) {
RelNode input = newTableAccessRel;
if (input instanceof ProjectRelBase) {
// The table has implemented the project in the obvious way - by
// creating project with 0 fields. Strip it away, and create our own
// project with one field.
ProjectRelBase project = (ProjectRelBase) input;
if (project.getRowType().getFieldCount() == 0) {
input = project.getChild();
}
}
return dummyProject(fieldCount, input);
}
final Mapping mapping = createMapping(fieldsUsed, fieldCount);
return new TrimResult(newTableAccessRel, mapping);
}
//~ Inner Classes ----------------------------------------------------------
/**
* Result of an attempt to trim columns from a relational expression.
*
* <p>The mapping describes where to find the columns wanted by the parent
* of the current relational expression.
*
* <p>The mapping is a
* {@link org.eigenbase.util.mapping.Mappings.SourceMapping}, which means
* that no column can be used more than once, and some columns are not used.
* {@code columnsUsed.getSource(i)} returns the source of the i'th output
* field.
*
* <p>For example, consider the mapping for a relational expression that
* has 4 output columns but only two are being used. The mapping
* {2 → 1, 3 → 0} would give the following behavior:</p>
*
* <ul>
* <li>columnsUsed.getSourceCount() returns 4
* <li>columnsUsed.getTargetCount() returns 2
* <li>columnsUsed.getSource(0) returns 3
* <li>columnsUsed.getSource(1) returns 2
* <li>columnsUsed.getSource(2) throws IndexOutOfBounds
* <li>columnsUsed.getTargetOpt(3) returns 0
* <li>columnsUsed.getTargetOpt(0) returns -1
* </ul>
*/
protected static class TrimResult extends Pair<RelNode, Mapping> {
/**
* Creates a TrimResult.
*
* @param left New relational expression
* @param right Mapping of fields onto original fields
*/
public TrimResult(RelNode left, Mapping right) {
super(left, right);
}
}
}
// End RelFieldTrimmer.java