use of com.apple.foundationdb.record.query.plan.temp.RelationalExpression in project fdb-record-layer by FoundationDB.
the class RecordQueryPlan method structuralEquals.
/**
* Determine if two plans are structurally equal. This differs from the semantic equality defined in
* {@link RelationalExpression}. For instance this method would return false
* for two given plans {@code UNION(p1, p2)} and {@code UNION(p2, p1)} of two different sub-plans {@code p1} and
* {@code p2}. In contrast to that these plans are considered semantically equal.
* @param other object to compare this object with
* @param equivalenceMap alias map to indicate aliases that should be considered as equal when {@code other} is
* compared to {@code this}. For instance {@code q1.x = 1} is only structurally equal with {@code q2.x = 1}
* if there is a mapping {@code q1 -> q2} in the alias map passed in
* @return {@code true} if {@code this} is structurally equal to {@code other}, {@code false} otherwise
*/
@API(API.Status.EXPERIMENTAL)
default boolean structuralEquals(@Nullable final Object other, @Nonnull final AliasMap equivalenceMap) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
final RelationalExpression otherExpression = (RelationalExpression) other;
// We know this and otherExpression are of the same class. canCorrelate() needs to match as well.
Verify.verify(canCorrelate() == otherExpression.canCorrelate());
final List<Quantifier.Physical> quantifiers = Quantifiers.narrow(Quantifier.Physical.class, getQuantifiers());
final List<Quantifier.Physical> otherQuantifiers = Quantifiers.narrow(Quantifier.Physical.class, otherExpression.getQuantifiers());
if (quantifiers.size() != otherQuantifiers.size()) {
return false;
}
final Iterable<AliasMap> boundCorrelatedReferencesIterable = enumerateUnboundCorrelatedTo(equivalenceMap, otherExpression);
for (final AliasMap boundCorrelatedReferencesMap : boundCorrelatedReferencesIterable) {
final AliasMap.Builder boundCorrelatedToBuilder = boundCorrelatedReferencesMap.derived();
AliasMap boundCorrelatedToMap = AliasMap.emptyMap();
int i;
for (i = 0; i < quantifiers.size(); i++) {
boundCorrelatedToMap = boundCorrelatedToBuilder.build();
final Quantifier.Physical quantifier = quantifiers.get(i);
final Quantifier.Physical otherQuantifier = otherQuantifiers.get(i);
if (quantifier.structuralHashCode() != otherQuantifier.structuralHashCode()) {
break;
}
if (!quantifier.structuralEquals(otherQuantifier)) {
break;
}
if (canCorrelate()) {
boundCorrelatedToBuilder.put(quantifier.getAlias(), otherQuantifier.getAlias());
}
}
if (i == quantifiers.size() && (equalsWithoutChildren(otherExpression, boundCorrelatedToMap))) {
return true;
}
}
return false;
}
use of com.apple.foundationdb.record.query.plan.temp.RelationalExpression in project fdb-record-layer by FoundationDB.
the class OrderingProperty method evaluateAtRef.
@Nonnull
@Override
public Optional<Ordering> evaluateAtRef(@Nonnull ExpressionRef<? extends RelationalExpression> ref, @Nonnull List<Optional<Ordering>> orderingOptionals) {
final Optional<SetMultimap<KeyExpression, Comparisons.Comparison>> commonEqualityBoundKeysMapOptional = Ordering.combineEqualityBoundKeys(orderingOptionals, Ordering::intersectEqualityBoundKeys);
if (commonEqualityBoundKeysMapOptional.isEmpty()) {
return Optional.empty();
}
final var commonEqualityBoundKeysMap = commonEqualityBoundKeysMapOptional.get();
final Optional<List<KeyPart>> commonOrderingKeysOptional = Ordering.commonOrderingKeys(orderingOptionals, RequestedOrdering.preserve());
if (commonOrderingKeysOptional.isEmpty()) {
return Optional.empty();
}
final var commonOrderingKeys = commonOrderingKeysOptional.get().stream().filter(keyPart -> !commonEqualityBoundKeysMap.containsKey(keyPart.getNormalizedKeyExpression())).collect(ImmutableList.toImmutableList());
final var allAreDistinct = orderingOptionals.stream().allMatch(orderingOptional -> orderingOptional.map(Ordering::isDistinct).orElse(false));
return Optional.of(new Ordering(commonEqualityBoundKeysMap, commonOrderingKeys, allAreDistinct));
}
use of com.apple.foundationdb.record.query.plan.temp.RelationalExpression in project fdb-record-layer by FoundationDB.
the class SelectExpression method subsumedBy.
@Nonnull
@Override
public Iterable<MatchInfo> subsumedBy(@Nonnull final RelationalExpression candidateExpression, @Nonnull final AliasMap aliasMap, @Nonnull final IdentityBiMap<Quantifier, PartialMatch> partialMatchMap) {
// TODO This method should be simplified by adding some structure to it.
final Collection<MatchInfo> matchInfos = PartialMatch.matchesFromMap(partialMatchMap);
Verify.verify(this != candidateExpression);
if (getClass() != candidateExpression.getClass()) {
return ImmutableList.of();
}
final SelectExpression otherSelectExpression = (SelectExpression) candidateExpression;
// merge parameter maps -- early out if a binding clashes
final ImmutableList<Map<CorrelationIdentifier, ComparisonRange>> parameterBindingMaps = matchInfos.stream().map(MatchInfo::getParameterBindingMap).collect(ImmutableList.toImmutableList());
final Optional<Map<CorrelationIdentifier, ComparisonRange>> mergedParameterBindingMapOptional = MatchInfo.tryMergeParameterBindings(parameterBindingMaps);
if (!mergedParameterBindingMapOptional.isPresent()) {
return ImmutableList.of();
}
final Map<CorrelationIdentifier, ComparisonRange> mergedParameterBindingMap = mergedParameterBindingMapOptional.get();
final ImmutableSet.Builder<CorrelationIdentifier> matchedCorrelatedToBuilder = ImmutableSet.builder();
// for-each quantifiers. Also keep track of all aliases the matched quantifiers are correlated to.
for (final Quantifier quantifier : getQuantifiers()) {
if (partialMatchMap.containsKeyUnwrapped(quantifier)) {
if (quantifier instanceof Quantifier.ForEach) {
// current quantifier is matched
final PartialMatch childPartialMatch = Objects.requireNonNull(partialMatchMap.getUnwrapped(quantifier));
if (!childPartialMatch.getQueryExpression().computeUnmatchedForEachQuantifiers(childPartialMatch).isEmpty()) {
return ImmutableList.of();
}
}
matchedCorrelatedToBuilder.addAll(quantifier.getCorrelatedTo());
}
}
for (final Value resultValue : getResultValues()) {
matchedCorrelatedToBuilder.addAll(resultValue.getCorrelatedTo());
}
final ImmutableSet<CorrelationIdentifier> matchedCorrelatedTo = matchedCorrelatedToBuilder.build();
if (getQuantifiers().stream().anyMatch(quantifier -> quantifier instanceof Quantifier.ForEach && !partialMatchMap.containsKeyUnwrapped(quantifier))) {
return ImmutableList.of();
}
final boolean allNonMatchedQuantifiersIndependent = getQuantifiers().stream().filter(quantifier -> !partialMatchMap.containsKeyUnwrapped(quantifier)).noneMatch(quantifier -> matchedCorrelatedTo.contains(quantifier.getAlias()));
if (!allNonMatchedQuantifiersIndependent) {
return ImmutableList.of();
}
// Loop through all for each quantifiers on the other side to ensure that they are all matched.
// If any are not matched we cannot establish a match at all.
final boolean allOtherForEachQuantifiersMatched = otherSelectExpression.getQuantifiers().stream().filter(quantifier -> quantifier instanceof Quantifier.ForEach).allMatch(quantifier -> aliasMap.containsTarget(quantifier.getAlias()));
// would help us here to make sure the additional non-matched quantifier is not eliminating records.
if (!allOtherForEachQuantifiersMatched) {
return ImmutableList.of();
}
//
// Map predicates on the query side to predicates on the candidate side. Record parameter bindings and/or
// compensations for each mapped predicate.
// A predicate on this side (the query side) can cause us to filter out rows, a mapped predicate (for that
// predicate) can only filter out fewer rows which is correct and can be compensated for. The important part
// is that we must not have predicates on the other (candidate) side at the end of this mapping process which
// would mean that the candidate eliminates records that the query side may not eliminate. If we detect that
// case we MUST not create a match.
//
final ImmutableList.Builder<Iterable<PredicateMapping>> predicateMappingsBuilder = ImmutableList.builder();
//
if (getPredicates().isEmpty()) {
final boolean allNonFiltering = otherSelectExpression.getPredicates().stream().allMatch(queryPredicate -> queryPredicate instanceof Placeholder || queryPredicate.isTautology());
if (allNonFiltering) {
return MatchInfo.tryMerge(partialMatchMap, mergedParameterBindingMap, PredicateMap.empty()).map(ImmutableList::of).orElse(ImmutableList.of());
} else {
return ImmutableList.of();
}
}
for (final QueryPredicate predicate : getPredicates()) {
final Set<PredicateMapping> impliedMappingsForPredicate = predicate.findImpliedMappings(aliasMap, otherSelectExpression.getPredicates());
predicateMappingsBuilder.add(impliedMappingsForPredicate);
}
//
// We now have a multimap from predicates on the query side to predicates on the candidate side. In the trivial
// case this multimap only contains singular mappings for a query predicate. If it doesn't we need to enumerate
// through their cross product exhaustively. Each complete and non-contradictory element of that cross product
// can lead to a match.
//
final EnumeratingIterable<PredicateMapping> crossedMappings = CrossProduct.crossProduct(predicateMappingsBuilder.build());
return IterableHelpers.flatMap(crossedMappings, predicateMappings -> {
final Set<QueryPredicate> unmappedOtherPredicates = Sets.newIdentityHashSet();
unmappedOtherPredicates.addAll(otherSelectExpression.getPredicates());
final Map<CorrelationIdentifier, ComparisonRange> parameterBindingMap = Maps.newHashMap();
final PredicateMap.Builder predicateMapBuilder = PredicateMap.builder();
for (final PredicateMapping predicateMapping : predicateMappings) {
predicateMapBuilder.put(predicateMapping.getQueryPredicate(), predicateMapping);
unmappedOtherPredicates.remove(predicateMapping.getCandidatePredicate());
final Optional<CorrelationIdentifier> parameterAliasOptional = predicateMapping.getParameterAliasOptional();
final Optional<ComparisonRange> comparisonRangeOptional = predicateMapping.getComparisonRangeOptional();
if (parameterAliasOptional.isPresent() && comparisonRangeOptional.isPresent()) {
parameterBindingMap.put(parameterAliasOptional.get(), comparisonRangeOptional.get());
}
}
//
// Last chance for unmapped predicates - if there is a placeholder or a tautology on the other side that is still
// unmapped, we can (and should) remove it from the unmapped other set now. The reasoning is that this predicate is
// not filtering so it does not cause records to be filtered that are not filtered on the query side.
//
unmappedOtherPredicates.removeIf(queryPredicate -> queryPredicate instanceof Placeholder || queryPredicate.isTautology());
if (!unmappedOtherPredicates.isEmpty()) {
return ImmutableList.of();
}
final Optional<? extends PredicateMap> predicateMapOptional = predicateMapBuilder.buildMaybe();
return predicateMapOptional.map(predicateMap -> {
final Optional<Map<CorrelationIdentifier, ComparisonRange>> allParameterBindingMapOptional = MatchInfo.tryMergeParameterBindings(ImmutableList.of(mergedParameterBindingMap, parameterBindingMap));
return allParameterBindingMapOptional.flatMap(allParameterBindingMap -> MatchInfo.tryMerge(partialMatchMap, allParameterBindingMap, predicateMap)).map(ImmutableList::of).orElse(ImmutableList.of());
}).orElse(ImmutableList.of());
});
}
use of com.apple.foundationdb.record.query.plan.temp.RelationalExpression in project fdb-record-layer by FoundationDB.
the class PlannerGraphProperty method show.
/**
* Show the planner expression that and all the match candidates rendered in your default browser. This also
* shows {@link PartialMatch}es between references if they exist.
* @param renderSingleGroups iff true group references with just one member are not rendered
* @param queryPlanRootReference the planner expression to be rendered.
* @param matchCandidates a set of candidates for matching which should also be shown
* @return the word "done" (IntelliJ really likes a return of String).
*/
@Nonnull
public static String show(final boolean renderSingleGroups, @Nonnull final GroupExpressionRef<? extends RelationalExpression> queryPlanRootReference, @Nonnull final Set<MatchCandidate> matchCandidates) {
final PlannerGraph queryPlannerGraph = Objects.requireNonNull(queryPlanRootReference.acceptPropertyVisitor(forInternalShow(renderSingleGroups, true)));
final PlannerGraph.InternalPlannerGraphBuilder graphBuilder = queryPlannerGraph.derived();
final Map<MatchCandidate, PlannerGraph> matchCandidateMap = matchCandidates.stream().collect(ImmutableMap.toImmutableMap(Function.identity(), matchCandidate -> Objects.requireNonNull(matchCandidate.getTraversal().getRootReference().acceptPropertyVisitor(forInternalShow(renderSingleGroups)))));
matchCandidateMap.forEach((matchCandidate, matchCandidateGraph) -> graphBuilder.addGraph(matchCandidateGraph));
final ExpressionRefTraversal queryGraphTraversal = ExpressionRefTraversal.withRoot(queryPlanRootReference);
final Set<ExpressionRef<? extends RelationalExpression>> queryGraphRefs = queryGraphTraversal.getRefs();
queryGraphRefs.forEach(queryGraphRef -> {
for (final MatchCandidate matchCandidate : Sets.intersection(matchCandidates, queryGraphRef.getMatchCandidates())) {
final Set<PartialMatch> partialMatchesForCandidate = queryGraphRef.getPartialMatchesForCandidate(matchCandidate);
final PlannerGraph matchCandidatePlannerGraph = Objects.requireNonNull(matchCandidateMap.get(matchCandidate));
final Node queryRefNode = Objects.requireNonNull(queryPlannerGraph.getNodeForIdentity(queryGraphRef));
for (final PartialMatch partialMatchForCandidate : partialMatchesForCandidate) {
@Nullable final Node matchCandidateNode = matchCandidatePlannerGraph.getNodeForIdentity(partialMatchForCandidate.getCandidateRef());
// should always be true but we don't want to bail out for corrupt graphs
if (matchCandidateNode != null) {
graphBuilder.addEdge(queryRefNode, matchCandidateNode, new PartialMatchEdge());
}
}
}
});
final String dotString = exportToDot(graphBuilder.build(), queryPlannerGraph.getNetwork().nodes(), nestedClusterProvider -> matchCandidateMap.entrySet().stream().map(entry -> new NamedCluster(entry.getKey().getName(), entry.getValue().getNetwork().nodes(), nestedClusterProvider)).collect(Collectors.toList()));
return show(dotString);
}
use of com.apple.foundationdb.record.query.plan.temp.RelationalExpression in project fdb-record-layer by FoundationDB.
the class PushDistinctBelowFilterRule method onMatch.
@Override
public void onMatch(@Nonnull PlannerRuleCall call) {
final ExpressionRef<? extends RelationalExpression> inner = call.get(innerRefMatcher);
final Quantifier.Physical qun = call.get(innerQuantifierMatcher);
final RecordQueryPredicatesFilterPlan filterPlan = call.get(filterPlanMatcher);
final RecordQueryUnorderedPrimaryKeyDistinctPlan newDistinctPlan = new RecordQueryUnorderedPrimaryKeyDistinctPlan(Quantifier.physical(inner));
final Quantifier.Physical newQun = Quantifier.physical(call.ref(newDistinctPlan));
final List<QueryPredicate> rebasedPredicates = filterPlan.getPredicates().stream().map(queryPredicate -> queryPredicate.rebase(Quantifiers.translate(qun, newQun))).collect(ImmutableList.toImmutableList());
call.yield(call.ref(new RecordQueryPredicatesFilterPlan(newQun, rebasedPredicates)));
}
Aggregations