use of com.apple.foundationdb.record.query.plan.temp.Quantifier in project fdb-record-layer by FoundationDB.
the class ImplementDistinctUnionRule method onMatch.
@Override
@SuppressWarnings("java:S135")
public void onMatch(@Nonnull PlannerRuleCall call) {
final PlanContext context = call.getContext();
final Optional<Set<RequestedOrdering>> requiredOrderingsOptional = call.getInterestingProperty(OrderingAttribute.ORDERING);
if (requiredOrderingsOptional.isEmpty()) {
return;
}
final Set<RequestedOrdering> requestedOrderings = requiredOrderingsOptional.get();
final KeyExpression commonPrimaryKey = context.getCommonPrimaryKey();
if (commonPrimaryKey == null) {
return;
}
final List<KeyExpression> commonPrimaryKeyParts = commonPrimaryKey.normalizeKeyForPositions();
final PlannerBindings bindings = call.getBindings();
final Quantifier.ForEach unionForEachQuantifier = bindings.get(unionForEachQuantifierMatcher);
final List<? extends Collection<? extends RecordQueryPlan>> plansByQuantifier = bindings.getAll(unionLegPlansMatcher);
// group each leg's plans by their provided ordering
final ImmutableList<Set<Map.Entry<Ordering, ImmutableList<RecordQueryPlan>>>> plansByQuantifierOrdering = plansByQuantifier.stream().map(plansForQuantifier -> {
final Map<Ordering, ImmutableList<RecordQueryPlan>> groupedBySortedness = plansForQuantifier.stream().flatMap(plan -> {
final Optional<Ordering> orderingForLegOptional = OrderingProperty.evaluate(plan, context);
return orderingForLegOptional.stream().map(ordering -> Pair.of(ordering, plan));
}).collect(Collectors.groupingBy(Pair::getLeft, Collectors.mapping(Pair::getRight, ImmutableList.toImmutableList())));
return groupedBySortedness.entrySet();
}).collect(ImmutableList.toImmutableList());
for (final List<Map.Entry<Ordering, ImmutableList<RecordQueryPlan>>> entries : CrossProduct.crossProduct(plansByQuantifierOrdering)) {
final ImmutableList<Optional<Ordering>> orderingOptionals = entries.stream().map(entry -> Optional.of(entry.getKey())).collect(ImmutableList.toImmutableList());
for (final RequestedOrdering requestedOrdering : requestedOrderings) {
final Optional<Ordering> combinedOrderingOptional = OrderingProperty.deriveForUnionFromOrderings(orderingOptionals, requestedOrdering, Ordering::intersectEqualityBoundKeys);
pushInterestingOrders(call, unionForEachQuantifier, orderingOptionals, requestedOrdering);
if (combinedOrderingOptional.isEmpty()) {
//
continue;
}
final Ordering ordering = combinedOrderingOptional.get();
final Set<KeyExpression> equalityBoundKeys = ordering.getEqualityBoundKeys();
final List<KeyPart> orderingKeyParts = ordering.getOrderingKeyParts();
final List<KeyExpression> orderingKeys = orderingKeyParts.stream().map(KeyPart::getNormalizedKeyExpression).collect(ImmutableList.toImmutableList());
// make sure the common primary key parts are either bound through equality or they are part of the ordering
if (!isPrimaryKeyCompatibleWithOrdering(commonPrimaryKeyParts, orderingKeys, equalityBoundKeys)) {
continue;
}
//
// At this point we know we can implement the distinct union over the partitions of compatibly ordered plans
//
final KeyExpression comparisonKey = orderingKeys.size() == 1 ? Iterables.getOnlyElement(orderingKeys) : Key.Expressions.concat(orderingKeys);
//
// create new references
//
final ImmutableList<Quantifier.Physical> newQuantifiers = entries.stream().map(Map.Entry::getValue).map(GroupExpressionRef::from).map(Quantifier::physical).collect(ImmutableList.toImmutableList());
call.yield(call.ref(RecordQueryUnionPlan.fromQuantifiers(newQuantifiers, comparisonKey, true)));
}
}
}
use of com.apple.foundationdb.record.query.plan.temp.Quantifier in project fdb-record-layer by FoundationDB.
the class ImplementInJoinRule method getInSourcesForRequestedOrdering.
@Nonnull
@SuppressWarnings("unchecked")
private ImmutableList<InSource> getInSourcesForRequestedOrdering(@Nonnull final Map<CorrelationIdentifier, Quantifier> explodeAliasToQuantifierMap, @Nonnull final Set<CorrelationIdentifier> explodeAliases, @Nonnull final IdentityBiMap<Quantifier.ForEach, ExplodeExpression> quantifierToExplodeBiMap, @Nonnull final Ordering providedInnerOrdering, @Nonnull final RequestedOrdering requestedOrdering) {
final var availableExplodeAliases = Sets.newLinkedHashSet(explodeAliases);
final var requestedOrderingKeyParts = requestedOrdering.getOrderingKeyParts();
final var sourcesBuilder = ImmutableList.<InSource>builder();
final var resultOrderingKeyPartsBuilder = ImmutableList.<KeyPart>builder();
final var innerOrderingKeyParts = providedInnerOrdering.getOrderingKeyParts();
final var innerEqualityBoundKeyMap = providedInnerOrdering.getEqualityBoundKeyMap();
final var resultOrderingEqualityBoundKeyMap = HashMultimap.create(innerEqualityBoundKeyMap);
for (var i = 0; i < requestedOrderingKeyParts.size() && !availableExplodeAliases.isEmpty(); i++) {
final var requestedOrderingKeyPart = requestedOrderingKeyParts.get(i);
final var comparisons = innerEqualityBoundKeyMap.get(requestedOrderingKeyPart.getNormalizedKeyExpression());
if (comparisons.isEmpty()) {
return ImmutableList.of();
}
final var comparisonsCorrelatedTo = comparisons.stream().flatMap(comparison -> comparison.getCorrelatedTo().stream()).collect(ImmutableSet.toImmutableSet());
if (comparisonsCorrelatedTo.size() > 1) {
return ImmutableList.of();
}
if (Sets.intersection(comparisonsCorrelatedTo, explodeAliases).isEmpty()) {
//
continue;
}
final var explodeAlias = Iterables.getOnlyElement(comparisonsCorrelatedTo);
//
if (!availableExplodeAliases.contains(explodeAlias)) {
return ImmutableList.of();
}
//
// We need to find the one quantifier over an explode expression that we can use to establish
// the requested order.
//
final var explodeQuantifier = Objects.requireNonNull(explodeAliasToQuantifierMap.get(explodeAlias));
final var explodeExpression = Objects.requireNonNull(quantifierToExplodeBiMap.getUnwrapped(explodeQuantifier));
//
// At this point we have a bound key expression that matches the requested order at this position,
// and we have our hands on a particular explode expression leading us directly do the in source.
//
final var explodeResultValues = explodeExpression.getResultValues();
if (explodeResultValues.size() != 1) {
return ImmutableList.of();
}
final var explodeValue = Iterables.getOnlyElement(explodeResultValues);
final InSource inSource;
if (explodeValue instanceof LiteralValue<?>) {
final Object literalValue = ((LiteralValue<?>) explodeValue).getLiteralValue();
if (literalValue instanceof List<?>) {
inSource = new SortedInValuesSource(CORRELATION.bindingName(explodeQuantifier.getAlias().getId()), (List<Object>) literalValue, requestedOrderingKeyPart.isReverse());
} else {
return ImmutableList.of();
}
} else if (explodeValue instanceof QuantifiedColumnValue) {
inSource = new SortedInParameterSource(CORRELATION.bindingName(explodeQuantifier.getAlias().getId()), ((QuantifiedColumnValue) explodeValue).getAlias().getId(), requestedOrderingKeyPart.isReverse());
} else {
return ImmutableList.of();
}
availableExplodeAliases.remove(explodeAlias);
sourcesBuilder.add(inSource);
resultOrderingEqualityBoundKeyMap.removeAll(requestedOrderingKeyPart.getNormalizedKeyExpression());
resultOrderingKeyPartsBuilder.add(requestedOrderingKeyPart);
}
if (availableExplodeAliases.isEmpty()) {
//
// All available explode aliases have been depleted. Create an ordering and check against the requested
// ordering.
//
resultOrderingKeyPartsBuilder.addAll(innerOrderingKeyParts);
final var resultOrdering = new Ordering(resultOrderingEqualityBoundKeyMap, resultOrderingKeyPartsBuilder.build(), providedInnerOrdering.isDistinct());
return Ordering.satisfiesRequestedOrdering(resultOrdering, requestedOrdering) ? sourcesBuilder.build() : ImmutableList.of();
} else {
//
for (final var explodeAlias : availableExplodeAliases) {
final var explodeQuantifier = Objects.requireNonNull(explodeAliasToQuantifierMap.get(explodeAlias));
final var explodeExpression = Objects.requireNonNull(quantifierToExplodeBiMap.getUnwrapped(explodeQuantifier));
final var explodeResultValues = explodeExpression.getResultValues();
if (explodeResultValues.size() != 1) {
return ImmutableList.of();
}
final var explodeValue = Iterables.getOnlyElement(explodeResultValues);
final InSource inSource;
if (explodeValue instanceof LiteralValue<?>) {
final Object literalValue = ((LiteralValue<?>) explodeValue).getLiteralValue();
if (literalValue instanceof List<?>) {
inSource = new InValuesSource(CORRELATION.bindingName(explodeQuantifier.getAlias().getId()), (List<Object>) literalValue);
} else {
return ImmutableList.of();
}
} else if (explodeValue instanceof QuantifiedColumnValue) {
inSource = new InParameterSource(CORRELATION.bindingName(explodeQuantifier.getAlias().getId()), ((QuantifiedColumnValue) explodeValue).getAlias().getId());
} else {
return ImmutableList.of();
}
sourcesBuilder.add(inSource);
}
}
//
return sourcesBuilder.build();
}
use of com.apple.foundationdb.record.query.plan.temp.Quantifier 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.Quantifier in project fdb-record-layer by FoundationDB.
the class RemoveRedundantTypeFilterRule method onMatch.
@Override
public void onMatch(@Nonnull PlannerRuleCall call) {
final LogicalTypeFilterExpression typeFilter = call.get(root);
final Quantifier.ForEach qun = call.get(qunMatcher);
// TODO add overload
final Set<String> childRecordTypes = RecordTypesProperty.evaluate(call.getContext(), call.getAliasResolver(), qun.getRangesOver());
final Set<String> filterRecordTypes = Sets.newHashSet(typeFilter.getRecordTypes());
if (filterRecordTypes.containsAll(childRecordTypes)) {
// type filter is completely redundant, so remove it entirely
call.yield(qun.getRangesOver());
} else {
// otherwise, keep a logical filter on record types which the quantifier might produce and are included in the filter
final Set<String> unsatisfiedTypeFilters = Sets.intersection(childRecordTypes, filterRecordTypes);
if (!unsatisfiedTypeFilters.equals(filterRecordTypes)) {
// there were some unnecessary filters, so remove them
call.yield(GroupExpressionRef.of(new LogicalTypeFilterExpression(unsatisfiedTypeFilters, qun)));
}
// otherwise, nothing changes
}
}
use of com.apple.foundationdb.record.query.plan.temp.Quantifier in project fdb-record-layer by FoundationDB.
the class RelationalExpressionWithChildren method getCorrelatedTo.
@Nonnull
@Override
@SuppressWarnings("squid:S2201")
default Set<CorrelationIdentifier> getCorrelatedTo() {
final ImmutableSet.Builder<CorrelationIdentifier> builder = ImmutableSet.builder();
final List<? extends Quantifier> quantifiers = getQuantifiers();
final Map<CorrelationIdentifier, ? extends Quantifier> aliasToQuantifierMap = quantifiers.stream().collect(Collectors.toMap(Quantifier::getAlias, Function.identity()));
// We should check if the graph is sound here, if it is not we should throw an exception. This method
// will properly return with an empty. There are other algorithms that may not be as defensive and we
// must protect ourselves from illegal graphs (and bugs).
final Optional<List<CorrelationIdentifier>> orderedOptional = TopologicalSort.anyTopologicalOrderPermutation(quantifiers.stream().map(Quantifier::getAlias).collect(Collectors.toSet()), alias -> Objects.requireNonNull(aliasToQuantifierMap.get(alias)).getCorrelatedTo());
orderedOptional.orElseThrow(() -> new IllegalArgumentException("correlations are cyclic"));
getCorrelatedToWithoutChildren().stream().filter(correlationIdentifier -> !aliasToQuantifierMap.containsKey(correlationIdentifier)).forEach(builder::add);
for (final Quantifier quantifier : quantifiers) {
quantifier.getCorrelatedTo().stream().filter(correlationIdentifier -> !canCorrelate() || !aliasToQuantifierMap.containsKey(correlationIdentifier)).forEach(builder::add);
}
return builder.build();
}
Aggregations