Skip to content

Prepare the Cascades planner for multi-stage planning by enabling property computation on arbitrary expressions #3321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,10 @@ public static <T> Optional<List<T>> anyTopologicalOrderPermutation(@Nonnull fina
* @return a permutation of the set that is topologically correctly ordered with respect to {@code dependsOnFn}
*/
public static <T> Optional<List<T>> anyTopologicalOrderPermutation(@Nonnull final PartiallyOrderedSet<T> partiallyOrderedSet) {
if (partiallyOrderedSet.getDependencyMap().isEmpty()) {
// if there are no dependencies, just return a list copy of the set
return Optional.of(ImmutableList.copyOf(partiallyOrderedSet.getSet()));
}
return anyTopologicalOrderPermutation(partiallyOrderedSet.getSet(),
() -> new KahnIterable<>(PartiallyOrderedSet.of(partiallyOrderedSet.getSet(), partiallyOrderedSet.getDependencyMap().inverse())));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.RecordCoreException;
import com.apple.foundationdb.record.logging.LogMessageKeys;
import com.apple.foundationdb.record.query.plan.cascades.explain.ExplainPlanVisitor;
import com.apple.foundationdb.record.query.plan.explain.ExplainLevel;
import com.apple.foundationdb.record.query.plan.explain.ExplainPlanVisitor;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan;

import javax.annotation.Nonnull;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@
import com.apple.foundationdb.record.query.expressions.RecordTypeKeyComparison;
import com.apple.foundationdb.record.query.plan.cascades.ComparisonRange;
import com.apple.foundationdb.record.query.plan.cascades.ComparisonRanges;
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraphProperty;
import com.apple.foundationdb.record.query.plan.cascades.properties.ComparisonsProperty;
import com.apple.foundationdb.record.query.plan.cascades.properties.FieldWithComparisonCountProperty;
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraphVisitor;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.planning.BooleanNormalizer;
import com.apple.foundationdb.record.query.plan.planning.FilterSatisfiedMask;
Expand Down Expand Up @@ -131,6 +129,9 @@
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.apple.foundationdb.record.query.plan.cascades.properties.ComparisonsProperty.comparisons;
import static com.apple.foundationdb.record.query.plan.cascades.properties.FieldWithComparisonCountProperty.fieldWithComparisonCount;

/**
* The query planner.
*
Expand Down Expand Up @@ -333,7 +334,7 @@ public RecordQueryPlan plan(@Nonnull RecordQuery query, @Nonnull ParameterRelati

if (logger.isTraceEnabled()) {
logger.trace(KeyValueLogMessage.of("explain of plan",
"explain", PlannerGraphProperty.explain(plan)));
"explain", PlannerGraphVisitor.explain(plan)));
}

return plan;
Expand Down Expand Up @@ -543,7 +544,8 @@ private ScoredPlan planFilter(@Nonnull PlanContext planContext, @Nonnull QueryCo
final ScoredPlan withInJoin = planFilterWithInJoin(planContext, inExtractor, needOrdering);
if (withInAsOrUnion != null) {
if (withInJoin == null || withInAsOrUnion.score > withInJoin.score ||
FieldWithComparisonCountProperty.evaluate(withInAsOrUnion.getPlan()) < FieldWithComparisonCountProperty.evaluate(withInJoin.getPlan())) {
fieldWithComparisonCount().evaluate(withInAsOrUnion.getPlan()) <
fieldWithComparisonCount().evaluate(withInJoin.getPlan())) {
return withInAsOrUnion;
}
}
Expand Down Expand Up @@ -943,7 +945,7 @@ private ScoredPlan computePlanProperties(@Nonnull PlanContext planContext, @Nonn
}

protected Set<Comparisons.Comparison> computeSargedComparisons(@Nonnull final RecordQueryPlan plan) {
return ComparisonsProperty.evaluate(plan);
return comparisons().evaluate(plan);
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import com.apple.foundationdb.record.RecordPlannerConfigurationProto;
import com.apple.foundationdb.record.query.RecordQuery;
import com.apple.foundationdb.record.query.plan.cascades.CascadesRule;
import com.apple.foundationdb.record.query.plan.cascades.PlannerRuleSet;
import com.apple.foundationdb.record.query.plan.cascades.PlanningRuleSet;
import com.apple.foundationdb.record.query.plan.cascades.rules.PredicateToLogicalUnionRule;
import com.apple.foundationdb.record.query.plan.plans.QueryPlan;
import com.apple.foundationdb.record.query.plan.serialization.PlanSerialization;
Expand Down Expand Up @@ -605,11 +605,11 @@ public Builder setDisabledTransformationRules(@Nonnull final Set<Class<? extends
* for any planning effort.
* @param disabledTransformationRuleNames a set of rule names identifying (via simple class name)
* transformation rules
* @param plannerRuleSet a {@link PlannerRuleSet} that is used to resolve the rule name to a rule class
* @param planningRuleSet a {@link PlanningRuleSet} that is used to resolve the rule name to a rule class
* @return this builder
*/
@Nonnull
public Builder setDisabledTransformationRuleNames(@Nonnull final Set<String> disabledTransformationRuleNames, @Nonnull PlannerRuleSet plannerRuleSet) {
public Builder setDisabledTransformationRuleNames(@Nonnull final Set<String> disabledTransformationRuleNames, @Nonnull PlanningRuleSet planningRuleSet) {
protoBuilder.clearDisabledTransformationRules()
.addAllDisabledTransformationRules(disabledTransformationRuleNames);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,10 @@
import com.apple.foundationdb.record.query.plan.QueryPlanner.IndexScanPreference;
import com.apple.foundationdb.record.query.plan.RecordQueryPlannerConfiguration;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.apple.foundationdb.record.query.plan.cascades.properties.CardinalitiesProperty;
import com.apple.foundationdb.record.query.plan.cascades.properties.CardinalitiesProperty.Cardinalities;
import com.apple.foundationdb.record.query.plan.cascades.properties.CardinalitiesProperty.Cardinality;
import com.apple.foundationdb.record.query.plan.cascades.properties.ComparisonsProperty;
import com.apple.foundationdb.record.query.plan.cascades.properties.FindExpressionProperty;
import com.apple.foundationdb.record.query.plan.cascades.properties.ExpressionDepthProperty;
import com.apple.foundationdb.record.query.plan.cascades.properties.NormalizedResidualPredicateProperty;
import com.apple.foundationdb.record.query.plan.cascades.properties.RelationalExpressionDepthProperty;
import com.apple.foundationdb.record.query.plan.cascades.properties.TypeFilterCountProperty;
import com.apple.foundationdb.record.query.plan.cascades.properties.UnmatchedFieldsCountProperty;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryCoveringIndexPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryFetchFromPartialRecordPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryInJoinPlan;
Expand All @@ -55,11 +50,18 @@
import java.util.function.Supplier;

import static com.apple.foundationdb.record.Bindings.Internal.CORRELATION;
import static com.apple.foundationdb.record.query.plan.cascades.properties.CardinalitiesProperty.cardinalities;
import static com.apple.foundationdb.record.query.plan.cascades.properties.ComparisonsProperty.comparisons;
import static com.apple.foundationdb.record.query.plan.cascades.properties.ExpressionDepthProperty.fetchDepth;
import static com.apple.foundationdb.record.query.plan.cascades.properties.ExpressionDepthProperty.typeFilterDepth;
import static com.apple.foundationdb.record.query.plan.cascades.properties.TypeFilterCountProperty.typeFilterCount;
import static com.apple.foundationdb.record.query.plan.cascades.properties.UnmatchedFieldsCountProperty.unmatchedFieldsCount;

/**
* A comparator implementing the current heuristic cost model for the {@link CascadesPlanner}.
*/
@API(API.Status.EXPERIMENTAL)
@SuppressWarnings("PMD.TooManyStaticImports")
public class CascadesCostModel implements Comparator<RelationalExpression> {
@Nonnull
private static final Set<Class<? extends RelationalExpression>> interestingPlanClasses =
Expand Down Expand Up @@ -89,13 +91,13 @@ public int compare(@Nonnull RelationalExpression a, @Nonnull RelationalExpressio
}

final Map<Class<? extends RelationalExpression>, Set<RelationalExpression>> planOpsMapA =
FindExpressionProperty.evaluate(interestingPlanClasses, a);
FindExpressionVisitor.evaluate(interestingPlanClasses, a);

final Map<Class<? extends RelationalExpression>, Set<RelationalExpression>> planOpsMapB =
FindExpressionProperty.evaluate(interestingPlanClasses, b);
FindExpressionVisitor.evaluate(interestingPlanClasses, b);

final Cardinalities cardinalitiesA = CardinalitiesProperty.evaluate(a);
final Cardinalities cardinalitiesB = CardinalitiesProperty.evaluate(b);
final Cardinalities cardinalitiesA = cardinalities().evaluate(a);
final Cardinalities cardinalitiesB = cardinalities().evaluate(b);

//
// Technically, both cardinalities at runtime must be the same. The question is if we can actually
Expand Down Expand Up @@ -155,8 +157,8 @@ public int compare(@Nonnull RelationalExpression a, @Nonnull RelationalExpressio
return inPlanVsOtherOptional.getAsInt();
}

final int typeFilterCountA = TypeFilterCountProperty.evaluate(a);
final int typeFilterCountB = TypeFilterCountProperty.evaluate(b);
final int typeFilterCountA = typeFilterCount().evaluate(a);
final int typeFilterCountB = typeFilterCount().evaluate(b);

// special case
// if one plan is a primary scan with a type filter and the other one is an index scan with the same number of
Expand All @@ -174,8 +176,8 @@ public int compare(@Nonnull RelationalExpression a, @Nonnull RelationalExpressio
return typeFilterCountCompare;
}

int typeFilterPositionCompare = Integer.compare(RelationalExpressionDepthProperty.TYPE_FILTER_DEPTH.evaluate(b),
RelationalExpressionDepthProperty.TYPE_FILTER_DEPTH.evaluate(a)); // prefer the one with a deeper type filter
// prefer the one with a deeper type filter
int typeFilterPositionCompare = Integer.compare(typeFilterDepth().evaluate(b), typeFilterDepth().evaluate(a));
if (typeFilterPositionCompare != 0) {
return typeFilterPositionCompare;
}
Expand All @@ -193,8 +195,8 @@ public int compare(@Nonnull RelationalExpression a, @Nonnull RelationalExpressio
return numFetchesCompare;
}

final int fetchDepthB = RelationalExpressionDepthProperty.FETCH_DEPTH.evaluate(b);
final int fetchDepthA = RelationalExpressionDepthProperty.FETCH_DEPTH.evaluate(a);
final int fetchDepthB = fetchDepth().evaluate(b);
final int fetchDepthA = fetchDepth().evaluate(a);
int fetchPositionCompare = Integer.compare(fetchDepthA, fetchDepthB);
if (fetchPositionCompare != 0) {
return fetchPositionCompare;
Expand All @@ -211,14 +213,14 @@ public int compare(@Nonnull RelationalExpression a, @Nonnull RelationalExpressio
}
}

int distinctFilterPositionCompare = Integer.compare(RelationalExpressionDepthProperty.DISTINCT_FILTER_DEPTH.evaluate(b),
RelationalExpressionDepthProperty.DISTINCT_FILTER_DEPTH.evaluate(a));
int distinctFilterPositionCompare = Integer.compare(ExpressionDepthProperty.distinctDepth().evaluate(b),
ExpressionDepthProperty.distinctDepth().evaluate(a));
if (distinctFilterPositionCompare != 0) {
return distinctFilterPositionCompare;
}

int ufpA = UnmatchedFieldsCountProperty.evaluate(a);
int ufpB = UnmatchedFieldsCountProperty.evaluate(b);
int ufpA = unmatchedFieldsCount().evaluate(a);
int ufpB = unmatchedFieldsCount().evaluate(b);
if (ufpA != ufpB) {
return Integer.compare(ufpA, ufpB);
}
Expand Down Expand Up @@ -266,9 +268,9 @@ public int compare(@Nonnull RelationalExpression a, @Nonnull RelationalExpressio

@Nonnull
private Cardinality maxOfMaxCardinalitiesOfAllDataAccesses(@Nonnull final Map<Class<? extends RelationalExpression>, Set<RelationalExpression>> planOpsMap) {
return FindExpressionProperty.slice(planOpsMap, RecordQueryScanPlan.class, RecordQueryPlanWithIndex.class, RecordQueryCoveringIndexPlan.class)
return FindExpressionVisitor.slice(planOpsMap, RecordQueryScanPlan.class, RecordQueryPlanWithIndex.class, RecordQueryCoveringIndexPlan.class)
.stream()
.map(plan -> CardinalitiesProperty.evaluate(plan).getMaxCardinality())
.map(plan -> cardinalities().evaluate(plan).getMaxCardinality())
.reduce(Cardinality.ofCardinality(0),
(l, r) -> {
if (l.isUnknown()) {
Expand Down Expand Up @@ -314,8 +316,8 @@ private OptionalInt comparePrimaryScanToIndexScan(@Nonnull RelationalExpression
isSingularIndexScanWithFetch(planOpsMapIndexScan)) {

if (typeFilterCountPrimaryScan > 0 && typeFilterCountIndexScan == 0) {
final var primaryScanComparisons = ComparisonsProperty.evaluate(primaryScan);
final var indexScanComparisons = ComparisonsProperty.evaluate(indexScan);
final var primaryScanComparisons = comparisons().evaluate(primaryScan);
final var indexScanComparisons = comparisons().evaluate(indexScan);

//
// The primary scan side has a type filter in it, the index scan side does not. The primary side
Expand Down Expand Up @@ -373,7 +375,7 @@ private OptionalInt compareInOperator(@Nonnull final RelationalExpression leftEx

// If no scan comparison on the in union side uses a comparison to the in-values, then the in union
// plan is not useful.
final Set<Comparisons.Comparison> scanComparisonsSet = ComparisonsProperty.evaluate(leftExpression);
final Set<Comparisons.Comparison> scanComparisonsSet = comparisons().evaluate(leftExpression);

final ImmutableSet<CorrelationIdentifier> scanComparisonsCorrelatedTo =
scanComparisonsSet
Expand Down Expand Up @@ -429,6 +431,6 @@ private static OptionalInt flipFlop(final Supplier<OptionalInt> variantA,

@SafeVarargs
private static int count(@Nonnull final Map<Class<? extends RelationalExpression>, Set<RelationalExpression>> expressionsMap, @Nonnull final Class<? extends RelationalExpression>... interestingClasses) {
return FindExpressionProperty.slice(expressionsMap, interestingClasses).size();
return FindExpressionVisitor.slice(expressionsMap, interestingClasses).size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.apple.foundationdb.record.query.IndexQueryabilityFilter;
import com.apple.foundationdb.record.query.ParameterRelationshipGraph;
import com.apple.foundationdb.record.query.RecordQuery;
import com.apple.foundationdb.record.query.plan.explain.ExplainPlanVisitor;
import com.apple.foundationdb.record.query.plan.QueryPlanConstraint;
import com.apple.foundationdb.record.query.plan.QueryPlanInfo;
import com.apple.foundationdb.record.query.plan.QueryPlanInfoKeys;
Expand All @@ -42,11 +41,12 @@
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger.Location;
import com.apple.foundationdb.record.query.plan.cascades.debug.RestartException;
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraphProperty;
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraphVisitor;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.apple.foundationdb.record.query.plan.cascades.matching.structure.BindingMatcher;
import com.apple.foundationdb.record.query.plan.cascades.matching.structure.PlannerBindings;
import com.apple.foundationdb.record.query.plan.cascades.matching.structure.ReferenceMatchers;
import com.apple.foundationdb.record.query.plan.cascades.explain.ExplainPlanVisitor;
import com.apple.foundationdb.record.query.plan.plans.QueryPlan;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan;
import com.google.common.base.Suppliers;
Expand Down Expand Up @@ -83,7 +83,7 @@
* Like many optimization frameworks, Cascades is driven by sets of {@link CascadesRule}s that can be defined for
* {@link RelationalExpression}s, {@link PartialMatch}es and {@link MatchPartition}s, each of which describes a
* particular transformation and encapsulates the logic for determining its applicability and applying it. The planner
* searches through its {@link PlannerRuleSet} to find a matching rule and then executes that rule, creating zero or
* searches through its {@link PlanningRuleSet} to find a matching rule and then executes that rule, creating zero or
* more additional {@code PlannerExpression}s and/or zero or more additional {@link PartialMatch}es. A rule is defined by:
* </p>
* <ul>
Expand Down Expand Up @@ -206,7 +206,7 @@ public class CascadesPlanner implements QueryPlanner {
@Nonnull
private final RecordStoreState recordStoreState;
@Nonnull
private final PlannerRuleSet ruleSet;
private final PlanningRuleSet ruleSet;
@Nonnull
private Reference currentRoot;
@Nonnull
Expand All @@ -222,7 +222,7 @@ public CascadesPlanner(@Nonnull RecordMetaData metaData, @Nonnull RecordStoreSta
this(metaData, recordStoreState, defaultPlannerRuleSet());
}

public CascadesPlanner(@Nonnull RecordMetaData metaData, @Nonnull RecordStoreState recordStoreState, @Nonnull PlannerRuleSet ruleSet) {
public CascadesPlanner(@Nonnull RecordMetaData metaData, @Nonnull RecordStoreState recordStoreState, @Nonnull PlanningRuleSet ruleSet) {
this.configuration = RecordQueryPlannerConfiguration.builder().build();
this.metaData = metaData;
this.recordStoreState = recordStoreState;
Expand Down Expand Up @@ -375,7 +375,7 @@ private RecordQueryPlan resultOrFail() {
if (singleRoot instanceof RecordQueryPlan) {
if (logger.isDebugEnabled()) {
logger.debug(KeyValueLogMessage.of("GML explain of plan",
"explain", PlannerGraphProperty.explain(singleRoot)));
"explain", PlannerGraphVisitor.explain(singleRoot)));
logger.debug(KeyValueLogMessage.of("string explain of plan",
"explain", ExplainPlanVisitor.toStringForDebugging((RecordQueryPlan)singleRoot)));
}
Expand Down Expand Up @@ -656,7 +656,7 @@ public RelationalExpression getExpression() {
}

@Nonnull
protected PlannerRuleSet getRules() {
protected PlanningRuleSet getRules() {
return ruleSet;
}
}
Expand Down Expand Up @@ -1125,10 +1125,10 @@ public String toString() {

/**
* Returns the default set of transformation rules.
* @return a {@link PlannerRuleSet} using the default set of transformation rules
* @return a {@link PlanningRuleSet} using the default set of transformation rules
*/
@Nonnull
public static PlannerRuleSet defaultPlannerRuleSet() {
return PlannerRuleSet.DEFAULT;
public static PlanningRuleSet defaultPlannerRuleSet() {
return PlanningRuleSet.DEFAULT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public CascadesRule(@Nonnull BindingMatcher<T> matcher, Collection<PlannerConstr
* Returns the class of the operator at the root of the binding expression, if this rule uses a non-trivial binding.
* Used primarily for indexing rules for more efficient rule search.
* @return the class of the root of this rule's binding, or <code>Optional.empty()</code> if the rule matches anything
* @see PlannerRuleSet
* @see PlanningRuleSet
*/
@Nonnull
@Override
Expand Down
Loading