use of org.apache.calcite.sql.SqlKind.UNION in project calcite by apache.
the class RelBuilder method rewriteAggregateWithDuplicateGroupSets.
/**
* The {@code GROUP_ID()} function is used to distinguish duplicate groups.
* However, as Aggregate normalizes group sets to canonical form (i.e.,
* flatten, sorting, redundancy removal), this information is lost in RelNode.
* Therefore, it is impossible to implement the function in runtime.
*
* <p>To fill this gap, an aggregation query that contains duplicate group
* sets is rewritten into a Union of Aggregate operators whose group sets are
* distinct. The number of inputs to the Union is equal to the maximum number
* of duplicates. In the {@code N}th input to the Union, calls to the
* {@code GROUP_ID} aggregate function are replaced by the integer literal
* {@code N}.
*
* <p>This method also handles the case where group sets are distinct but
* there is a call to {@code GROUP_ID}. That call is replaced by the integer
* literal {@code 0}.
*
* <p>Also see the discussion in
* <a href="https://issues.apache.org/jira/browse/CALCITE-1824">[CALCITE-1824]
* GROUP_ID returns wrong result</a> and
* <a href="https://issues.apache.org/jira/browse/CALCITE-4748">[CALCITE-4748]
* If there are duplicate GROUPING SETS, Calcite should return duplicate
* rows</a>.
*/
private RelBuilder rewriteAggregateWithDuplicateGroupSets(ImmutableBitSet groupSet, ImmutableSortedMultiset<ImmutableBitSet> groupSets, List<AggCall> aggregateCalls) {
final List<String> fieldNamesIfNoRewrite = Aggregate.deriveRowType(getTypeFactory(), peek().getRowType(), false, groupSet, groupSets.asList(), aggregateCalls.stream().map(c -> ((AggCallPlus) c).aggregateCall()).collect(Util.toImmutableList())).getFieldNames();
// If n duplicates exist for a particular grouping, the {@code GROUP_ID()}
// function produces values in the range 0 to n-1. For each value,
// we need to figure out the corresponding group sets.
//
// For example, "... GROUPING SETS (a, a, b, c, c, c, c)"
// (i) The max value of the GROUP_ID() function returns is 3
// (ii) GROUPING SETS (a, b, c) produces value 0,
// GROUPING SETS (a, c) produces value 1,
// GROUPING SETS (c) produces value 2
// GROUPING SETS (c) produces value 3
final Map<Integer, Set<ImmutableBitSet>> groupIdToGroupSets = new HashMap<>();
int maxGroupId = 0;
for (Multiset.Entry<ImmutableBitSet> entry : groupSets.entrySet()) {
int groupId = entry.getCount() - 1;
if (groupId > maxGroupId) {
maxGroupId = groupId;
}
for (int i = 0; i <= groupId; i++) {
groupIdToGroupSets.computeIfAbsent(i, k -> Sets.newTreeSet(ImmutableBitSet.COMPARATOR)).add(entry.getElement());
}
}
// AggregateCall list without GROUP_ID function
final List<AggCall> aggregateCallsWithoutGroupId = new ArrayList<>(aggregateCalls);
aggregateCallsWithoutGroupId.removeIf(RelBuilder::isGroupId);
// For each group id value, we first construct an Aggregate without
// GROUP_ID() function call, and then create a Project node on top of it.
// The Project adds literal value for group id in right position.
final Frame frame = stack.pop();
for (int groupId = 0; groupId <= maxGroupId; groupId++) {
// Create the Aggregate node without GROUP_ID() call
stack.push(frame);
aggregate(groupKey(groupSet, castNonNull(groupIdToGroupSets.get(groupId))), aggregateCallsWithoutGroupId);
final List<RexNode> selectList = new ArrayList<>();
final int groupExprLength = groupSet.cardinality();
// Project fields in group by expressions
for (int i = 0; i < groupExprLength; i++) {
selectList.add(field(i));
}
// Project fields in aggregate calls
int groupIdCount = 0;
for (int i = 0; i < aggregateCalls.size(); i++) {
if (isGroupId(aggregateCalls.get(i))) {
selectList.add(getRexBuilder().makeExactLiteral(BigDecimal.valueOf(groupId), getTypeFactory().createSqlType(SqlTypeName.BIGINT)));
groupIdCount++;
} else {
selectList.add(field(groupExprLength + i - groupIdCount));
}
}
project(selectList, fieldNamesIfNoRewrite);
}
return union(true, maxGroupId + 1);
}
use of org.apache.calcite.sql.SqlKind.UNION in project calcite by apache.
the class RelBuilder method setOp.
private RelBuilder setOp(boolean all, SqlKind kind, int n) {
List<RelNode> inputs = new ArrayList<>();
for (int i = 0; i < n; i++) {
inputs.add(0, build());
}
switch(kind) {
case UNION:
case INTERSECT:
case EXCEPT:
if (n < 1) {
throw new IllegalArgumentException("bad INTERSECT/UNION/EXCEPT input count");
}
break;
default:
throw new AssertionError("bad setOp " + kind);
}
if (n == 1) {
return push(inputs.get(0));
}
if (config.simplifyValues() && kind == UNION && inputs.stream().allMatch(r -> r instanceof Values)) {
List<RelDataType> inputTypes = Util.transform(inputs, RelNode::getRowType);
RelDataType rowType = getTypeFactory().leastRestrictive(inputTypes);
requireNonNull(rowType, () -> "leastRestrictive(" + inputTypes + ")");
final List<List<RexLiteral>> tuples = new ArrayList<>();
for (RelNode input : inputs) {
tuples.addAll(((Values) input).tuples);
}
final List<List<RexLiteral>> tuples2 = all ? tuples : Util.distinctList(tuples);
return values(tuples2, rowType);
}
return push(struct.setOpFactory.createSetOp(kind, inputs, all));
}
Aggregations