Skip to content

Commit e409db3

Browse files
authored
Composite aggregates (#3266)
This PR adds the ability to plan arbitrary aggregation queries using aggregation indexes as well as intersections of aggregation indexes. It also corrects/adds the infrastructure to deal with complicated order-by requirements. - refactor data access rules into a data access rule for value indexes and one for aggregation indexes - have both of these data access rule implementations share the maximum code (only intersection planning is different) - intersection planning for aggregation indexes - because of the same reasoning as regular index intersections AND in order to answer the query as these indexes carry data that is not part of the underlying table (the aggregation) - rollups of finer-granularity index scans if such a roll-up can be useful in answering the query - order by requirements are treated in the same framework as all other kinds of expressions (using M3 maps, translations, etc.)
1 parent d203a30 commit e409db3

File tree

230 files changed

+14152
-11226
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

230 files changed

+14152
-11226
lines changed

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/combinatorics/EnumeratingIterable.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ static <T> EnumeratingIterable<T> singleIterable(@Nonnull final T singleElement)
4444
return new SingleIterable<>(singleElement);
4545
}
4646

47+
static <T> EnumeratingIterable<T> emptyOnEmptyIterable() {
48+
return new SingleIterable<>();
49+
}
50+
4751
/**
4852
* An implementation of {@link EnumeratingIterable} that is optimized to work for empty
4953
* input sets.
@@ -84,13 +88,17 @@ public EnumeratingIterator<T> iterator() {
8488
* @param <T> type
8589
*/
8690
class SingleIterable<T> implements EnumeratingIterable<T> {
87-
@Nonnull
91+
@Nullable
8892
private final T singleElement;
8993

9094
private SingleIterable(@Nonnull final T singleElement) {
9195
this.singleElement = singleElement;
9296
}
9397

98+
private SingleIterable() {
99+
this.singleElement = null;
100+
}
101+
94102
@Nonnull
95103
@Override
96104
public EnumeratingIterator<T> iterator() {
@@ -103,12 +111,12 @@ public EnumeratingIterator<T> iterator() {
103111
* @param <T> type of the element
104112
*/
105113
class SingleIterator<T> extends AbstractIterator<List<T>> implements EnumeratingIterator<T> {
106-
@Nonnull
114+
@Nullable
107115
private final T singleElement;
108116

109117
boolean atFirst = true;
110118

111-
private SingleIterator(@Nonnull final T singleElement) {
119+
private SingleIterator(@Nullable final T singleElement) {
112120
this.singleElement = singleElement;
113121
}
114122

@@ -128,6 +136,9 @@ protected List<T> computeNext() {
128136

129137
atFirst = false;
130138

139+
if (singleElement == null) {
140+
return ImmutableList.of();
141+
}
131142
return ImmutableList.of(singleElement);
132143
}
133144
}

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/combinatorics/TopologicalSort.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -537,10 +537,6 @@ public static <T, P> Iterable<List<T>> satisfyingPermutations(@Nonnull final Par
537537
@Nonnull final List<P> targetPermutation,
538538
@Nonnull final Function<T, P> domainMapper,
539539
@Nonnull final Function<List<T>, Integer> satisfiabilityFunction) {
540-
if (partiallyOrderedSet.isEmpty()) {
541-
return ImmutableList.of();
542-
}
543-
544540
if (partiallyOrderedSet.size() < targetPermutation.size()) {
545541
return ImmutableList.of();
546542
}
@@ -565,9 +561,11 @@ protected Iterator<T> domain(final int t) {
565561
}
566562
}
567563
};
568-
} else {
569-
Verify.verify(partiallyOrderedSet.size() == 1);
564+
} else if (partiallyOrderedSet.size() == 1) {
570565
enumeratingIterator = EnumeratingIterable.singleIterable(Iterables.getOnlyElement(partiallyOrderedSet.getSet())).iterator();
566+
} else {
567+
Verify.verify(partiallyOrderedSet.isEmpty());
568+
enumeratingIterator = EnumeratingIterable.<T>emptyOnEmptyIterable().iterator();
571569
}
572570

573571
return () -> new AbstractIterator<>() {

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/QueryPlanConstraint.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ public QueryPredicate getPredicate() {
6262
return predicate;
6363
}
6464

65+
public boolean isTautology() {
66+
return predicate.isTautology();
67+
}
68+
6569
public boolean isConstrained() {
6670
return !getPredicate().isTautology();
6771
}

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/AggregateIndexExpansionVisitor.java

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
3535
import com.apple.foundationdb.record.query.plan.cascades.predicates.Placeholder;
3636
import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValueAndRanges;
37+
import com.apple.foundationdb.record.query.plan.cascades.values.AggregateValue;
3738
import com.apple.foundationdb.record.query.plan.cascades.values.ArithmeticValue;
3839
import com.apple.foundationdb.record.query.plan.cascades.values.CountValue;
3940
import com.apple.foundationdb.record.query.plan.cascades.values.EmptyValue;
@@ -57,10 +58,10 @@
5758
import javax.annotation.Nonnull;
5859
import javax.annotation.Nullable;
5960
import java.util.Collection;
60-
import java.util.Collections;
6161
import java.util.List;
6262
import java.util.Map;
6363
import java.util.Objects;
64+
import java.util.Optional;
6465
import java.util.function.Supplier;
6566
import java.util.stream.Stream;
6667

@@ -72,7 +73,12 @@
7273
public class AggregateIndexExpansionVisitor extends KeyExpressionExpansionVisitor
7374
implements ExpansionVisitor<KeyExpressionExpansionVisitor.VisitorState> {
7475
@Nonnull
75-
static final Supplier<Map<String, BuiltInFunction<? extends Value>>> aggregateMap = Suppliers.memoize(AggregateIndexExpansionVisitor::computeAggregateMap);
76+
static final Supplier<Map<String, BuiltInFunction<? extends Value>>> aggregateMap =
77+
Suppliers.memoize(AggregateIndexExpansionVisitor::computeAggregateMap);
78+
79+
@Nonnull
80+
static final Supplier<Map<String, BuiltInFunction<? extends Value>>> rollUpAggregateMap =
81+
Suppliers.memoize(AggregateIndexExpansionVisitor::computeRollUpAggregateMap);
7682

7783
@Nonnull
7884
protected final Index index;
@@ -92,7 +98,8 @@ public class AggregateIndexExpansionVisitor extends KeyExpressionExpansionVisito
9298
* @param recordTypes The indexed record types.
9399
*/
94100
public AggregateIndexExpansionVisitor(@Nonnull final Index index, @Nonnull final Collection<RecordType> recordTypes) {
95-
Preconditions.checkArgument(IndexTypes.BITMAP_VALUE.equals(index.getType()) || aggregateMap.get().containsKey(index.getType()));
101+
Preconditions.checkArgument(IndexTypes.BITMAP_VALUE.equals(index.getType()) ||
102+
aggregateMap.get().containsKey(index.getType()));
96103
Preconditions.checkArgument(index.getRootExpression() instanceof GroupingKeyExpression);
97104
this.index = index;
98105
this.groupingKeyExpression = ((GroupingKeyExpression)index.getRootExpression());
@@ -139,9 +146,9 @@ public MatchCandidate expand(@Nonnull final Supplier<Quantifier.ForEach> baseQua
139146
.addAll(groupByPlaceholders).build();
140147

141148
// 3. construct SELECT-HAVING with SORT on top.
142-
final var selectHavingAndPlaceholderAliases = constructSelectHaving(groupByQun, placeholders);
143-
final var selectHaving = selectHavingAndPlaceholderAliases.getLeft();
144-
final var placeHolderAliases = selectHavingAndPlaceholderAliases.getRight();
149+
final var constructSelectHavingResult = constructSelectHaving(groupByQun, placeholders);
150+
final var selectHaving = constructSelectHavingResult.getSelectExpression();
151+
final var placeHolderAliases = constructSelectHavingResult.getPlaceholderAliases();
145152

146153
// 4. add sort on top, if necessary, this will be absorbed later on as an ordering property of the match candidate.
147154
final var maybeWithSort = placeHolderAliases.isEmpty()
@@ -243,7 +250,8 @@ protected NonnullPair<Quantifier, List<Placeholder>> constructGroupBy(@Nonnull f
243250
throw new RecordCoreException("unable to plan group by with non-field value")
244251
.addLogInfo(LogMessageKeys.VALUE, groupedValue);
245252
}
246-
final var aggregateValue = (Value)aggregateMap.get().get(index.getType()).encapsulate(ImmutableList.of(argument));
253+
final var aggregateValue =
254+
aggregateValue(index, argument).orElseThrow(() -> new RecordCoreException("unknown aggregation type"));
247255
// add an RCV column representing the grouping columns as the first result set column
248256
// also, make sure to set the field type names correctly for each field value in the grouping keys RCV.
249257

@@ -278,19 +286,25 @@ protected NonnullPair<Quantifier, List<Placeholder>> constructGroupBy(@Nonnull f
278286
}
279287

280288
@Nonnull
281-
private NonnullPair<SelectExpression, List<CorrelationIdentifier>> constructSelectHaving(@Nonnull final Quantifier groupByQun,
282-
@Nonnull final List<Placeholder> selectWherePlaceholders) {
289+
private ConstructSelectHavingResult constructSelectHaving(@Nonnull final Quantifier groupByQun,
290+
@Nonnull final List<Placeholder> selectWherePlaceholders) {
291+
final var rangesOverExpression = groupByQun.getRangesOver().get();
292+
Verify.verify(rangesOverExpression instanceof GroupByExpression);
293+
final var groupByExpression = (GroupByExpression)rangesOverExpression;
294+
283295
// the grouping value in GroupByExpression comes first (if set).
284296
@Nullable final var groupingValueReference =
285-
(groupByQun.getRangesOver().get() instanceof GroupByExpression && ((GroupByExpression)groupByQun.getRangesOver().get()).getGroupingValue() == null)
286-
? null
287-
: FieldValue.ofOrdinalNumber(groupByQun.getFlowedObjectValue(), 0);
297+
groupByExpression.getGroupingValue() == null
298+
? null : FieldValue.ofOrdinalNumber(groupByQun.getFlowedObjectValue(), 0);
288299

289-
final var aggregateValueReference = FieldValue.ofOrdinalNumberAndFuseIfPossible(FieldValue.ofOrdinalNumber(groupByQun.getFlowedObjectValue(), groupingValueReference == null ? 0 : 1), 0);
300+
final var aggregateValueReference =
301+
FieldValue.ofOrdinalNumberAndFuseIfPossible(FieldValue.ofOrdinalNumber(groupByQun.getFlowedObjectValue(),
302+
groupingValueReference == null ? 0 : 1), 0);
290303

291304
final var placeholderAliases = ImmutableList.<CorrelationIdentifier>builder();
292305
final var selectHavingGraphExpansionBuilder = GraphExpansion.builder().addQuantifier(groupByQun);
293-
final List<Value> groupingValues = groupingValueReference == null ? Collections.emptyList() : Values.deconstructRecord(groupingValueReference);
306+
final List<Value> groupingValues = groupingValueReference == null
307+
? ImmutableList.of() : Values.deconstructRecord(groupingValueReference);
294308
if (groupingValueReference != null) {
295309
int i = 0;
296310
for (final var groupingValue : groupingValues) {
@@ -322,7 +336,15 @@ private NonnullPair<SelectExpression, List<CorrelationIdentifier>> constructSele
322336
} else {
323337
finalPlaceholders = placeholderAliases.build();
324338
}
325-
return NonnullPair.of(selectHavingGraphExpansionBuilder.build().buildSelect(), finalPlaceholders);
339+
340+
return new ConstructSelectHavingResult(selectHavingGraphExpansionBuilder.build().buildSelect(),
341+
finalPlaceholders);
342+
}
343+
344+
@Nonnull
345+
public static Optional<AggregateValue> aggregateValue(@Nonnull final Index index, @Nonnull final Value argument) {
346+
return Optional.of((AggregateValue)aggregateMap.get()
347+
.get(index.getType()).encapsulate(ImmutableList.of(argument)));
326348
}
327349

328350
@Nonnull
@@ -339,4 +361,53 @@ private static Map<String, BuiltInFunction<? extends Value>> computeAggregateMap
339361
mapBuilder.put(IndexTypes.PERMUTED_MIN, new NumericAggregationValue.MinFn());
340362
return mapBuilder.build();
341363
}
364+
365+
public static boolean canBeRolledUp(@Nonnull final String indexType) {
366+
return rollUpAggregateMap.get().containsKey(indexType);
367+
}
368+
369+
@Nonnull
370+
public static Optional<AggregateValue> rollUpAggregateValueMaybe(@Nonnull final String indexType, @Nonnull final Value argument) {
371+
return Optional.ofNullable(rollUpAggregateMap.get()
372+
.get(indexType))
373+
.map(fn -> (AggregateValue)fn.encapsulate(ImmutableList.of(argument)));
374+
}
375+
376+
@Nonnull
377+
private static Map<String, BuiltInFunction<? extends Value>> computeRollUpAggregateMap() {
378+
final ImmutableMap.Builder<String, BuiltInFunction<? extends Value>> mapBuilder = ImmutableMap.builder();
379+
mapBuilder.put(IndexTypes.MAX_EVER_LONG, new NumericAggregationValue.MaxFn());
380+
mapBuilder.put(IndexTypes.MIN_EVER_LONG, new NumericAggregationValue.MinFn());
381+
mapBuilder.put(IndexTypes.MAX_EVER_TUPLE, new NumericAggregationValue.MaxFn());
382+
mapBuilder.put(IndexTypes.MIN_EVER_TUPLE, new NumericAggregationValue.MinFn());
383+
mapBuilder.put(IndexTypes.SUM, new NumericAggregationValue.SumFn());
384+
mapBuilder.put(IndexTypes.COUNT, new NumericAggregationValue.SumFn());
385+
mapBuilder.put(IndexTypes.COUNT_NOT_NULL, new NumericAggregationValue.SumFn());
386+
mapBuilder.put(IndexTypes.PERMUTED_MAX, new NumericAggregationValue.MaxFn());
387+
mapBuilder.put(IndexTypes.PERMUTED_MIN, new NumericAggregationValue.MinFn());
388+
return mapBuilder.build();
389+
}
390+
391+
private static class ConstructSelectHavingResult {
392+
@Nonnull
393+
private final SelectExpression selectExpression;
394+
@Nonnull
395+
private final List<CorrelationIdentifier> placeholderAliases;
396+
397+
private ConstructSelectHavingResult(@Nonnull final SelectExpression selectExpression,
398+
@Nonnull final List<CorrelationIdentifier> placeholderAliases) {
399+
this.selectExpression = selectExpression;
400+
this.placeholderAliases = placeholderAliases;
401+
}
402+
403+
@Nonnull
404+
public SelectExpression getSelectExpression() {
405+
return selectExpression;
406+
}
407+
408+
@Nonnull
409+
public List<CorrelationIdentifier> getPlaceholderAliases() {
410+
return placeholderAliases;
411+
}
412+
}
342413
}

0 commit comments

Comments
 (0)