Skip to content

Commit 45ed7ba

Browse files
authored
Don't memoize correlation sets in planner objects (#3365)
We should not use a memoizing supplier to cache the correlation sets of a quantifier since the reference the quantifier ranges over is mutable with respect to the contained expressions. While these expressions are rarely exposing new correlations it may be that a new expression is added that has no correlations at all. If we prune to that expression later, the correlation set, if memoized, is wrong. Example ``` SELECT * FROM A, (SELECT * FROM B WHERE A = 5 AND FALSE) ``` The inner ``` ...(SELECT * FROM B WHERE A = 5 AND FALSE)... ``` has `B` in its correlation set. That expression, however, can also be simplified to: ``` ...(SELECT * FROM B WHERE FALSE)... ``` (and even further). The correlation set is empty. This PR deals with the change of removing the `MemoizingSupplier` as well as the fallout from now exposed bugs.
1 parent da26d61 commit 45ed7ba

21 files changed

+501
-491
lines changed

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,6 @@ public abstract class Quantifier implements Correlated<Quantifier> {
9494
@Nonnull
9595
private final CorrelationIdentifier alias;
9696

97-
/**
98-
* As a quantifier is immutable, the correlated set can be computed lazily and then cached. This supplier
99-
* represents that cached set.
100-
*/
101-
@Nonnull
102-
private final Supplier<Set<CorrelationIdentifier>> correlatedToSupplier;
103-
10497
/**
10598
* As a quantifier is immutable, the columns that flow along the quantifier can be lazily computed.
10699
*/
@@ -584,7 +577,6 @@ public static Physical physical(@Nonnull final Reference reference,
584577

585578
protected Quantifier(@Nonnull final CorrelationIdentifier alias) {
586579
this.alias = alias;
587-
this.correlatedToSupplier = Suppliers.memoize(() -> getRangesOver().getCorrelatedTo());
588580
this.flowedColumnsSupplier = Suppliers.memoize(this::computeFlowedColumns);
589581
this.flowedValuesSupplier = Suppliers.memoize(this::computeFlowedValues);
590582
// Call debugger hook for this new quantifier.
@@ -672,7 +664,7 @@ public String toString() {
672664
@Nonnull
673665
@Override
674666
public Set<CorrelationIdentifier> getCorrelatedTo() {
675-
return correlatedToSupplier.get();
667+
return getRangesOver().getCorrelatedTo();
676668
}
677669

678670
/**

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -704,10 +704,26 @@ private static boolean isMemoizedExpression(@Nonnull final RelationalExpression
704704
return false;
705705
}
706706

707+
//
708+
// We need to check if the expressions' correlatedTo sets are identical under the consideration of the given
709+
// map of alias equivalences. If they are not, the member is not memoizing the otherExpression.
710+
//
711+
// Specific example:
712+
// SELECT *
713+
// FROM A a, SELECT * FROM B WHERE a.x = b.y
714+
//
715+
// Let's assume the inner 'SELECT * FROM B WHERE a.x = b.y' already is member (in the memoization structure).
716+
// This member is correlated to 'a'.
717+
// However, if we didn't do the check for correlation sets here, we would satisfy the following otherExpression:
718+
// SELECT * FROM A WHERE a.x = b.y is correlated to b
719+
//
707720
final Set<CorrelationIdentifier> originalCorrelatedTo = member.getCorrelatedTo();
708-
final Set<CorrelationIdentifier> translatedCorrelatedTo = equivalenceMap.definesOnlyIdentities()
721+
final Set<CorrelationIdentifier> translatedCorrelatedTo =
722+
equivalenceMap.definesOnlyIdentities()
709723
? originalCorrelatedTo
710-
: originalCorrelatedTo.stream().map(id -> equivalenceMap.getTargetOrDefault(id, id)).collect(ImmutableSet.toImmutableSet());
724+
: originalCorrelatedTo.stream()
725+
.map(alias -> equivalenceMap.getTargetOrDefault(alias, alias))
726+
.collect(ImmutableSet.toImmutableSet());
711727
if (!translatedCorrelatedTo.equals(otherExpression.getCorrelatedTo())) {
712728
return false;
713729
}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ static Optional<Value> pushValueThroughFetch(@Nonnull final Value toBePushedValu
5454
@Nonnull final CorrelationIdentifier sourceAlias,
5555
@Nonnull final CorrelationIdentifier targetAlias,
5656
@Nonnull final Iterable<? extends Value> providedValuesFromIndex) {
57-
if (!isOfPushableTypes(toBePushedValue)) {
57+
if (!isOfPushableTypesOrConstant(toBePushedValue, sourceAlias)) {
5858
return Optional.empty();
5959
}
6060

@@ -76,12 +76,16 @@ static Optional<Value> pushValueThroughFetch(@Nonnull final Value toBePushedValu
7676
return translatedValueOptional.filter(translatedValue -> !translatedValue.getCorrelatedTo().contains(sourceAlias));
7777
}
7878

79-
private static boolean isOfPushableTypes(@Nonnull Value toBePushedValue) {
79+
private static boolean isOfPushableTypesOrConstant(@Nonnull final Value toBePushedValue,
80+
@Nonnull final CorrelationIdentifier sourceAlias) {
81+
if (!toBePushedValue.getCorrelatedTo().contains(sourceAlias)) {
82+
return true;
83+
}
8084
if (toBePushedValue instanceof FieldValue) {
8185
return true;
8286
} else if (toBePushedValue instanceof RecordConstructorValue) {
8387
return ((RecordConstructorValue)toBePushedValue).getColumns().stream()
84-
.allMatch(column -> isOfPushableTypes(column.getValue()));
88+
.allMatch(column -> isOfPushableTypesOrConstant(column.getValue(), sourceAlias));
8589
} else {
8690
// Effectively, this check is needed because of values like the VersionValue, which aren't
8791
// accessible without a record fetch, even if the index entry contains a VersionValue. We

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValue;
3333
import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate;
3434
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
35-
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
3635
import com.apple.foundationdb.record.query.plan.plans.RecordQueryFetchFromPartialRecordPlan;
3736
import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan;
3837
import com.apple.foundationdb.record.query.plan.plans.RecordQueryPredicatesFilterPlan;
@@ -43,7 +42,6 @@
4342
import javax.annotation.Nonnull;
4443
import javax.annotation.Nullable;
4544
import java.util.List;
46-
import java.util.Objects;
4745
import java.util.Optional;
4846

4947
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.QuantifierMatchers.physicalQuantifier;
@@ -229,21 +227,24 @@ public void onMatch(@Nonnull final ImplementationCascadesRuleCall call) {
229227
}
230228

231229
@Nullable
232-
private QueryPredicate pushLeafPredicate(@Nonnull RecordQueryFetchFromPartialRecordPlan fetchPlan,
233-
@Nonnull CorrelationIdentifier oldInnerAlias,
234-
@Nonnull CorrelationIdentifier newInnerAlias,
230+
private QueryPredicate pushLeafPredicate(@Nonnull final RecordQueryFetchFromPartialRecordPlan fetchPlan,
231+
@Nonnull final CorrelationIdentifier oldInnerAlias,
232+
@Nonnull final CorrelationIdentifier newInnerAlias,
235233
@Nonnull final QueryPredicate leafPredicate) {
236234
if (!(leafPredicate instanceof PredicateWithValue)) {
237235
// Only values depend on aliases -- returning this leaf is ok as it
238236
// appears to be pushable as is.
239237
return leafPredicate;
240238
}
241-
final PredicateWithValue predicateWithValue = (PredicateWithValue)leafPredicate;
242-
243-
final Value value = Objects.requireNonNull(predicateWithValue.getValue());
244-
final Optional<Value> pushedValueOptional = fetchPlan.pushValue(value, oldInnerAlias, newInnerAlias);
239+
final var predicateWithValue = (PredicateWithValue)leafPredicate;
240+
final var pushedLeafPredicateOptional =
241+
predicateWithValue.translateValueAndComparisonsMaybe(
242+
value -> fetchPlan.pushValue(value, oldInnerAlias, newInnerAlias),
243+
comparison -> comparison.replaceValuesMaybe(value -> {
244+
return fetchPlan.pushValue(value, oldInnerAlias, newInnerAlias);
245+
}));
245246
// Something went wrong when attempting to push this value through the fetch.
246247
// We must return null to prevent pushing of this conjunct.
247-
return pushedValueOptional.map(predicateWithValue::withValue).orElse(null);
248+
return pushedLeafPredicateOptional.orElse(null);
248249
}
249250
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,16 @@ public IndexedValue() {
5454
this(Type.primitiveType(Type.TypeCode.UNKNOWN));
5555
}
5656

57+
public IndexedValue(@Nonnull final Type resultType) {
58+
this.resultType = resultType;
59+
}
60+
5761
@Nonnull
5862
@Override
5963
protected Iterable<? extends Value> computeChildren() {
6064
return ImmutableList.of();
6165
}
6266

63-
public IndexedValue(@Nonnull final Type resultType) {
64-
this.resultType = resultType;
65-
}
66-
6767
@Nonnull
6868
@Override
6969
public Type getResultType() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public <M extends Message> Object eval(@Nullable final FDBRecordStoreBase<M> sto
7575
@Nonnull
7676
@Override
7777
protected Iterable<? extends Value> computeChildren() {
78-
return ImmutableList.of();
78+
return ImmutableList.of(in);
7979
}
8080

8181
@Nonnull

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/plans/RecordQueryAggregateIndexPlan.java

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@
4747
import com.apple.foundationdb.record.query.plan.cascades.FinalMemoizer;
4848
import com.apple.foundationdb.record.query.plan.cascades.MatchCandidate;
4949
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
50+
import com.apple.foundationdb.record.query.plan.cascades.explain.ExplainPlanVisitor;
5051
import com.apple.foundationdb.record.query.plan.cascades.explain.NodeInfo;
5152
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraph;
5253
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
53-
import com.apple.foundationdb.record.query.plan.cascades.explain.ExplainPlanVisitor;
5454
import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository;
5555
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
5656
import com.apple.foundationdb.record.query.plan.cascades.values.translation.TranslationMap;
@@ -83,11 +83,14 @@ public class RecordQueryAggregateIndexPlan implements RecordQueryPlanWithNoChild
8383
private final String recordTypeName;
8484
@Nonnull
8585
private final IndexKeyValueToPartialRecord toRecord;
86+
// TODO the following value should not be part of this plan
87+
// https://github.com/FoundationDB/fdb-record-layer/issues/3367
8688
@Nonnull
8789
private final Value resultValue;
90+
// TODO the following value should not be part of this plan
91+
// https://github.com/FoundationDB/fdb-record-layer/issues/3367
8892
@Nonnull
8993
private final Value groupByResultValue;
90-
9194
@Nonnull
9295
private final QueryPlanConstraint constraint;
9396

@@ -227,6 +230,7 @@ public boolean hasLoadBykeys() {
227230
@Nonnull
228231
@Override
229232
public Value getResultValue() {
233+
// TODO this is bad since the derivations property or others might pick this up without understanding it
230234
return resultValue;
231235
}
232236

@@ -241,8 +245,6 @@ public String toString() {
241245
public Set<CorrelationIdentifier> getCorrelatedTo() {
242246
final var result = ImmutableSet.<CorrelationIdentifier>builder();
243247
result.addAll(indexPlan.getCorrelatedTo());
244-
result.addAll(resultValue.getCorrelatedTo());
245-
result.addAll(groupByResultValue.getCorrelatedTo());
246248
return result.build();
247249
}
248250

@@ -260,13 +262,11 @@ public RecordQueryAggregateIndexPlan translateCorrelations(@Nonnull final Transl
260262

261263
final var translatedIndexPlan = indexPlan.translateCorrelations(translationMap,
262264
shouldSimplifyValues, translatedQuantifiers);
263-
final var newResultValue = resultValue.translateCorrelations(translationMap, shouldSimplifyValues);
264-
final var newGroupByResultValue = groupByResultValue.translateCorrelations(translationMap, shouldSimplifyValues);
265265

266266
// this is ok as there are no new quantifiers
267-
if (translatedIndexPlan != indexPlan || newResultValue != resultValue || newGroupByResultValue != groupByResultValue) {
268-
return new RecordQueryAggregateIndexPlan(translatedIndexPlan, recordTypeName, toRecord, newResultValue,
269-
newGroupByResultValue, constraint);
267+
if (translatedIndexPlan != indexPlan) {
268+
return new RecordQueryAggregateIndexPlan(translatedIndexPlan, recordTypeName, toRecord, resultValue,
269+
groupByResultValue, constraint);
270270
}
271271
return this;
272272
}
@@ -297,9 +297,7 @@ public boolean equalsWithoutChildren(@Nonnull RelationalExpression otherExpressi
297297
final RecordQueryAggregateIndexPlan other = (RecordQueryAggregateIndexPlan) otherExpression;
298298
return indexPlan.structuralEquals(other.indexPlan, equivalencesMap) &&
299299
recordTypeName.equals(other.recordTypeName) &&
300-
toRecord.equals(other.toRecord) &&
301-
resultValue.semanticEquals(other.resultValue, equivalencesMap) &&
302-
groupByResultValue.semanticEquals(other.groupByResultValue, equivalencesMap);
300+
toRecord.equals(other.toRecord);
303301
}
304302

305303
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
@@ -315,7 +313,7 @@ public int hashCode() {
315313

316314
@Override
317315
public int hashCodeWithoutChildren() {
318-
return Objects.hash(indexPlan, recordTypeName, toRecord, resultValue, groupByResultValue);
316+
return Objects.hash(indexPlan, recordTypeName, toRecord);
319317
}
320318

321319
@Override

fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBNestedRepeatedQueryTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1672,7 +1672,8 @@ void chooseCorrectAggregateIndex() {
16721672
// Verify that the byKey plan chooses the index that keeps entries together
16731673
final RecordQueryPlan byKeyPlan = planGraph(this::querySumIntValueByKey);
16741674
assertThat(byKeyPlan.getUsedIndexes(), contains(SUM_VALUE_BY_KEY));
1675-
assertEquals(byKeyPlan, planGraph(this::querySumIntValueByKey, SUM_VALUE_BY_KEY));
1675+
final var actual = planGraph(this::querySumIntValueByKey, SUM_VALUE_BY_KEY);
1676+
assertEquals(byKeyPlan, actual);
16761677

16771678
// Verify that the other byWholeRecord plan chooses the index that does not keep entries together
16781679
final RecordQueryPlan byWholeRecordPlan = planGraph(this::querySumIntValueForRecordByKey);

fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/ValueComputationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ void testPullUpValue2() {
174174
final var new_x_b = FieldValue.ofFieldNames(upperCurrentValue, ImmutableList.of("_1", "x", "xb"));
175175

176176
final var expectedMap = ImmutableMap.of(
177-
record_type, new RecordTypeValue(QuantifiedObjectValue.of(UPPER_ALIAS, new Type.AnyRecord(true))),
177+
record_type, new RecordTypeValue(FieldValue.ofFieldName(upperCurrentValue, "_1")),
178178
_a_ab__plus__x_xb, (Value)new ArithmeticValue.AddFn().encapsulate(ImmutableList.of(new_a_b, new_x_b)));
179179
Assertions.assertEquals(expectedMap, resultsMap);
180180
}

0 commit comments

Comments
 (0)