Skip to content

Commit 3b65482

Browse files
committed
Consider number of predicates at each level in rewriting cost model
1 parent 190700a commit 3b65482

File tree

6 files changed

+367
-117
lines changed

6 files changed

+367
-117
lines changed

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/RewritingCostModel.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@
2424
import com.apple.foundationdb.annotation.SpotBugsSuppressWarnings;
2525
import com.apple.foundationdb.record.query.plan.RecordQueryPlannerConfiguration;
2626
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
27+
import com.apple.foundationdb.record.query.plan.cascades.properties.PredicateCountByLevelProperty;
2728

2829
import javax.annotation.Nonnull;
2930

3031
import static com.apple.foundationdb.record.query.plan.cascades.properties.ExpressionCountProperty.selectCount;
3132
import static com.apple.foundationdb.record.query.plan.cascades.properties.ExpressionCountProperty.tableFunctionCount;
3233
import static com.apple.foundationdb.record.query.plan.cascades.properties.PredicateComplexityProperty.predicateComplexity;
33-
import static com.apple.foundationdb.record.query.plan.cascades.properties.PredicateHeightProperty.predicateHeight;
34+
import static com.apple.foundationdb.record.query.plan.cascades.properties.PredicateCountByLevelProperty.predicateCountByLevel;
35+
import static com.apple.foundationdb.record.query.plan.cascades.properties.PredicateCountProperty.predicateCount;
3436

