use of com.apple.foundationdb.record.query.plan.temp.AliasMap 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.AliasMap in project fdb-record-layer by FoundationDB.
the class RecordQuerySetPlan method tryPushValues.
@Nonnull
@SuppressWarnings("java:S135")
default Set<CorrelationIdentifier> tryPushValues(@Nonnull final List<TranslateValueFunction> dependentFunctions, @Nonnull final List<? extends Quantifier> quantifiers, @Nonnull final Iterable<? extends Value> values) {
Verify.verify(!dependentFunctions.isEmpty());
Verify.verify(dependentFunctions.size() == quantifiers.size());
final Set<CorrelationIdentifier> candidatesAliases = quantifiers.stream().map(Quantifier::getAlias).collect(Collectors.toSet());
final CorrelationIdentifier newBaseAlias = CorrelationIdentifier.uniqueID();
final QuantifiedColumnValue newBaseColumnValue = QuantifiedColumnValue.of(newBaseAlias, 0);
for (final Value value : values) {
final AliasMap equivalencesMap = AliasMap.identitiesFor(ImmutableSet.of(newBaseAlias));
@Nullable Value previousPushedValue = null;
for (int i = 0; i < dependentFunctions.size(); i++) {
final TranslateValueFunction dependentFunction = dependentFunctions.get(i);
final Quantifier quantifier = quantifiers.get(i);
if (!candidatesAliases.contains(quantifier.getAlias())) {
continue;
}
final Optional<Value> pushedValueOptional = dependentFunction.translateValue(value, newBaseColumnValue);
if (!pushedValueOptional.isPresent()) {
candidatesAliases.remove(quantifier.getAlias());
continue;
}
if (previousPushedValue == null) {
previousPushedValue = pushedValueOptional.get();
} else {
if (!previousPushedValue.semanticEquals(pushedValueOptional.get(), equivalencesMap)) {
// something is really wrong as we cannot establish a proper genuine derivation path
return ImmutableSet.of();
}
}
}
}
return ImmutableSet.copyOf(candidatesAliases);
}
use of com.apple.foundationdb.record.query.plan.temp.AliasMap 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.AliasMap in project fdb-record-layer by FoundationDB.
the class FindingMatcher method enumerate.
/**
* Method to enumerate the permutations on this side against the permutation of the other side in order
* to form matches (bijective mappings between the permutations). The match predicate is called for each pair of
* elements (for a match attempt). If the match predicate returns {@code true} the pair is recorded
* as a matching pair. We attempt to find a matching pair (one element from this side; one from the
* other side) for each element identified by {@link #getAliases()}. For each individual new such pair that is found,
* we continue in the matching attempt. Once a set of bindings is established for all aliases
* in {@link #getAliases} this method then includes that {@link AliasMap} in the computable of {@link AliasMap}s that
* form the result and proceeds to consume further permutations from the iterator that is passed in in order to
* establish other matches between this and other.
* @param iterator an enumerating iterable for the permutations on this side
* @param otherOrdered one permutation (that is not violating dependencies, constraints, etc.) of the other side
* @return an {@link Iterator} of match results (of type {@link AliasMap})
*/
@SuppressWarnings("java:S135")
@Nonnull
public Iterator<AliasMap> enumerate(@Nonnull final EnumeratingIterator<CorrelationIdentifier> iterator, @Nonnull final List<CorrelationIdentifier> otherOrdered) {
final Set<CorrelationIdentifier> aliases = getAliases();
final AliasMap boundAliasesMap = getBoundAliasesMap();
if (otherOrdered.isEmpty()) {
return ImmutableList.of(boundAliasesMap).iterator();
}
int size = otherOrdered.size();
return new AbstractIterator<AliasMap>() {
@Override
protected AliasMap computeNext() {
while (iterator.hasNext()) {
final List<CorrelationIdentifier> ordered = iterator.next();
final AliasMap.Builder aliasMapBuilder = AliasMap.builder(aliases.size());
int i;
for (i = 0; i < size; i++) {
final AliasMap aliasMap = aliasMapBuilder.build();
final CorrelationIdentifier alias = ordered.get(i);
final CorrelationIdentifier otherAlias = otherOrdered.get(i);
final Optional<AliasMap> dependsOnMapOptional = mapDependenciesToOther(aliasMap, alias, otherAlias);
if (!dependsOnMapOptional.isPresent()) {
break;
}
final AliasMap dependsOnMap = dependsOnMapOptional.get();
final T entity = Objects.requireNonNull(getAliasToElementMap().get(alias));
final T otherEntity = Objects.requireNonNull(getOtherAliasToElementMap().get(otherAlias));
if (!matchPredicate.test(entity, otherEntity, boundAliasesMap.combine(dependsOnMap))) {
break;
}
// We now amend the equivalences passed in by adding the already known bound aliases left
// of i and make them equivalent as well
aliasMapBuilder.put(alias, otherAlias);
}
if (i == size) {
iterator.skip(i - 1);
return boundAliasesMap.derived(ordered.size()).zip(ordered, otherOrdered, i).build();
} else {
// we can skip all permutations where the i-th value is bound the way it currently is
iterator.skip(i);
}
}
return endOfData();
}
};
}
use of com.apple.foundationdb.record.query.plan.temp.AliasMap in project fdb-record-layer by FoundationDB.
the class SelectExpression method partitionPredicates.
private static List<? extends QueryPredicate> partitionPredicates(final List<? extends QueryPredicate> predicates) {
final ImmutableList<QueryPredicate> flattenedAndPredicates = predicates.stream().flatMap(predicate -> flattenAndPredicate(predicate).stream()).collect(ImmutableList.toImmutableList());
// partition predicates in value-based predicates and non-value-based predicates
final ImmutableList.Builder<PredicateWithValue> predicateWithValuesBuilder = ImmutableList.builder();
final ImmutableList.Builder<QueryPredicate> resultPredicatesBuilder = ImmutableList.builder();
for (final QueryPredicate flattenedAndPredicate : flattenedAndPredicates) {
if (flattenedAndPredicate instanceof PredicateWithValue) {
predicateWithValuesBuilder.add((PredicateWithValue) flattenedAndPredicate);
} else {
resultPredicatesBuilder.add(flattenedAndPredicate);
}
}
final ImmutableList<PredicateWithValue> predicateWithValues = predicateWithValuesBuilder.build();
final AliasMap boundIdentitiesMap = AliasMap.identitiesFor(flattenedAndPredicates.stream().flatMap(predicate -> predicate.getCorrelatedTo().stream()).collect(ImmutableSet.toImmutableSet()));
final BoundEquivalence boundEquivalence = new BoundEquivalence(boundIdentitiesMap);
final HashMultimap<Equivalence.Wrapper<Value>, PredicateWithValue> partitionedPredicatesWithValues = predicateWithValues.stream().collect(Multimaps.toMultimap(predicate -> boundEquivalence.wrap(predicate.getValue()), Function.identity(), HashMultimap::create));
partitionedPredicatesWithValues.asMap().forEach((valueWrapper, predicatesOnValue) -> {
final Value value = Objects.requireNonNull(valueWrapper.get());
ComparisonRange resultRange = ComparisonRange.EMPTY;
for (final PredicateWithValue predicateOnValue : predicatesOnValue) {
if (predicateOnValue instanceof ValuePredicate) {
final Comparisons.Comparison comparison = ((ValuePredicate) predicateOnValue).getComparison();
final ComparisonRange.MergeResult mergeResult = resultRange.merge(comparison);
resultRange = mergeResult.getComparisonRange();
mergeResult.getResidualComparisons().forEach(residualComparison -> resultPredicatesBuilder.add(value.withComparison(residualComparison)));
} else if (predicateOnValue instanceof Sargable) {
final Sargable valueComparisonRangePredicate = (Sargable) predicateOnValue;
final ComparisonRange comparisonRange = valueComparisonRangePredicate.getComparisonRange();
final ComparisonRange.MergeResult mergeResult = resultRange.merge(comparisonRange);
resultRange = mergeResult.getComparisonRange();
mergeResult.getResidualComparisons().forEach(residualComparison -> resultPredicatesBuilder.add(value.withComparison(residualComparison)));
} else {
resultPredicatesBuilder.add(predicateOnValue);
}
}
if (!resultRange.isEmpty()) {
resultPredicatesBuilder.add(ValueComparisonRangePredicate.sargable(value, resultRange));
}
});
return resultPredicatesBuilder.build();
}
Aggregations