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