3537
/**
3638
* Cost model for {@link PlannerPhase#REWRITING}. TODO To be fleshed out whe we have actual rules.
@@ -72,12 +74,23 @@ public int compare(final RelationalExpression a, final RelationalExpression b) {
7274
}
7375

7476
//
75-
// Pick the expression where predicates have been pushed down as far as they can go
77+
// Pick the expression where that has the least number of predicates overall
7678
//
77-
int aPredicateHeight = predicateHeight().evaluate(a);
78-
int bPredicateHeight = predicateHeight().evaluate(b);
79-
if (aPredicateHeight != bPredicateHeight) {
80-
return Integer.compare(aPredicateHeight, bPredicateHeight);
79+
int aPredicateCount = predicateCount().evaluate(a);
80+
int bPredicateCount = predicateCount().evaluate(b);
81+
if (aPredicateCount != bPredicateCount) {
82+
return Integer.compare(aPredicateCount, bPredicateCount);
83+
}
84+
85+
//
86+
// Pick the expression where it has the higher number of predicates at a lower level
87+
//
88+
PredicateCountByLevelProperty.PredicateCountByLevelInfo aPredicateCountByLevel = predicateCountByLevel().evaluate(a);
89+
PredicateCountByLevelProperty.PredicateCountByLevelInfo bPredicateCountByLevel = predicateCountByLevel().evaluate(b);
90+
int levelByLevelComparison = PredicateCountByLevelProperty.PredicateCountByLevelInfo.compare(aPredicateCountByLevel, bPredicateCountByLevel);
91+
if (levelByLevelComparison != 0) {
92+
// Choose the expression that has more predicates at a lower level
93+
return -levelByLevelComparison;
8194
}
8295

8396
//
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* PredicateCountByLevelProperty.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.record.query.plan.cascades.properties;
22+
23+
import com.apple.foundationdb.record.query.plan.cascades.ExpressionProperty;
24+
import com.apple.foundationdb.record.query.plan.cascades.Reference;
25+
import com.apple.foundationdb.record.query.plan.cascades.SimpleExpressionVisitor;
26+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
27+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpressionVisitor;
28+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpressionWithPredicates;
29+
import com.google.common.collect.ImmutableMap;
30+
import com.google.common.collect.Iterables;
31+
32+
import javax.annotation.Nonnull;
33+
import java.util.Collection;
34+
import java.util.Collections;
35+
import java.util.HashMap;
36+
import java.util.List;
37+
import java.util.Map;
38+
import java.util.function.BiFunction;
39+
40+
public class PredicateCountByLevelProperty implements ExpressionProperty<PredicateCountByLevelProperty.PredicateCountByLevelInfo> {
41+
private static final PredicateCountByLevelProperty PREDICATE_COUNT_BY_LEVEL = new PredicateCountByLevelProperty();
42+
43+
private PredicateCountByLevelProperty() {
44+
// prevent outside instantiation
45+
}
46+
47+
@Nonnull
48+
@Override
49+
public RelationalExpressionVisitor<PredicateCountByLevelInfo> createVisitor() {
50+
return PredicateCountByLevelVisitor.VISITOR;
51+
}
52+
53+
public PredicateCountByLevelInfo evaluate(RelationalExpression expression) {
54+
return createVisitor().visit(expression);
55+
}
56+
57+
@Nonnull
58+
public static PredicateCountByLevelProperty predicateCountByLevel() {
59+
return PREDICATE_COUNT_BY_LEVEL;
60+
}
61+
62+
public static final class PredicateCountByLevelInfo {
63+
private final Map<Integer, Integer> levelToPredicateCount;
64+
private final int highestLevel;
65+
66+
private PredicateCountByLevelInfo(Map<Integer, Integer> levelToPredicateCount, int highestLevel) {
67+
this.levelToPredicateCount = levelToPredicateCount;
68+
this.highestLevel = highestLevel;
69+
}
70+
71+
public static PredicateCountByLevelInfo combine(Collection<? extends PredicateCountByLevelInfo> heightInfos, BiFunction<Integer, Integer, Integer> combineFunc) {
72+
final Map<Integer, Integer> newLevelToComplexityMap = new HashMap<>();
73+
int heightLevel = 0;
74+
for (PredicateCountByLevelInfo heightInfo : heightInfos) {
75+
for (var entry : heightInfo.getLevelToPredicateCount().entrySet()) {
76+
newLevelToComplexityMap.put(entry.getKey(), combineFunc.apply(newLevelToComplexityMap.getOrDefault(entry.getKey(), 0), entry.getValue()));
77+
}
78+
heightLevel = Integer.max(heightLevel, heightInfo.getHighestLevel());
79+
}
80+
return new PredicateCountByLevelInfo(newLevelToComplexityMap, heightLevel);
81+
}
82+
83+
public Map<Integer, Integer> getLevelToPredicateCount() {
84+
return Collections.unmodifiableMap(levelToPredicateCount);
85+
}
86+
87+
public int getHighestLevel() {
88+
return highestLevel;
89+
}
90+
91+
/**
92+
* Compares two {@code PredicateHeightInfo} instances level by level.
93+
*
94+
* @param a the first {@code PredicateHeightInfo} to compare
95+
* @param b the second {@code PredicateHeightInfo} to compare
96+
* @return the value {@code 0} if {@code a} have the same number of predicates at each level as {@code b};
97+
* a value less than {@code 0} if a {@code a} has fewer predicates than {@code b} at a
98+
* lower level or if {@code a} has a lower number of levels with predicates; and
99+
* a value greater than {@code 0} if {@code b} has fewer predicates than {@code a} at a
100+
* lower level or if {@code b} has a lower number of levels with predicates.
101+
* @since 1.7
102+
*/
103+
public static int compare(PredicateCountByLevelInfo a, PredicateCountByLevelInfo b) {
104+
for (int currentLevel = 0; currentLevel < Integer.max(a.getHighestLevel(), b.getHighestLevel()); currentLevel++) {
105+
var aLevelComplexity = a.getLevelToPredicateCount().getOrDefault(currentLevel, 0);
106+
var bLevelComplexity = b.getLevelToPredicateCount().getOrDefault(currentLevel, 0);
107+
var levelComparison = Integer.compare(aLevelComplexity, bLevelComplexity);
108+
if (levelComparison != 0) {
109+
return levelComparison;
110+
}
111+
}
112+
return 0;
113+
}
114+
}
115+
116+
private static final class PredicateCountByLevelVisitor implements SimpleExpressionVisitor<PredicateCountByLevelInfo> {
117+
private static final PredicateCountByLevelVisitor VISITOR = new PredicateCountByLevelVisitor();
118+
119+
@Nonnull
120+
@Override
121+
public PredicateCountByLevelInfo evaluateAtExpression(@Nonnull final RelationalExpression expression, @Nonnull final List<PredicateCountByLevelInfo> childResults) {
122+
var lowerLevels = PredicateCountByLevelInfo.combine(childResults, Integer::sum);
123+
var currentLevel = childResults.stream().mapToInt(PredicateCountByLevelInfo::getHighestLevel).max().orElse(0) + 1;
124+
var currentLevelPredicates = 0;
125+
if (expression instanceof RelationalExpressionWithPredicates) {
126+
currentLevelPredicates = ((RelationalExpressionWithPredicates)expression).getPredicates().size();
127+
}
128+
return new PredicateCountByLevelInfo(
129+
ImmutableMap.<Integer, Integer>builder()
130+
.putAll(lowerLevels.getLevelToPredicateCount())
131+
.put(currentLevel, currentLevelPredicates)
132+
.build(),
133+
currentLevel
134+
);
135+
}
136+
137+
@Nonnull
138+
@Override
139+
public PredicateCountByLevelInfo evaluateAtRef(@Nonnull final Reference ref, @Nonnull final List<PredicateCountByLevelInfo> memberResults) {
140+
if (memberResults.size() == 1) {
141+
return Iterables.getOnlyElement(memberResults);
142+
}
143+
return PredicateCountByLevelInfo.combine(memberResults, Integer::max);
144+
}
145+
}
146+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* PredicateCountProperty.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.record.query.plan.cascades.properties;
22+
23+
import com.apple.foundationdb.record.query.plan.cascades.ExpressionProperty;
24+
import com.apple.foundationdb.record.query.plan.cascades.Reference;
25+
import com.apple.foundationdb.record.query.plan.cascades.SimpleExpressionVisitor;
26+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
27+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpressionVisitor;
28+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpressionWithPredicates;
29+
import com.google.common.collect.Iterables;
30+
31+
import javax.annotation.Nonnull;
32+
import java.util.Collection;
33+
import java.util.List;
34+
35+
public class PredicateCountProperty implements ExpressionProperty<Integer> {
36+
private static final PredicateCountProperty PREDICATE_COUNT = new PredicateCountProperty();
37+
38+
private PredicateCountProperty() {
39+
// prevent outside instantiation
40+
}
41+
42+
@Nonnull
43+
@Override
44+
public RelationalExpressionVisitor<Integer> createVisitor() {
45+
return PredicateCountVisitor.VISITOR;
46+
}
47+
48+
public int evaluate(RelationalExpression expression) {
49+
return createVisitor().visit(expression);
50+
}
51+
52+
@Nonnull
53+
public static PredicateCountProperty predicateCount() {
54+
return PREDICATE_COUNT;
55+
}
56+
57+
private static final class PredicateCountVisitor implements SimpleExpressionVisitor<Integer> {
58+
private static final PredicateCountVisitor VISITOR = new PredicateCountVisitor();
59+
60+
@Nonnull
61+
@Override
62+
public Integer evaluateAtExpression(@Nonnull final RelationalExpression expression, @Nonnull final List<Integer> childResults) {
63+
final var childPredicates = childResults.stream().mapToInt(Integer::intValue).sum();
64+
if (!(expression instanceof RelationalExpressionWithPredicates)) {
65+
return childPredicates;
66+
}
67+
return childPredicates + ((RelationalExpressionWithPredicates)expression).getPredicates().size();
68+
}
69+
70+
@Nonnull
71+
@Override
72+
public Integer evaluateAtRef(@Nonnull final Reference ref, @Nonnull final List<Integer> memberResults) {
73+
if (memberResults.size() == 1) {
74+
return Iterables.getOnlyElement(memberResults);
75+
}
76+
return memberResults.stream().mapToInt(Integer::intValue).max().orElse(0);
77+
}
78+
}
79+
}

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

Lines changed: 0 additions & 111 deletions
This file was deleted.

0 commit comments

Comments
 (0)