Skip to content
Open
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 @@ -285,8 +285,11 @@ public <P> Map<RecordQueryPlan, P> propertyValueForPlans(@Nonnull final Expressi
public static ExpressionPropertiesMap<RelationalExpression> defaultForRewritePhase() {
return new ExpressionPropertiesMap<>(RelationalExpression.class,
ImmutableSet.of(),
ImmutableSet.of(ExpressionCountProperty.selectCount(), ExpressionCountProperty.tableFunctionCount(),
PredicateComplexityProperty.predicateComplexity()),
ImmutableSet.of(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I think this should really selectCount and tableFunctionCount in there. I think predicateComplexity should not be in here but 🤷 . I think the new ones you are introducing should not be here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note that I had to leave predicateComplexity here because it is used in the SelectMergeRule in a code path that expects it to be tracked (

public static <E extends RelationalExpression> BiFunction<ExpressionPartition<E>, ? super E, Tuple> comparisonByPropertyList(@Nonnull ExpressionProperty<?>... expressionProperties) {
return (partition, expression) ->
Tuple.fromItems(Arrays.stream(expressionProperties)
.map(property -> partition.getNonPartitioningPropertyValue(expression, property))
.collect(Collectors.toList()));
).

ExpressionCountProperty.selectCount(),
ExpressionCountProperty.tableFunctionCount(),
PredicateComplexityProperty.predicateComplexity()
),
ImmutableList.of());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,21 @@
import com.apple.foundationdb.annotation.SpotBugsSuppressWarnings;
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.NormalizedResidualPredicateProperty;
import com.apple.foundationdb.record.query.plan.cascades.properties.PredicateCountByLevelProperty;

import javax.annotation.Nonnull;

import static com.apple.foundationdb.record.query.plan.cascades.properties.ExpressionCountProperty.selectCount;
import static com.apple.foundationdb.record.query.plan.cascades.properties.ExpressionCountProperty.tableFunctionCount;
import static com.apple.foundationdb.record.query.plan.cascades.properties.PredicateComplexityProperty.predicateComplexity;
import static com.apple.foundationdb.record.query.plan.cascades.properties.PredicateHeightProperty.predicateHeight;
import static com.apple.foundationdb.record.query.plan.cascades.properties.PredicateCountByLevelProperty.predicateCountByLevel;

/**
* Cost model for {@link PlannerPhase#REWRITING}. TODO To be fleshed out whe we have actual rules.
*/
@API(API.Status.EXPERIMENTAL)
@SpotBugsSuppressWarnings("SE_COMPARATOR_SHOULD_BE_SERIALIZABLE")
@SuppressWarnings("PMD.TooManyStaticImports")
public class RewritingCostModel implements CascadesCostModel {
@Nonnull
private final RecordQueryPlannerConfiguration configuration;
Expand Down Expand Up @@ -72,21 +74,26 @@ public int compare(final RelationalExpression a, final RelationalExpression b) {
}

//
// Pick the expression where predicates have been pushed down as far as they can go
// Pick the expression which has the least number of conjuncts in the normalized form of
// its combined query predicates.
//
int aPredicateHeight = predicateHeight().evaluate(a);
int bPredicateHeight = predicateHeight().evaluate(b);
if (aPredicateHeight != bPredicateHeight) {
return Integer.compare(aPredicateHeight, bPredicateHeight);
final long aNormalizedConjuncts = NormalizedResidualPredicateProperty.countNormalizedConjuncts(a);
final long bNormalizedConjuncts = NormalizedResidualPredicateProperty.countNormalizedConjuncts(b);
if (aNormalizedConjuncts != bNormalizedConjuncts) {
return Long.compare(aNormalizedConjuncts, bNormalizedConjuncts);
}

//
// Choose the expression with the simplest predicate.
// Pick the expression that has a higher number of query predicates at a deeper level of
// the expression tree.
//
int aPredicateComplexity = predicateComplexity().evaluate(a);
int bPredicateComplexity = predicateComplexity().evaluate(b);
if (aPredicateComplexity != bPredicateComplexity) {
return Integer.compare(aPredicateComplexity, bPredicateComplexity);
PredicateCountByLevelProperty.PredicateCountByLevelInfo aPredicateCountByLevel = predicateCountByLevel().evaluate(a);
PredicateCountByLevelProperty.PredicateCountByLevelInfo bPredicateCountByLevel = predicateCountByLevel().evaluate(b);
final int predicateCountByLevelComparison = PredicateCountByLevelProperty.PredicateCountByLevelInfo.compare(
bPredicateCountByLevel, aPredicateCountByLevel);
if (predicateCountByLevelComparison != 0) {
// The expression that has more predicates at a deeper level wins
return predicateCountByLevelComparison;
}

//
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*
* PredicateCountByLevelProperty.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.apple.foundationdb.record.query.plan.cascades.properties;

import com.apple.foundationdb.record.query.plan.cascades.ExpressionProperty;
import com.apple.foundationdb.record.query.plan.cascades.Reference;
import com.apple.foundationdb.record.query.plan.cascades.SimpleExpressionVisitor;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpressionWithPredicates;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;

import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;
import java.util.stream.Collectors;

/**
* <p>
* This property traverses a {@link RelationalExpression} to find the total number of
* {@link com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate}s associated with
* {@link RelationalExpressionWithPredicates} implementations at each level of the expression tree.
* </p>
* <p>
* Information about the number of predicates at each level of the tree is encoded in instances of
* {@link PredicateCountByLevelInfo}, which can be compared using the method {@link PredicateCountByLevelInfo#compare}
* to determine which expression trees contain more predicates at a deeper level.
* </p>
* <p>
* See also {@link com.apple.foundationdb.record.query.plan.cascades.rules.PredicatePushDownRule}.
* </p>
*/
public class PredicateCountByLevelProperty implements ExpressionProperty<PredicateCountByLevelProperty.PredicateCountByLevelInfo> {
@Nonnull
private static final PredicateCountByLevelProperty PREDICATE_COUNT_BY_LEVEL = new PredicateCountByLevelProperty();

private PredicateCountByLevelProperty() {
// prevent outside instantiation
}

/**
* Returns the singleton instance of {@link PredicateCountByLevelProperty}.
*
* @return the singleton instance of {@link PredicateCountByLevelProperty}
*/
@Nonnull
public static PredicateCountByLevelProperty predicateCountByLevel() {
return PREDICATE_COUNT_BY_LEVEL;
}

/**
* Creates a {@link SimpleExpressionVisitor} that can traverse a {@link RelationalExpression}
* to find the total number of {@link com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate}
* associated with {@link RelationalExpressionWithPredicates} implementations at each level of the expression tree.
*
* @return a {@link SimpleExpressionVisitor} that produces {@link PredicateCountByLevelInfo} results.
*/
@Nonnull
@Override
public SimpleExpressionVisitor<PredicateCountByLevelInfo> createVisitor() {
return PredicateCountByLevelVisitor.VISITOR;
}

/**
* Evaluate this property for over the given {@link RelationalExpression}.
*
* @param expression The root of the expression tree to traverse.
* @return a {@link PredicateCountByLevelInfo} containing the predicate count at each level
* of the expression tree.
*/
@Nonnull
public PredicateCountByLevelInfo evaluate(RelationalExpression expression) {
return Objects.requireNonNull(expression.acceptVisitor(createVisitor()));
}

/**
* <p>
* An object that contains information about the number of query predicates at each level
* of a {@link RelationalExpression} tree. Level numbers in instances of this class
* start from 1 for leaf nodes and increase towards the root, with the root node having the highest
* level number, which can be retrieved via {@link #getHighestLevel()}.
* </p>
* <p>
* Instances of this class are can be created for a {@link RelationalExpression} using the method
* {@link PredicateCountByLevelProperty#evaluate(RelationalExpression)}.
* </p>
*/
public static final class PredicateCountByLevelInfo {

Check warning on line 111 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/PredicateCountByLevelProperty.java

View check run for this annotation

fdb.teamscale.io / Teamscale | Findings

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/PredicateCountByLevelProperty.java#L111

Reduce this class from 32 lines to the maximum allowed 25 or externalize it in a public class https://fdb.teamscale.io/findings/details/foundationdb-fdb-record-layer?t=FORK_MR%2F3681%2Fhazefully%2Fimprove-rewriting-cost-model%3AHEAD&id=71E84DAE431A9A27F49B0E2020A82851
private final ImmutableSortedMap<Integer, Integer> levelToPredicateCount;

public PredicateCountByLevelInfo(Map<Integer, Integer> levelToPredicateCount) {
this.levelToPredicateCount = ImmutableSortedMap.copyOf(levelToPredicateCount);
}

/**
* Combine a list of {@link PredicateCountByLevelInfo} instances by summing the number of query predicates
* at the same level across all instances.
*
* @param heightInfos a collection of {@link PredicateCountByLevelInfo} instances.
* @return a new instance of {@link PredicateCountByLevelInfo} with the combined count.
*/
@Nonnull
public static PredicateCountByLevelInfo combine(Collection<PredicateCountByLevelInfo> heightInfos) {
return new PredicateCountByLevelInfo(heightInfos
.stream()
.flatMap(heightInfo -> heightInfo.getLevelToPredicateCount().entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum)));
}

/**
* <p>
* Get a view of the query predicate count at each level as a {@link SortedMap}.
* </p>
* <p>
* Level heights, which are the keys of the returned {@link SortedMap}, start from 1 for leaf nodes in the
* {@link RelationalExpression} used to create this instance, and increase towards the root, with the root node
* having the highest level number.
* </p>
*
* @return a {@link SortedMap} of level heights to the count of query predicates at that level.
*/
@Nonnull
public SortedMap<Integer, Integer> getLevelToPredicateCount() {
return levelToPredicateCount;
}

/**
* Retrieves the highest level height in the {@link RelationalExpression} tree used to create
* used to create this instance, which corresponds to the level of the root node of the expression tree.
*
* @return an integer the height of the highest level number in the tree, or 0 if no levels have been recorded.
*/
public int getHighestLevel() {
return levelToPredicateCount.isEmpty() ? 0 : levelToPredicateCount.lastKey();
}

/**
* <p>
* Compares two {@link PredicateCountByLevelInfo} instances level by level.
* </p>
* <p>
* This comparison is done by comparing the number of
* {@link com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate}s at each level recorded
* within the provided {@link PredicateCountByLevelInfo} instances, starting from the deepest level (representing
* the leaf nodes of the {@link RelationalExpression} tree used to create those instances) to the highest level
* (representing the root node) and returns the integer comparison between the first non-equal query predicate
* counts. If the number of query predicates is equal at each level, the integer comparison between the highest
* level in each {@link PredicateCountByLevelInfo} instance is returned instead.
* </p>
* @param a the first {@link PredicateCountByLevelInfo} to compare
* @param b the second {@link PredicateCountByLevelInfo} to compare
*
* @return the value {@code 0} if {@code a} have the same number of predicates at each level as {@code b};
* a value less than {@code 0} if {@code a} has fewer predicates than {@code b} at a
* deeper level or if {@code a} has fewer levels than {@code b}; and
* a value greater than {@code 0} if {@code a} has more predicates than {@code b} at a
* deeper level or if {@code a} has more levels than {@code b}.
*/
public static int compare(final PredicateCountByLevelInfo a, final PredicateCountByLevelInfo b) {
final SortedMap<Integer, Integer> aLevelToPredicateCount = a.getLevelToPredicateCount();
final SortedMap<Integer, Integer> bLevelToPredicateCount = b.getLevelToPredicateCount();
for (final var entry : aLevelToPredicateCount.entrySet()) {
final int aPredicateCountAtLevel = entry.getValue();
final int bPredicateCountAtLevel = bLevelToPredicateCount.getOrDefault(entry.getKey(), 0);
if (aPredicateCountAtLevel != bPredicateCountAtLevel) {
return Integer.compare(aPredicateCountAtLevel, bPredicateCountAtLevel);
}
}
return Integer.compare(a.getHighestLevel(), b.getHighestLevel());
}
}

private static final class PredicateCountByLevelVisitor implements SimpleExpressionVisitor<PredicateCountByLevelInfo> {

Check warning on line 196 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/PredicateCountByLevelProperty.java

View check run for this annotation

fdb.teamscale.io / Teamscale | Findings

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/properties/PredicateCountByLevelProperty.java#L196

Reduce this class from 28 lines to the maximum allowed 25 or externalize it in a public class https://fdb.teamscale.io/findings/details/foundationdb-fdb-record-layer?t=FORK_MR%2F3681%2Fhazefully%2Fimprove-rewriting-cost-model%3AHEAD&id=57D969DB933B1DADB4A732AE74D7D2FD
@Nonnull
private static final PredicateCountByLevelVisitor VISITOR = new PredicateCountByLevelVisitor();

private PredicateCountByLevelVisitor() {
// prevent outside instantiation
}

@Nonnull
@Override
public PredicateCountByLevelInfo evaluateAtExpression(@Nonnull final RelationalExpression expression,
@Nonnull final List<PredicateCountByLevelInfo> childResults) {
final var newLevelToPredicateCountMap = ImmutableMap.<Integer, Integer>builder()
.putAll(PredicateCountByLevelInfo.combine(childResults).getLevelToPredicateCount());
final var currentLevel = childResults
.stream().mapToInt(PredicateCountByLevelInfo::getHighestLevel).max().orElse(0) + 1;
final int currentLevelPredicates;
if (expression instanceof RelationalExpressionWithPredicates) {
currentLevelPredicates = ((RelationalExpressionWithPredicates)expression).getPredicates().size();
} else {
currentLevelPredicates = 0;
}
return new PredicateCountByLevelInfo(newLevelToPredicateCountMap.put(currentLevel, currentLevelPredicates).build());
}

@Nonnull
@Override
public PredicateCountByLevelInfo evaluateAtRef(@Nonnull final Reference reference, @Nonnull final List<PredicateCountByLevelInfo> memberResults) {
Verify.verify(memberResults.size() == 1);
return Iterables.getOnlyElement(memberResults);
}
}
}
Loading
Loading