Skip to content

Commit 16776ea

Browse files
authored
fix for pull up of null-on-empty bug (#3323)
1 parent 5a65936 commit 16776ea

32 files changed

+1406
-1375
lines changed

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,11 @@
5252
import java.util.stream.StreamSupport;
5353

5454
/**
55-
* <p>
5655
* The <em>memo</em> data structure can compactly represent a large set of similar {@link RelationalExpression}s through
5756
* careful memoization. The Cascades "group expression", represented by the {@code Reference}, is the key to
5857
* that memoization by sharing optimization work on a sub-expression with other parts of the expression that reference
5958
* the same sub-expression.
60-
* </p>
61-
*
62-
* <p>
59+
* <br>
6360
* The reference abstraction is designed to make it difficult for authors of rules to mutate group expressions directly,
6461
* which is undefined behavior. Note that a {@link Reference} cannot be "de-referenced" using the {@link #get()}
6562
* method if it contains more than one member. Expressions with more than one member should not be used outside of the

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

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,9 @@
2424
import com.apple.foundationdb.record.query.plan.cascades.CascadesRuleCall;
2525
import com.apple.foundationdb.record.query.plan.cascades.GraphExpansion;
2626
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
27-
import com.apple.foundationdb.record.query.plan.cascades.Reference;
2827
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
2928
import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
3029
import com.apple.foundationdb.record.query.plan.cascades.matching.structure.BindingMatcher;
31-
import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan;
32-
import com.google.common.collect.ImmutableList;
3330
import com.google.common.collect.Iterables;
3431

3532
import javax.annotation.Nonnull;
@@ -71,21 +68,26 @@ public void onMatch(@Nonnull final CascadesRuleCall call) {
7168
final var bindings = call.getBindings();
7269
final var selectExpression = bindings.get(root);
7370
final var quantifier = bindings.get(defaultOnEmptyQuantifier);
74-
final var childExpressions = quantifier.getRangesOver().getMembers().stream()
75-
.filter(expression -> isPermittedToPullFrom(selectExpression, quantifier, expression))
76-
.collect(ImmutableList.toImmutableList());
77-
if (childExpressions.isEmpty()) {
78-
// it is impossible to pull the expression up, or it might introduce infinite recursion, bailout.
79-
return;
71+
final var childrenExpressions = quantifier.getRangesOver().getMembers();
72+
boolean pullUpDesired = false;
73+
for (final var childExpression : childrenExpressions) {
74+
final var childClassification =
75+
classifyExpression(selectExpression, quantifier, childExpression);
76+
if (childClassification == ExpressionClassification.DO_NOT_PULL_UP) {
77+
return;
78+
}
79+
if (childClassification == ExpressionClassification.PULL_UP) {
80+
pullUpDesired = true;
81+
}
8082
}
81-
82-
final Quantifier.ForEach newChildrenQuantifier;
83-
if (childExpressions.size() < quantifier.getRangesOver().getMembers().size()) {
84-
newChildrenQuantifier = Quantifier.forEach(Reference.from(childExpressions), quantifier.getAlias());
85-
} else {
86-
newChildrenQuantifier = Quantifier.forEachBuilder().withAlias(quantifier.getAlias()).build(quantifier.getRangesOver());
83+
if (!pullUpDesired) {
84+
return;
8785
}
88-
call.memoizeReference(newChildrenQuantifier.getRangesOver());
86+
87+
final var newChildrenQuantifier =
88+
Quantifier.forEachBuilder()
89+
.withAlias(quantifier.getAlias())
90+
.build(quantifier.getRangesOver());
8991

9092
// Create the lower select expression.
9193
final var newSelectExpression = call.memoizeExpression(GraphExpansion.builder()
@@ -102,27 +104,32 @@ public void onMatch(@Nonnull final CascadesRuleCall call) {
102104
call.yieldExpression(topLevelSelectExpression);
103105
}
104106

105-
public boolean isPermittedToPullFrom(@Nonnull final SelectExpression selectOnTopExpression,
106-
@Nonnull final Quantifier.ForEach quantifier,
107-
@Nonnull final RelationalExpression expression) {
108-
if (expression instanceof RecordQueryPlan) {
109-
return false;
110-
}
107+
private ExpressionClassification classifyExpression(@Nonnull final SelectExpression selectOnTopExpression,
108+
@Nonnull final Quantifier.ForEach quantifier,
109+
@Nonnull final RelationalExpression expression) {
111110
if (!(expression instanceof SelectExpression)) {
112-
return true;
111+
return ExpressionClassification.DO_NOT_CARE;
113112
}
114113

115114
final var selectExpression = (SelectExpression)expression;
116115
if (selectExpression.getQuantifiers().size() > 1) {
117-
return true;
116+
return ExpressionClassification.PULL_UP;
118117
}
119118
if (!Iterables.getOnlyElement(selectExpression.getQuantifiers()).getAlias().equals(quantifier.getAlias())) {
120-
return true;
119+
return ExpressionClassification.PULL_UP;
121120
}
122121

123122
// if all predicates are not the same, bail out, otherwise, we can pull up.
124123
final var predicates = selectOnTopExpression.getPredicates();
125124
final var otherPredicates = selectExpression.getPredicates();
126-
return !predicates.equals(otherPredicates);
125+
return !predicates.equals(otherPredicates)
126+
? ExpressionClassification.PULL_UP
127+
: ExpressionClassification.DO_NOT_PULL_UP;
128+
}
129+
130+
private enum ExpressionClassification {
131+
DO_NOT_CARE,
132+
DO_NOT_PULL_UP,
133+
PULL_UP
127134
}
128135
}

0 commit comments

Comments
 (0)