use of com.apple.foundationdb.record.query.plan.temp.PlannerRuleCall in project fdb-record-layer by FoundationDB.
the class AbstractDataAccessRule method onMatch.
/**
* Method that does the leg work to create the appropriate expression dag for data access using value indexes or
* value index-like scans (primary scans).
*
* Conceptually we do the following work:
*
* <ul>
* <li> This method yields a scan plan for each matching primary candidate ({@link PrimaryScanMatchCandidate}).
* There is only ever going to be exactly one {@link PrimaryScanMatchCandidate} for a primary key. Due to the
* candidate being solely based upon a primary key, the match structure is somewhat limited. In essence, there
* is an implicit guarantee that we can always create a primary scan for a data source.
* </li>
* <li> This method yields an index scan plan for each matching value index candidate
* ({@link ValueIndexScanMatchCandidate}).
* </li>
* <li> This method yields the combinatorial expansion of intersections of distinct-ed index scan plans.
* </li>
* </ul>
*
* The work described above is semantically correct in a sense that it creates a search space that can be explored
* and pruned in suitable ways that will eventually converge into an optimal data access plan.
*
* We can choose to create an index scan for every index that is available regardless what the coverage
* of an index is. The coverage of an index is a measurement that tells us how well an index can answer what a
* filter (or by extension a query) asks for. For instance, a high number of search arguments used in the index scan
* can be associated with high coverage (as in the index scan covers more of the query) and vice versa.
*
* Similarly, we can choose to create the intersection of all possible combinations of suitable scans over indexes
* (that we have matches for). Since we create a logical intersection of these access plans we can leave it up to
* the respective implementation rules (e.g., {@link ImplementIntersectionRule}) to do the right thing and implement
* the physical plan for the intersection if possible (e.g. ensuring compatibly ordered legs, etc.).
*
* In fact, the two before-mentioned approaches are completely valid with respect to correctness of the plan and
* the guaranteed creation of the optimal plan. However, in reality using this approach, although valid and probably
* the conceptually better and more orthogonal approach, will result in a ballooning of the search space very quickly.
* While that may be acceptable for group-by engines and only few index access paths, in an OLTP world where there
* are potentially dozens of indexes, memory footprint and the sheer number of tasks that would be created for
* subsequent exploration and implementation of all these alternatives make the purist approach to planning these
* indexes infeasible.
*
* Thus we would like to eliminate unnecessary exploration by avoiding variations we know can never be successful
* either in creating a successful executable plan (e.g. logical expression may not ever be able to produce a
* compatible ordering) or cannot ever create an optimal plan. In a nutshell, we try to utilize additional
* information that is available in addition to the matching partition in order to make decisions about which
* expression variation to create and which to avoid:
*
* <ul>
* <li> For a matching primary scan candidate ({@link PrimaryScanMatchCandidate})
* we will not create a primary scan if the scan is incompatible with an interesting order that has been
* communicated downwards in the graph.
* </li>
* <li> For a matching index scan candidate ({@link ValueIndexScanMatchCandidate})
* we will not create an index scan if the scan is incompatible with an interesting order that has been
* communicated downwards in the graph.
* </li>
* <li> We will only create a scan if there is no other index scan with a greater coverage (think of coverage
* as the assumed amount of filtering or currently the number of bound predicates) for the search arguments
* which are bound by the query.
* For instance, an index scan {@code INDEX SCAN(i1, a = [5, 5], b = [10, 10])} is still planned along
* {@code INDEX SCAN(i2, x = ["hello", "hello"], y = ["world", "world"], z = [10, inf])} even though
* the latter utilizes three search arguments while the former one only uses two. However, an index scan
* {@code INDEX SCAN(i1, a = [5, 5], b = [10, 10])} is not created (and yielded) if there we also
* have a choice to plan {@code INDEX SCAN(i2, b = [10, 10], a = [5, 5], c = ["Guten", "Morgen"])} as that
* index {@code i2} has a higher coverage compared to {@code i1} <em>and</em> all bound arguments in the scan
* over {@code i2} are also bound in the scan over {@code i1}.
* <li>
* We will only create intersections of scans if we can already establish that the logical intersection
* can be implemented by a {@link com.apple.foundationdb.record.query.plan.plans.RecordQueryIntersectionPlan}.
* That requires that the legs of the intersection are compatibly ordered <em>and</em> that that ordering follows
* a potentially required ordering.
* </li>
* </ul>
*
* @param call the call associated with this planner rule execution
*/
@Override
@SuppressWarnings("java:S135")
public void onMatch(@Nonnull PlannerRuleCall call) {
final PlannerBindings bindings = call.getBindings();
final List<? extends PartialMatch> completeMatches = bindings.getAll(getCompleteMatchMatcher());
final R expression = bindings.get(getExpressionMatcher());
//
if (completeMatches.isEmpty()) {
return;
}
//
// return if there is no pre-determined interesting ordering
//
final Optional<Set<RequestedOrdering>> requestedOrderingsOptional = call.getInterestingProperty(OrderingAttribute.ORDERING);
if (requestedOrderingsOptional.isEmpty()) {
return;
}
final Set<RequestedOrdering> requestedOrderings = requestedOrderingsOptional.get();
//
// group matches by candidates
//
final LinkedHashMap<MatchCandidate, ? extends ImmutableList<? extends PartialMatch>> completeMatchMap = completeMatches.stream().collect(Collectors.groupingBy(PartialMatch::getMatchCandidate, LinkedHashMap::new, ImmutableList.toImmutableList()));
// find the best match for a candidate as there may be more than one due to partial matching
final ImmutableSet<PartialMatch> maximumCoverageMatchPerCandidate = completeMatchMap.entrySet().stream().flatMap(entry -> {
final List<? extends PartialMatch> completeMatchesForCandidate = entry.getValue();
final Optional<? extends PartialMatch> bestMatchForCandidateOptional = completeMatchesForCandidate.stream().max(Comparator.comparing(PartialMatch::getNumBoundParameterPrefix));
return bestMatchForCandidateOptional.map(Stream::of).orElse(Stream.empty());
}).collect(ImmutableSet.toImmutableSet());
final List<PartialMatch> bestMaximumCoverageMatches = maximumCoverageMatches(maximumCoverageMatchPerCandidate, requestedOrderings);
if (bestMaximumCoverageMatches.isEmpty()) {
return;
}
// create scans for all best matches
final Map<PartialMatch, RelationalExpression> bestMatchToExpressionMap = createScansForMatches(bestMaximumCoverageMatches);
final ExpressionRef<RelationalExpression> toBeInjectedReference = GroupExpressionRef.empty();
// create single scan accesses
for (final PartialMatch bestMatch : bestMaximumCoverageMatches) {
final RelationalExpression dataAccessAndCompensationExpression = compensateSingleDataAccess(bestMatch, bestMatchToExpressionMap.get(bestMatch));
toBeInjectedReference.insert(dataAccessAndCompensationExpression);
}
final Map<PartialMatch, RelationalExpression> bestMatchToDistinctExpressionMap = distinctMatchToScanMap(bestMatchToExpressionMap);
@Nullable final KeyExpression commonPrimaryKey = call.getContext().getCommonPrimaryKey();
if (commonPrimaryKey != null) {
final var commonPrimaryKeyParts = commonPrimaryKey.normalizeKeyForPositions();
final var boundPartitions = Lists.<List<PartialMatch>>newArrayList();
// create intersections for all n choose k partitions from k = 2 .. n
IntStream.range(2, bestMaximumCoverageMatches.size() + 1).mapToObj(k -> ChooseK.chooseK(bestMaximumCoverageMatches, k)).flatMap(iterable -> StreamSupport.stream(iterable.spliterator(), false)).forEach(boundPartitions::add);
boundPartitions.stream().flatMap(partition -> createIntersectionAndCompensation(commonPrimaryKeyParts, bestMatchToDistinctExpressionMap, partition, requestedOrderings).stream()).forEach(toBeInjectedReference::insert);
}
call.yield(inject(expression, completeMatches, toBeInjectedReference));
}
use of com.apple.foundationdb.record.query.plan.temp.PlannerRuleCall in project fdb-record-layer by FoundationDB.
the class PushSetOperationThroughFetchRule method onMatch.
@Override
@SuppressWarnings("java:S1905")
public void onMatch(@Nonnull PlannerRuleCall call) {
final PlannerBindings bindings = call.getBindings();
final RecordQuerySetPlan setOperationPlan = bindings.get(getMatcher());
final List<? extends Quantifier.Physical> quantifiersOverFetches = bindings.getAll(quantifierOverFetchMatcher);
// if set operation is dynamic all quantifiers must have fetches
if (setOperationPlan.isDynamic()) {
if (quantifiersOverFetches.size() < setOperationPlan.getQuantifiers().size()) {
return;
}
} else {
if (quantifiersOverFetches.size() <= 1) {
// pulling up the fetch is meaningless in this case
return;
}
}
final List<? extends RecordQueryFetchFromPartialRecordPlan> fetchPlans = bindings.getAll(fetchPlanMatcher);
final ImmutableList<TranslateValueFunction> dependentFunctions = fetchPlans.stream().map(RecordQueryFetchFromPartialRecordPlan::getPushValueFunction).collect(ImmutableList.toImmutableList());
Verify.verify(quantifiersOverFetches.size() == fetchPlans.size());
Verify.verify(fetchPlans.size() == dependentFunctions.size());
final List<? extends Value> requiredValues = setOperationPlan.getRequiredValues(CorrelationIdentifier.uniqueID());
final Set<CorrelationIdentifier> pushableAliases = setOperationPlan.tryPushValues(dependentFunctions, quantifiersOverFetches, requiredValues);
// if set operation is dynamic all aliases must be pushable
if (setOperationPlan.isDynamic()) {
if (pushableAliases.size() < setOperationPlan.getQuantifiers().size()) {
return;
}
} else {
if (pushableAliases.size() <= 1) {
// pulling up the fetch is meaningless in this case
return;
}
}
final ImmutableList.Builder<Quantifier.Physical> pushableQuantifiersBuilder = ImmutableList.builder();
final ImmutableList.Builder<RecordQueryFetchFromPartialRecordPlan> pushableFetchPlansBuilder = ImmutableList.builder();
final ImmutableList.Builder<TranslateValueFunction> pushableDependentFunctionsBuilder = ImmutableList.builder();
for (int i = 0; i < quantifiersOverFetches.size(); i++) {
final Quantifier.Physical quantifier = quantifiersOverFetches.get(i);
if (pushableAliases.contains(quantifier.getAlias())) {
pushableQuantifiersBuilder.add(quantifier);
pushableFetchPlansBuilder.add(fetchPlans.get(i));
pushableDependentFunctionsBuilder.add(dependentFunctions.get(i));
}
}
final ImmutableList<Quantifier.Physical> pushableQuantifiers = pushableQuantifiersBuilder.build();
final ImmutableList<RecordQueryFetchFromPartialRecordPlan> pushableFetchPlans = pushableFetchPlansBuilder.build();
final ImmutableList<TranslateValueFunction> pushableDependentFunctions = pushableDependentFunctionsBuilder.build();
final ImmutableList<Quantifier.Physical> nonPushableQuantifiers = setOperationPlan.getQuantifiers().stream().map(quantifier -> (Quantifier.Physical) quantifier).filter(quantifier -> !pushableAliases.contains(quantifier.getAlias())).collect(ImmutableList.toImmutableList());
final List<? extends ExpressionRef<RecordQueryPlan>> newPushedInnerPlans = pushableFetchPlans.stream().map(RecordQueryFetchFromPartialRecordPlan::getChild).map(GroupExpressionRef::of).collect(ImmutableList.toImmutableList());
Verify.verify(pushableQuantifiers.size() + nonPushableQuantifiers.size() == setOperationPlan.getQuantifiers().size());
final TranslateValueFunction combinedTranslateValueFunction = setOperationPlan.pushValueFunction(pushableDependentFunctions);
final RecordQuerySetPlan newSetOperationPlan = setOperationPlan.withChildrenReferences(newPushedInnerPlans);
final RecordQueryFetchFromPartialRecordPlan newFetchPlan = new RecordQueryFetchFromPartialRecordPlan(newSetOperationPlan, combinedTranslateValueFunction);
if (nonPushableQuantifiers.isEmpty()) {
call.yield(GroupExpressionRef.of(newFetchPlan));
} else {
final List<ExpressionRef<? extends RecordQueryPlan>> newFetchPlanAndResidualInners = Streams.concat(Stream.of(GroupExpressionRef.of(newFetchPlan)), nonPushableQuantifiers.stream().map(Quantifier.Physical::getRangesOver).map(RecordQueryPlan::narrowReference)).collect(ImmutableList.toImmutableList());
call.yield(GroupExpressionRef.of(setOperationPlan.withChildrenReferences(newFetchPlanAndResidualInners)));
}
}
use of com.apple.foundationdb.record.query.plan.temp.PlannerRuleCall in project fdb-record-layer by FoundationDB.
the class PushTypeFilterBelowFilterRule method onMatch.
@Override
public void onMatch(@Nonnull PlannerRuleCall call) {
final PlannerBindings bindings = call.getBindings();
final ExpressionRef<? extends RelationalExpression> inner = bindings.get(innerMatcher);
final Quantifier.Physical qun = bindings.get(qunMatcher);
final List<? extends QueryPredicate> predicates = bindings.getAll(predMatcher);
final Collection<String> recordTypes = bindings.get(root).getRecordTypes();
final RecordQueryTypeFilterPlan newTypeFilterPlan = new RecordQueryTypeFilterPlan(Quantifier.physical(inner), recordTypes);
final Quantifier.Physical newQun = Quantifier.physical(call.ref(newTypeFilterPlan));
final List<QueryPredicate> rebasedPredicates = predicates.stream().map(queryPredicate -> queryPredicate.rebase(Quantifiers.translate(qun, newQun))).collect(ImmutableList.toImmutableList());
call.yield(GroupExpressionRef.of(new RecordQueryPredicatesFilterPlan(newQun, rebasedPredicates)));
}
use of com.apple.foundationdb.record.query.plan.temp.PlannerRuleCall in project fdb-record-layer by FoundationDB.
the class NormalizePredicatesRule method onMatch.
@Override
public void onMatch(@Nonnull PlannerRuleCall call) {
final PlannerBindings bindings = call.getBindings();
final SelectExpression selectExpression = bindings.get(root);
final Collection<? extends QueryPredicate> predicates = bindings.get(predicatesMatcher);
final Collection<? extends Quantifier> quantifiers = bindings.get(innerQuantifiersMatcher);
// create one big conjuncted predicate
final QueryPredicate conjunctedPredicate = AndPredicate.and(predicates);
final BooleanPredicateNormalizer cnfNormalizer = BooleanPredicateNormalizer.forConfiguration(BooleanPredicateNormalizer.Mode.CNF, call.getContext().getPlannerConfiguration());
cnfNormalizer.normalize(conjunctedPredicate, false).ifPresent(cnfPredicate -> call.yield(call.ref(new SelectExpression(selectExpression.getResultValues(), quantifiers.stream().map(quantifier -> quantifier.toBuilder().build(quantifier.getRangesOver())).collect(ImmutableList.toImmutableList()), AndPredicate.conjuncts(cnfPredicate)))));
final BooleanPredicateNormalizer dnfNormalizer = BooleanPredicateNormalizer.forConfiguration(BooleanPredicateNormalizer.Mode.DNF, call.getContext().getPlannerConfiguration());
dnfNormalizer.normalize(conjunctedPredicate, false).ifPresent(dnfPredicate -> call.yield(call.ref(new SelectExpression(selectExpression.getResultValues(), quantifiers.stream().map(quantifier -> quantifier.toBuilder().build(quantifier.getRangesOver())).collect(ImmutableList.toImmutableList()), ImmutableList.of(dnfPredicate)))));
}
use of com.apple.foundationdb.record.query.plan.temp.PlannerRuleCall in project fdb-record-layer by FoundationDB.
the class PushFilterThroughFetchRule method onMatch.
@Override
public void onMatch(@Nonnull PlannerRuleCall call) {
final PlannerBindings bindings = call.getBindings();
final RecordQueryPredicatesFilterPlan filterPlan = bindings.get(root);
final RecordQueryFetchFromPartialRecordPlan fetchPlan = bindings.get(fetchPlanMatcher);
final Quantifier.Physical quantifierOverFetch = bindings.get(quantifierOverFetchMatcher);
final RecordQueryPlan innerPlan = bindings.get(innerPlanMatcher);
final List<? extends QueryPredicate> queryPredicates = filterPlan.getPredicates();
final ImmutableList.Builder<QueryPredicate> pushedPredicatesBuilder = ImmutableList.builder();
final ImmutableList.Builder<QueryPredicate> residualPredicatesBuilder = ImmutableList.builder();
final CorrelationIdentifier newInnerAlias = CorrelationIdentifier.uniqueID();
for (final QueryPredicate queryPredicate : queryPredicates) {
final Optional<QueryPredicate> pushedPredicateOptional = queryPredicate.replaceLeavesMaybe(leafPredicate -> pushLeafPredicate(fetchPlan, newInnerAlias, leafPredicate));
if (pushedPredicateOptional.isPresent()) {
pushedPredicatesBuilder.add(pushedPredicateOptional.get());
} else {
residualPredicatesBuilder.add(queryPredicate);
}
}
final ImmutableList<QueryPredicate> pushedPredicates = pushedPredicatesBuilder.build();
final ImmutableList<QueryPredicate> residualPredicates = residualPredicatesBuilder.build();
Verify.verify(pushedPredicates.size() + residualPredicates.size() == queryPredicates.size());
// case 1
if (pushedPredicates.isEmpty()) {
return;
}
// for case 2 and case 3 we can at least build a FILTER(inner, pushedPredicates) as that is
// required both for case 2 nd 3
final Quantifier.Physical newInnerQuantifier = Quantifier.physical(GroupExpressionRef.of(innerPlan), newInnerAlias);
final RecordQueryPredicatesFilterPlan pushedFilterPlan = new RecordQueryPredicatesFilterPlan(newInnerQuantifier, pushedPredicates);
final RecordQueryFetchFromPartialRecordPlan newFetchPlan = new RecordQueryFetchFromPartialRecordPlan(pushedFilterPlan, fetchPlan.getPushValueFunction());
if (residualPredicates.isEmpty()) {
// case 2
call.yield(call.ref(newFetchPlan));
} else {
// case 3
// create yet another physical quantifier on top of the fetch
final Quantifier.Physical newQuantifierOverFetch = Quantifier.physical(GroupExpressionRef.of(newFetchPlan));
final AliasMap translationMap = AliasMap.of(quantifierOverFetch.getAlias(), newQuantifierOverFetch.getAlias());
// rebase all residual predicates to use that quantifier's alias
final ImmutableList<QueryPredicate> rebasedResidualPredicates = residualPredicates.stream().map(residualPredicate -> residualPredicate.rebase(translationMap)).collect(ImmutableList.toImmutableList());
call.yield(GroupExpressionRef.of(new RecordQueryPredicatesFilterPlan(newQuantifierOverFetch, rebasedResidualPredicates)));
}
}
Aggregations