Skip to content

Commit 0a05645

Browse files
committed
Add some Cascades support.
1 parent 6fe912b commit 0a05645

File tree

9 files changed

+516
-3
lines changed

9 files changed

+516
-3
lines changed

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementIntersectionRule;
3838
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementNestedLoopJoinRule;
3939
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementPhysicalScanRule;
40+
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementRecursiveRule;
4041
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementSimpleSelectRule;
4142
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementStreamingAggregationRule;
4243
import com.apple.foundationdb.record.query.plan.cascades.rules.ImplementTypeFilterRule;
@@ -66,6 +67,7 @@
6667
import com.apple.foundationdb.record.query.plan.cascades.rules.PushRequestedOrderingThroughGroupByRule;
6768
import com.apple.foundationdb.record.query.plan.cascades.rules.PushRequestedOrderingThroughInLikeSelectRule;
6869
import com.apple.foundationdb.record.query.plan.cascades.rules.PushRequestedOrderingThroughInsertRule;
70+
import com.apple.foundationdb.record.query.plan.cascades.rules.PushRequestedOrderingThroughRecursiveRule;
6971
import com.apple.foundationdb.record.query.plan.cascades.rules.PushRequestedOrderingThroughSelectExistentialRule;
7072
import com.apple.foundationdb.record.query.plan.cascades.rules.PushRequestedOrderingThroughSelectRule;
7173
import com.apple.foundationdb.record.query.plan.cascades.rules.PushRequestedOrderingThroughSortRule;
@@ -130,7 +132,8 @@ public class PlannerRuleSet {
130132
new PushRequestedOrderingThroughDeleteRule(),
131133
new PushRequestedOrderingThroughInsertRule(),
132134
new PushRequestedOrderingThroughUpdateRule(),
133-
new PushRequestedOrderingThroughUniqueRule()
135+
new PushRequestedOrderingThroughUniqueRule(),
136+
new PushRequestedOrderingThroughRecursiveRule()
134137
);
135138

