Skip to content

Commit bc1ceeb

Browse files
committed
Support LEFT OUTER JOIN
This change introduces support for LEFT OUTER JOIN. To this end we introduce a dedicated QGM box (`OuterJoinExpression`) to represent one outer join. It is strictly binary (unlike the SELECT box) and carries the join type (LEFT/RIGHT/FULL), the ON-clause predicates, and a reference to the “preserved” and the “null-supplying” quantifier. During planning, an exploration rule `RewriteOuterJoinRule` eliminates the outer join box by rewriting it into two nested select boxes. The preserved (left) side is connected to the outer `SelectExpression` through a normal FOREACH quantifier. The null-supplying (right) side is wrapped in an inner `SelectExpression` that carries the ON predicates and is connected through a FOREACH quantifier with `nullOnEmpty` set to true. This rewrite happens early during planning so that existing rules (predicate push-down, join ordering, implementation) take care of implementing the join and optimizing it without further modification. Key changes: * `OuterJoinExpression` represents the OUTER JOIN in the QGM. * `QueryVisitor` parses the LEFT OUTER JOIN syntax and constructs the logical `OuterJoinExpression` via `wrapOperandsForOuterJoin()`. * `RewriteOuterJoinRule` rewrites the `OuterJoinExpression` into nested `SelectExpression` boxes. * Also extend `SelectMergeRule` to exclude `OuterJoinExpression` from merge candidates. This prevents WHERE predicates from being absorbed into the join and interfering with outer join semantics. * Include `OuterJoinExpression` in `ExpressionCountProperty.selectCount` so that branches containing outer joins are counted correctly when evaluating merge candidates. Testing: * `join-tests-outer.yamsql` integration tests.
1 parent c2f2e14 commit bc1ceeb

15 files changed

Lines changed: 2317 additions & 39 deletions

File tree

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* This source file is part of the FoundationDB open source project
55
*
6-
* Copyright 2015-2021 Apple Inc. and the FoundationDB project authors
6+
* Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
77
*
88
* Licensed under the Apache License, Version 2.0 (the "License");
99
* you may not use this file except in compliance with the License.
@@ -505,6 +505,22 @@ public Ordering pullUp(@Nonnull final Value value, @Nonnull EvaluationContext ev
505505
return Ordering.ofOrderingSet(bindingMapBuilder.build(), mappedOrderingSet, isDistinct());
506506
}
507507

508+
/**
509+
* Pulls up this ordering from a child quantifier's column space into the parent expression's result-value
510+
* space. This is the common pattern used by join implementation rules to translate a child plan's ordering
511+
* into the join output's ordering.
512+
*
513+
* @param resultValue the parent expression's result value
514+
* @param childAlias the alias of the child quantifier whose ordering is being pulled up
515+
* @return the translated ordering in the parent's column space
516+
*/
517+
@Nonnull
518+
public Ordering pullUpForJoin(@Nonnull final Value resultValue, @Nonnull final CorrelationIdentifier childAlias) {
519+
return pullUp(resultValue, EvaluationContext.empty(),
520+
AliasMap.ofAliases(childAlias, Quantifier.current()),
521+
resultValue.getCorrelatedTo());
522+
}
523+
508524
@Nonnull
509525
public Ordering pushDown(@Nonnull final Value value, @Nonnull final EvaluationContext evaluationContext,
510526
@Nonnull final AliasMap aliasMap, @Nonnull final Set<CorrelationIdentifier> constantAliases) {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
import com.apple.foundationdb.record.query.plan.cascades.rules.PushTypeFilterBelowFilterRule;
8484
import com.apple.foundationdb.record.query.plan.cascades.rules.RemoveProjectionRule;
8585
import com.apple.foundationdb.record.query.plan.cascades.rules.RemoveSortRule;
86+
import com.apple.foundationdb.record.query.plan.cascades.rules.RewriteOuterJoinRule;
8687
import com.apple.foundationdb.record.query.plan.cascades.rules.SplitSelectExtractIndependentQuantifiersRule;
8788
import com.apple.foundationdb.record.query.plan.plans.RecordQueryInParameterJoinPlan;
8889
import com.apple.foundationdb.record.query.plan.plans.RecordQueryInUnionOnValuesPlan;
@@ -110,7 +111,8 @@ public class PlanningRuleSet extends CascadesRuleSet {
110111
new SplitSelectExtractIndependentQuantifiersRule(),
111112
new PullUpNullOnEmptyRule(),
112113
new PartitionSelectRule(),
113-
new PartitionBinarySelectRule()
114+
new PartitionBinarySelectRule(),
115+
new RewriteOuterJoinRule()
114116
);
115117
private static final Set<CascadesRule<? extends RelationalExpression>> MATCHING_RULES = ImmutableSet.of(
116118
new MatchLeafRule(),

0 commit comments

Comments
 (0)