operand(AggregateRelBase.class,
operand(FilterRelBase.class, any())));
}
public void onMatch(RelOptRuleCall call) {
final AggregateRelBase aggregate = call.rel(0);
final FilterRelBase filter = call.rel(1);
// Do the columns used by the filter appear in the output of the aggregate?
final BitSet filterColumns =
RelOptUtil.InputFinder.bits(filter.getCondition());
final BitSet newGroupSet =
BitSets.union(aggregate.getGroupSet(), filterColumns);
final RelNode input = filter.getChild();
final Boolean unique =
RelMetadataQuery.areColumnsUnique(input, newGroupSet);
if (unique != null && unique) {
// The input is already unique on the grouping columns, so there's little
// advantage of aggregating again. More important, without this check,
// the rule fires forever: A-F => A-F-A => A-A-F-A => A-A-A-F-A => ...
return;
}
final AggregateRelBase newAggregate =
aggregate.copy(aggregate.getTraitSet(), input, newGroupSet,
aggregate.getAggCallList());
final Mappings.TargetMapping mapping = Mappings.target(
new Function<Integer, Integer>() {
public Integer apply(Integer a0) {
return BitSets.toList(newGroupSet).indexOf(a0);
}
},
input.getRowType().getFieldCount(),
newGroupSet.cardinality());
final RexNode newCondition =
RexUtil.apply(mapping, filter.getCondition());
final FilterRelBase newFilter = filter.copy(filter.getTraitSet(),
newAggregate, newCondition);
if (BitSets.contains(aggregate.getGroupSet(), filterColumns)) {
// Everything needed by the filter is returned by the aggregate.
assert newGroupSet.equals(aggregate.getGroupSet());
call.transformTo(newFilter);
} else {
// The filter needs at least one extra column.
// Now aggregate it away.
final BitSet topGroupSet = new BitSet();
for (int c : BitSets.toIter(aggregate.getGroupSet())) {
topGroupSet.set(BitSets.toList(newGroupSet).indexOf(c));
}
final List<AggregateCall> topAggCallList = Lists.newArrayList();
int i = newGroupSet.cardinality();
for (AggregateCall aggregateCall : aggregate.getAggCallList()) {
final Aggregation rollup =
SubstitutionVisitor.getRollup(aggregateCall.getAggregation());
if (rollup == null) {
// This aggregate cannot be rolled up.
return;
}
if (aggregateCall.isDistinct()) {
// Cannot roll up distinct.
return;
}
topAggCallList.add(
new AggregateCall(rollup, aggregateCall.isDistinct(),
ImmutableList.of(i++), aggregateCall.type, aggregateCall.name));
}
final AggregateRelBase topAggregate =
aggregate.copy(aggregate.getTraitSet(), newFilter, topGroupSet,
topAggCallList);
call.transformTo(topAggregate);
}
}