136139
private static final List<CascadesRule<? extends RelationalExpression>> IMPLEMENTATION_RULES = ImmutableList.of(
@@ -167,7 +170,8 @@ public class PlannerRuleSet {
167170
new ImplementStreamingAggregationRule(),
168171
new ImplementDeleteRule(),
169172
new ImplementInsertRule(),
170-
new ImplementUpdateRule()
173+
new ImplementUpdateRule(),
174+
new ImplementRecursiveRule()
171175
);
172176

173177
private static final List<CascadesRule<? extends RelationalExpression>> EXPLORATION_RULES =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* RecursiveExpression.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2024 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.expressions;
22+
23+
import com.apple.foundationdb.annotation.API;
24+
import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
25+
import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
26+
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
27+
import com.apple.foundationdb.record.query.plan.cascades.explain.InternalPlannerGraphRewritable;
28+
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraph;
29+
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
30+
import com.apple.foundationdb.record.query.plan.cascades.values.Values;
31+
import com.apple.foundationdb.record.query.plan.cascades.values.translation.TranslationMap;
32+
import com.google.common.collect.ImmutableList;
33+
import com.google.common.collect.ImmutableMap;
34+
35+
import javax.annotation.Nonnull;
36+
import java.util.List;
37+
import java.util.Set;
38+
39+
/**
40+
* A recursive expression.
41+
*/
42+
@API(API.Status.EXPERIMENTAL)
43+
public class RecursiveExpression implements RelationalExpressionWithChildren, InternalPlannerGraphRewritable {
44+
@Nonnull
45+
private final Value resultValue;
46+
@Nonnull
47+
private final Quantifier rootQuantifier;
48+
@Nonnull
49+
private final Quantifier childQuantifier;
50+
51+
public RecursiveExpression(@Nonnull Value resultValue,
52+
@Nonnull Quantifier rootQuantifier,
53+
@Nonnull Quantifier childQuantifier) {
54+
this.resultValue = resultValue;
55+
this.rootQuantifier = rootQuantifier;
56+
this.childQuantifier = childQuantifier;
57+
}
58+
59+
@Nonnull
60+
@Override
61+
public Value getResultValue() {
62+
return resultValue;
63+
}
64+
65+
@Nonnull
66+
public List<? extends Value> getResultValues() {
67+
return Values.deconstructRecord(getResultValue());
68+
}
69+
70+
@Nonnull
71+
@Override
72+
public List<? extends Quantifier> getQuantifiers() {
73+
return List.of(rootQuantifier, childQuantifier);
74+
}
75+
76+
@Override
77+
public int getRelationalChildCount() {
78+
return 2;
79+
}
80+
81+
@Override
82+
public boolean canCorrelate() {
83+
return true;
84+
}
85+
86+
@Nonnull
87+
@Override
88+
public Set<CorrelationIdentifier> getCorrelatedToWithoutChildren() {
89+
return resultValue.getCorrelatedTo();
90+
}
91+
92+
@Nonnull
93+
@Override
94+
public RecursiveExpression translateCorrelations(@Nonnull final TranslationMap translationMap, @Nonnull final List<? extends Quantifier> translatedQuantifiers) {
95+
final Value translatedResultValue = resultValue.translateCorrelations(translationMap);
96+
return new RecursiveExpression(translatedResultValue, translatedQuantifiers.get(0), translatedQuantifiers.get(1));
97+
}
98+
99+
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
100+
@Override
101+
public boolean equals(final Object other) {
102+
return semanticEquals(other);
103+
}
104+
105+
@Override
106+
public int hashCode() {
107+
return semanticHashCode();
108+
}
109+
110+
@Override
111+
@SuppressWarnings({"UnstableApiUsage", "PMD.CompareObjectsWithEquals"})
112+
public boolean equalsWithoutChildren(@Nonnull RelationalExpression otherExpression,
113+
@Nonnull final AliasMap aliasMap) {
114+
if (this == otherExpression) {
115+
return true;
116+
}
117+
if (getClass() != otherExpression.getClass()) {
118+
return false;
119+
}
120+
121+
return semanticEqualsForResults(otherExpression, aliasMap);
122+
}
123+
124+
@Override
125+
public int hashCodeWithoutChildren() {
126+
return getResultValue().hashCode();
127+
}
128+
129+
@Nonnull
130+
@Override
131+
public PlannerGraph rewriteInternalPlannerGraph(@Nonnull final List<? extends PlannerGraph> childGraphs) {
132+
return PlannerGraph.fromNodeAndChildGraphs(
133+
new PlannerGraph.LogicalOperatorNode(this,
134+
"RECURSIVE " + resultValue,
135+
ImmutableList.of(),
136+
ImmutableMap.of()),
137+
childGraphs);
138+
}
139+
140+
@Override
141+
public String toString() {
142+
return "RECURSIVE " + resultValue;
143+
}
144+
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalUnionExpression;
3636
import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalUniqueExpression;
3737
import com.apple.foundationdb.record.query.plan.cascades.expressions.PrimaryScanExpression;
38+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RecursiveExpression;
3839
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
3940
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpressionWithPredicates;
4041
import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
@@ -277,4 +278,9 @@ public static BindingMatcher<InsertExpression> insertExpression(@Nonnull final B
277278
public static BindingMatcher<UpdateExpression> updateExpression(@Nonnull final BindingMatcher<? extends Quantifier> downstream) {
278279
return ofTypeOwning(UpdateExpression.class, only(downstream));
279280
}
281+
282+
@Nonnull
283+
public static BindingMatcher<RecursiveExpression> recursiveExpression(@Nonnull final CollectionMatcher<? extends Quantifier> downstream) {
284+
return ofTypeOwning(RecursiveExpression.class, downstream);
285+
}
280286
}

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

+7
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalUniqueExpression;
4646
import com.apple.foundationdb.record.query.plan.cascades.expressions.MatchableSortExpression;
4747
import com.apple.foundationdb.record.query.plan.cascades.expressions.PrimaryScanExpression;
48+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RecursiveExpression;
4849
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
4950
import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
5051
import com.apple.foundationdb.record.query.plan.cascades.expressions.UpdateExpression;
@@ -601,6 +602,12 @@ public Cardinalities visitRecordQueryRecursivePlan(@Nonnull final RecordQueryRec
601602
return Cardinalities.unknownMaxCardinality();
602603
}
603604

605+
@Nonnull
606+
@Override
607+
public Cardinalities visitRecursiveExpression(@Nonnull final RecursiveExpression element) {
608+
return Cardinalities.unknownMaxCardinality();
609+
}
610+
604611
@Nonnull
605612
@Override
606613
public Cardinalities evaluateAtExpression(@Nonnull RelationalExpression expression, @Nonnull List<Cardinalities> childResults) {

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.apple.foundationdb.record.query.plan.cascades.expressions.FullUnorderedScanExpression;
3232
import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalUnionExpression;
3333
import com.apple.foundationdb.record.query.plan.cascades.expressions.PrimaryScanExpression;
34+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RecursiveExpression;
3435
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
3536
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpressionVisitorWithDefaults;
3637
import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
@@ -125,7 +126,8 @@ public Set<String> evaluateAtExpression(@Nonnull RelationalExpression expression
125126
expression instanceof RecordQueryUnorderedUnionPlan ||
126127
expression instanceof RecordQueryIntersectionPlan ||
127128
expression instanceof LogicalUnionExpression ||
128-
expression instanceof SelectExpression) {
129+
expression instanceof SelectExpression ||
130+
expression instanceof RecursiveExpression) {
129131
final Set<String> union = new HashSet<>();
130132
for (Set<String> childResulSet : childResults) {
131133
union.addAll(childResulSet);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* ImplementRecursiveRule.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2024 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.rules;
22+
23+
import com.apple.foundationdb.annotation.API;
24+
import com.apple.foundationdb.record.logging.KeyValueLogMessage;
25+
import com.apple.foundationdb.record.query.plan.cascades.CascadesRule;
26+
import com.apple.foundationdb.record.query.plan.cascades.CascadesRuleCall;
27+
import com.apple.foundationdb.record.query.plan.cascades.PlanPartition;
28+
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
29+
import com.apple.foundationdb.record.query.plan.cascades.Reference;
30+
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
31+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RecursiveExpression;
32+
import com.apple.foundationdb.record.query.plan.cascades.matching.structure.BindingMatcher;
33+
import com.apple.foundationdb.record.query.plan.plans.RecordQueryRecursivePlan;
34+
import org.slf4j.Logger;
35+
import org.slf4j.LoggerFactory;
36+
37+
import javax.annotation.Nonnull;
38+
39+
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.ListMatcher.exactly;
40+
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.MultiMatcher.all;
41+
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.QuantifierMatchers.anyQuantifierOverRef;
42+
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.ReferenceMatchers.anyPlanPartition;
43+
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.ReferenceMatchers.planPartitions;
44+
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.ReferenceMatchers.rollUp;
45+
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.RelationalExpressionMatchers.canBeImplemented;
46+
import static com.apple.foundationdb.record.query.plan.cascades.matching.structure.RelationalExpressionMatchers.recursiveExpression;
47+
48+
/**
49+
* A rule that implements an existential nested loop join of its (already implemented) children.
50+
*/
51+
@API(API.Status.EXPERIMENTAL)
52+
public class ImplementRecursiveRule extends CascadesRule<RecursiveExpression> {
53+
@Nonnull
54+
private static final Logger logger = LoggerFactory.getLogger(ImplementRecursiveRule.class);
55+
56+
@Nonnull
57+
private static final BindingMatcher<PlanPartition> rootPlanPartitionsMatcher = anyPlanPartition();
58+
59+
@Nonnull
60+
private static final BindingMatcher<Reference> rootReferenceMatcher = planPartitions(rollUp(all(rootPlanPartitionsMatcher)));
61+
@Nonnull
62+
private static final BindingMatcher<Quantifier> rootQuantifierMatcher = anyQuantifierOverRef(rootReferenceMatcher);
63+
@Nonnull
64+
private static final BindingMatcher<PlanPartition> childPlanPartitionsMatcher = anyPlanPartition();
65+
66+
@Nonnull
67+
private static final BindingMatcher<Reference> childReferenceMatcher = planPartitions(rollUp(all(childPlanPartitionsMatcher)));
68+
@Nonnull
69+
private static final BindingMatcher<Quantifier> childQuantifierMatcher = anyQuantifierOverRef(childReferenceMatcher);
70+
@Nonnull
71+
private static final BindingMatcher<RecursiveExpression> root =
72+
recursiveExpression(exactly(rootQuantifierMatcher, childQuantifierMatcher)).where(canBeImplemented());
73+
74+
public ImplementRecursiveRule() {
75+
super(root);
76+
}
77+
78+
@Override
79+
public void onMatch(@Nonnull final CascadesRuleCall call) {
80+
final var bindings = call.getBindings();
81+
final var recursiveExpression = bindings.get(root);
82+
Debugger.withDebugger(debugger -> logger.debug(KeyValueLogMessage.of("matched RecursiveExpression", "legs", recursiveExpression.getQuantifiers().size())));
83+
84+
final var rootQuantifier = bindings.get(rootQuantifierMatcher);
85+
final var childQuantifier = bindings.get(childQuantifierMatcher);
86+
87+
final var rootReference = bindings.get(rootReferenceMatcher);
88+
final var childReference = bindings.get(childReferenceMatcher);
89+
90+
final var rootPartition = bindings.get(rootPlanPartitionsMatcher);
91+
final var childPartition = bindings.get(childPlanPartitionsMatcher);
92+
93+
final var rootAlias = rootQuantifier.getAlias();
94+
final var childAlias = childQuantifier.getAlias();
95+
96+
var rootRef = call.memoizeMemberPlans(rootReference, rootPartition.getPlans());
97+
final var newRootQuantifier = Quantifier.physicalBuilder().withAlias(rootAlias).build(rootRef);
98+
99+
var childRef = call.memoizeMemberPlans(childReference, childPartition.getPlans());
100+
final var newChildQuantifier = Quantifier.physicalBuilder().withAlias(childAlias).build(childRef);
101+
102+
call.yieldExpression(new RecordQueryRecursivePlan(newRootQuantifier, newChildQuantifier, recursiveExpression.getResultValue(), true));
103+
}
104+
}

0 commit comments

Comments
 (0)