|
23 | 23 | import com.apple.foundationdb.annotation.API;
|
24 | 24 | import com.apple.foundationdb.record.RecordCoreException;
|
25 | 25 | import com.apple.foundationdb.record.query.combinatorics.ChooseK;
|
26 |
| -import com.apple.foundationdb.record.query.plan.cascades.AggregateIndexMatchCandidate; |
27 | 26 | import com.apple.foundationdb.record.query.plan.cascades.CascadesPlanner;
|
28 | 27 | import com.apple.foundationdb.record.query.plan.cascades.CascadesRule;
|
29 | 28 | import com.apple.foundationdb.record.query.plan.cascades.CascadesRuleCall;
|
|
43 | 42 | import com.apple.foundationdb.record.query.plan.cascades.PlanContext;
|
44 | 43 | import com.apple.foundationdb.record.query.plan.cascades.PrimaryScanMatchCandidate;
|
45 | 44 | import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
|
| 45 | +import com.apple.foundationdb.record.query.plan.cascades.Quantifiers; |
46 | 46 | import com.apple.foundationdb.record.query.plan.cascades.Reference;
|
47 | 47 | import com.apple.foundationdb.record.query.plan.cascades.ReferencedFieldsConstraint;
|
48 | 48 | import com.apple.foundationdb.record.query.plan.cascades.RequestedOrdering;
|
49 | 49 | import com.apple.foundationdb.record.query.plan.cascades.RequestedOrderingConstraint;
|
50 | 50 | import com.apple.foundationdb.record.query.plan.cascades.ValueIndexScanMatchCandidate;
|
51 |
| -import com.apple.foundationdb.record.query.plan.cascades.WithPrimaryKeyMatchCandidate; |
52 | 51 | import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
|
53 | 52 | import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalDistinctExpression;
|
54 | 53 | import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalIntersectionExpression;
|
|
60 | 59 | import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan;
|
61 | 60 | import com.apple.foundationdb.record.query.plan.plans.RecordQueryUnorderedPrimaryKeyDistinctPlan;
|
62 | 61 | import com.apple.foundationdb.record.util.pair.NonnullPair;
|
| 62 | +import com.apple.foundationdb.record.util.pair.Pair; |
63 | 63 | import com.google.common.base.Suppliers;
|
64 | 64 | import com.google.common.base.Verify;
|
65 | 65 | import com.google.common.collect.ImmutableList;
|
66 | 66 | import com.google.common.collect.ImmutableMap;
|
67 | 67 | import com.google.common.collect.ImmutableSet;
|
68 | 68 | import com.google.common.collect.ImmutableSetMultimap;
|
| 69 | +import com.google.common.collect.Iterables; |
69 | 70 | import com.google.common.collect.Lists;
|
70 | 71 | import com.google.common.collect.Maps;
|
71 | 72 | import org.slf4j.Logger;
|
|
77 | 78 | import java.util.BitSet;
|
78 | 79 | import java.util.Collection;
|
79 | 80 | import java.util.Comparator;
|
| 81 | +import java.util.HashMap; |
| 82 | +import java.util.LinkedHashMap; |
80 | 83 | import java.util.LinkedHashSet;
|
81 | 84 | import java.util.List;
|
82 | 85 | import java.util.Map;
|
|
85 | 88 | import java.util.Set;
|
86 | 89 | import java.util.function.Function;
|
87 | 90 | import java.util.function.Supplier;
|
| 91 | +import java.util.stream.Collectors; |
| 92 | +import java.util.stream.Stream; |
88 | 93 |
|
89 | 94 | import static com.apple.foundationdb.record.query.plan.cascades.properties.CardinalitiesProperty.cardinalities;
|
90 | 95 |
|
@@ -128,6 +133,97 @@ protected BindingMatcher<R> getExpressionMatcher() {
|
128 | 133 | return expressionMatcher;
|
129 | 134 | }
|
130 | 135 |
|
| 136 | + @Override |
| 137 | + public void onMatch(@Nonnull final CascadesRuleCall call) { |
| 138 | + final var bindings = call.getBindings(); |
| 139 | + final var completeMatches = bindings.getAll(getCompleteMatchMatcher()); |
| 140 | + if (completeMatches.isEmpty()) { |
| 141 | + return; |
| 142 | + } |
| 143 | + |
| 144 | + final var expression = bindings.get(getExpressionMatcher()); |
| 145 | + |
| 146 | + // |
| 147 | + // return if there is no pre-determined interesting ordering |
| 148 | + // |
| 149 | + final var requestedOrderingsOptional = call.getPlannerConstraint(RequestedOrderingConstraint.REQUESTED_ORDERING); |
| 150 | + if (requestedOrderingsOptional.isEmpty()) { |
| 151 | + return; |
| 152 | + } |
| 153 | + |
| 154 | + final var requestedOrderings = requestedOrderingsOptional.get(); |
| 155 | + final var aliasToQuantifierMap = Quantifiers.aliasToQuantifierMap(expression.getQuantifiers()); |
| 156 | + final var aliases = aliasToQuantifierMap.keySet(); |
| 157 | + |
| 158 | + // group all successful matches by their sets of compensated aliases |
| 159 | + final var matchPartitionByMatchAliasMap = |
| 160 | + completeMatches |
| 161 | + .stream() |
| 162 | + .flatMap(match -> { |
| 163 | + final var compensatedAliases = match.getCompensatedAliases(); |
| 164 | + if (!compensatedAliases.containsAll(aliases)) { |
| 165 | + return Stream.empty(); |
| 166 | + } |
| 167 | + final Set<CorrelationIdentifier> matchedForEachAliases = |
| 168 | + compensatedAliases.stream() |
| 169 | + .filter(matchedAlias -> |
| 170 | + Objects.requireNonNull(aliasToQuantifierMap.get(matchedAlias)) instanceof Quantifier.ForEach) |
| 171 | + .collect(ImmutableSet.toImmutableSet()); |
| 172 | + if (matchedForEachAliases.size() == 1) { |
| 173 | + return Stream.of(NonnullPair.of(Iterables.getOnlyElement(matchedForEachAliases), match)); |
| 174 | + } |
| 175 | + return Stream.empty(); |
| 176 | + }) |
| 177 | + .collect(Collectors.groupingBy( |
| 178 | + Pair::getLeft, |
| 179 | + LinkedHashMap::new, |
| 180 | + Collectors.mapping(Pair::getRight, ImmutableList.toImmutableList()))); |
| 181 | + |
| 182 | + // loop through all compensated alias sets and their associated match partitions |
| 183 | + for (final var matchPartitionByMatchAliasEntry : matchPartitionByMatchAliasMap.entrySet()) { |
| 184 | + final var matchPartitionForMatchedAlias = |
| 185 | + matchPartitionByMatchAliasEntry.getValue(); |
| 186 | + |
| 187 | + // |
| 188 | + // We do know that local predicates (which includes predicates only using the matchedAlias quantifier) |
| 189 | + // are definitely handled by the logic expressed by the partial matches of the current match partition. |
| 190 | + // Join predicates are different in a sense that there will be matches that handle those predicates and |
| 191 | + // there will be matches where these predicates will not be handled. We further need to sub-partition the |
| 192 | + // current match partition, by the predicates that are being handled by the matches. |
| 193 | + // |
| 194 | + // TODO this should just be exactly one key |
| 195 | + final var matchPartitionsForAliasesByPredicates = |
| 196 | + matchPartitionForMatchedAlias |
| 197 | + .stream() |
| 198 | + .collect(Collectors.groupingBy(match -> |
| 199 | + new LinkedIdentitySet<>(match.getRegularMatchInfo().getPredicateMap().keySet()), |
| 200 | + HashMap::new, |
| 201 | + ImmutableList.toImmutableList())); |
| 202 | + |
| 203 | + // |
| 204 | + // Note that this works because there is only one for-each and potentially 0 - n existential quantifiers |
| 205 | + // that are covered by the match partition. Even though that logically forms a join, the existential |
| 206 | + // quantifiers do not mutate the result of the join, they only cause filtering, that is, the resulting |
| 207 | + // record is exactly what the for each quantifier produced filtered by the predicates expressed on the |
| 208 | + // existential quantifiers. |
| 209 | + // |
| 210 | + for (final var matchPartitionEntry : matchPartitionsForAliasesByPredicates.entrySet()) { |
| 211 | + final var matchPartition = matchPartitionEntry.getValue(); |
| 212 | + |
| 213 | + // |
| 214 | + // The current match partition covers all matches that match the aliases in matchedAliases |
| 215 | + // as well as all predicates in matchedPredicates. In other words we now have to compensate |
| 216 | + // for all the remaining quantifiers and all remaining predicates. |
| 217 | + // |
| 218 | + final var dataAccessExpressions = |
| 219 | + dataAccessForMatchPartition(call, |
| 220 | + requestedOrderings, |
| 221 | + matchPartition); |
| 222 | + call.yieldExpression(dataAccessExpressions); |
| 223 | + } |
| 224 | + } |
| 225 | + } |
| 226 | + |
131 | 227 | /**
|
132 | 228 | * Method that does the leg work to create the appropriate expression dag for data access using value indexes or
|
133 | 229 | * value index-like scans (primary scans).
|
@@ -418,44 +514,6 @@ protected Set<? extends RelationalExpression> dataAccessForMatchPartition(@Nonnu
|
418 | 514 | return intersectionInfoMapToExpressions(intersectionInfoMap);
|
419 | 515 | }
|
420 | 516 |
|
421 |
| - @Nonnull |
422 |
| - protected static Optional<List<Value>> commonRecordKeyValuesMaybe(@Nonnull Iterable<? extends PartialMatch> partialMatches) { |
423 |
| - List<Value> common = null; |
424 |
| - var first = true; |
425 |
| - for (final var partialMatch : partialMatches) { |
426 |
| - final var matchCandidate = partialMatch.getMatchCandidate(); |
427 |
| - final var regularMatchInfo = partialMatch.getRegularMatchInfo(); |
428 |
| - |
429 |
| - final List<Value> key; |
430 |
| - if (matchCandidate instanceof WithPrimaryKeyMatchCandidate) { |
431 |
| - final var withPrimaryKeyMatchCandidate = (WithPrimaryKeyMatchCandidate)matchCandidate; |
432 |
| - final var keyMaybe = withPrimaryKeyMatchCandidate.getPrimaryKeyValuesMaybe(); |
433 |
| - if (keyMaybe.isEmpty()) { |
434 |
| - return Optional.empty(); |
435 |
| - } |
436 |
| - key = keyMaybe.get(); |
437 |
| - } else if (matchCandidate instanceof AggregateIndexMatchCandidate) { |
438 |
| - final var aggregateIndexMatchCandidate = (AggregateIndexMatchCandidate)matchCandidate; |
439 |
| - final var rollUpToGroupingValues = regularMatchInfo.getRollUpToGroupingValues(); |
440 |
| - if (rollUpToGroupingValues == null) { |
441 |
| - key = aggregateIndexMatchCandidate.getGroupingAndAggregateAccessors(Quantifier.current()).getLeft(); |
442 |
| - } else { |
443 |
| - key = aggregateIndexMatchCandidate.getGroupingAndAggregateAccessors(rollUpToGroupingValues.size(), |
444 |
| - Quantifier.current()).getLeft(); |
445 |
| - } |
446 |
| - } else { |
447 |
| - return Optional.empty(); |
448 |
| - } |
449 |
| - if (first) { |
450 |
| - common = key; |
451 |
| - first = false; |
452 |
| - } else if (!common.equals(key)) { |
453 |
| - return Optional.empty(); |
454 |
| - } |
455 |
| - } |
456 |
| - return Optional.ofNullable(common); // common can only be null if we didn't have any match candidates to start with |
457 |
| - } |
458 |
| - |
459 | 517 | @Nonnull
|
460 | 518 | private static BitSet[] newSquareBitMatrix(final int size) {
|
461 | 519 | BitSet[] matrix = new BitSet[size];
|
@@ -1032,14 +1090,14 @@ private static Ordering orderingFromOrderingParts(final @Nonnull List<MatchedOrd
|
1032 | 1090 | * Private helper method to verify that a list of {@link Value}s can be used as a comparison key.
|
1033 | 1091 | * ordering information coming from a match in form of a list of {@link Value}s.
|
1034 | 1092 | * @param comparisonKeyValues a list of {@link Value}s
|
1035 |
| - * @param commonPrimaryKeyValues common primary key |
| 1093 | + * @param commonRecordKeyValues common primary key |
1036 | 1094 | * @param equalityBoundKeyValues a set of equality-bound key parts
|
1037 | 1095 | * @return a boolean that indicates if the list of values passed in can be used as comparison key
|
1038 | 1096 | */
|
1039 | 1097 | protected static boolean isCompatibleComparisonKey(@Nonnull Collection<Value> comparisonKeyValues,
|
1040 |
| - @Nonnull List<Value> commonPrimaryKeyValues, |
| 1098 | + @Nonnull List<Value> commonRecordKeyValues, |
1041 | 1099 | @Nonnull ImmutableSet<Value> equalityBoundKeyValues) {
|
1042 |
| - return commonPrimaryKeyValues |
| 1100 | + return commonRecordKeyValues |
1043 | 1101 | .stream()
|
1044 | 1102 | .filter(commonPrimaryKeyValue -> !equalityBoundKeyValues.contains(commonPrimaryKeyValue))
|
1045 | 1103 | .allMatch(comparisonKeyValues::contains);
|
|
0 commit comments