This is an automated email from the ASF dual-hosted git repository.
silun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 19ef7f0b47 [CALCITE-7327] Support IS NOT DISTINCT FROM as equi
condition of hash join
19ef7f0b47 is described below
commit 19ef7f0b47e97137e37c2956ace8319f2de5e5ce
Author: Silun Dong <[email protected]>
AuthorDate: Thu Dec 11 14:42:12 2025 +0800
[CALCITE-7327] Support IS NOT DISTINCT FROM as equi condition of hash join
---
.../adapter/enumerable/EnumerableHashJoin.java | 12 +-
.../adapter/enumerable/EnumerableMergeJoin.java | 10 ++
.../enumerable/EnumerableMergeJoinRule.java | 5 +-
.../calcite/adapter/enumerable/PhysType.java | 9 ++
.../calcite/adapter/enumerable/PhysTypeImpl.java | 50 ++++++++
.../java/org/apache/calcite/plan/RelOptUtil.java | 21 +++-
.../java/org/apache/calcite/rel/core/Join.java | 2 +-
.../java/org/apache/calcite/rel/core/JoinInfo.java | 36 ++++--
.../calcite/rel/rules/LoptOptimizeJoinRule.java | 2 +-
.../calcite/rel/rules/LoptSemiJoinOptimizer.java | 2 +-
.../org/apache/calcite/rel/rules/SemiJoinRule.java | 4 +-
.../java/org/apache/calcite/runtime/FlatLists.java | 13 ++
.../org/apache/calcite/util/BuiltInMethod.java | 1 +
.../java/org/apache/calcite/test/JdbcTest.java | 2 +-
.../test/enumerable/EnumerableHashJoinTest.java | 131 +++++++++++++++++++++
core/src/test/resources/sql/blank.iq | 4 +-
core/src/test/resources/sql/planner.iq | 17 ++-
core/src/test/resources/sql/sub-query.iq | 32 ++---
.../apache/calcite/linq4j/EnumerableDefaults.java | 52 ++++----
19 files changed, 325 insertions(+), 80 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
index 8bd9ffbf08..e37173137a 100644
---
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
+++
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
@@ -218,8 +218,10 @@ private Result
implementHashSemiJoin(EnumerableRelImplementor implementor, Prefe
Expressions.list(
leftExpression,
rightExpression,
-
leftResult.physType.generateAccessorWithoutNulls(joinInfo.leftKeys),
-
rightResult.physType.generateAccessorWithoutNulls(joinInfo.rightKeys),
+ leftResult.physType.generateNullAwareAccessor(
+ joinInfo.leftKeys, joinInfo.nullExclusionFlags),
+ rightResult.physType.generateNullAwareAccessor(
+ joinInfo.rightKeys, joinInfo.nullExclusionFlags),
Util.first(keyPhysType.comparer(),
Expressions.constant(null)),
predicate)))
@@ -264,8 +266,10 @@ private Result implementHashJoin(EnumerableRelImplementor
implementor, Prefer pr
BuiltInMethod.HASH_JOIN.method,
Expressions.list(
rightExpression,
-
leftResult.physType.generateAccessorWithoutNulls(joinInfo.leftKeys),
-
rightResult.physType.generateAccessorWithoutNulls(joinInfo.rightKeys),
+ leftResult.physType.generateNullAwareAccessor(
+ joinInfo.leftKeys, joinInfo.nullExclusionFlags),
+ rightResult.physType.generateNullAwareAccessor(
+ joinInfo.rightKeys, joinInfo.nullExclusionFlags),
EnumUtils.joinSelector(joinType,
physType,
ImmutableList.of(
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoin.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoin.java
index 838e37064f..557362e6bc 100644
---
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoin.java
+++
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoin.java
@@ -34,6 +34,7 @@
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinInfo;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.metadata.RelMdCollation;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
@@ -71,6 +72,9 @@
* {@link EnumerableConvention enumerable calling convention} using
* a merge algorithm. */
public class EnumerableMergeJoin extends Join implements EnumerableRel {
+ @SuppressWarnings("HidingField")
+ private final JoinInfo joinInfo;
+
protected EnumerableMergeJoin(
RelOptCluster cluster,
RelTraitSet traits,
@@ -80,6 +84,12 @@ protected EnumerableMergeJoin(
Set<CorrelationId> variablesSet,
JoinRelType joinType) {
super(cluster, traits, ImmutableList.of(), left, right, condition,
variablesSet, joinType);
+ // TODO: support IS NOT DISTINCT FROM condition as join keys of MergeJoin
+ // EnumerableMergeJoin cannot use IS NOT DISTINCT FROM condition as join
keys
+ // (In the algorithm of MergeJoin in Enumerable convention, it will stop
+ // when leftKey or rightKey is NULL), so we create a new JoinInfo that only
+ // considers EQUALS.
+ this.joinInfo = JoinInfo.createWithStrictEquality(left, right, condition);
assert getConvention() instanceof EnumerableConvention;
final List<RelCollation> leftCollations =
getCollations(left.getTraitSet());
final List<RelCollation> rightCollations =
getCollations(right.getTraitSet());
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoinRule.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoinRule.java
index 4137f6b74b..624db0a604 100644
---
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoinRule.java
+++
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoinRule.java
@@ -60,7 +60,10 @@ protected EnumerableMergeJoinRule(Config config) {
@Override public @Nullable RelNode convert(RelNode rel) {
Join join = (Join) rel;
- final JoinInfo info = join.analyzeCondition();
+ // EnumerableMergeJoin cannot use IS NOT DISTINCT FROM condition as join
keys. More details
+ // in EnumerableMergeJoin.java.
+ final JoinInfo info =
+ JoinInfo.createWithStrictEquality(join.getLeft(), join.getRight(),
join.getCondition());
if (!EnumerableMergeJoin.isMergeJoinSupported(join.getJoinType())) {
// EnumerableMergeJoin only supports certain join types.
return null;
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysType.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysType.java
index 8d4eeb176c..c50d6237df 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysType.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysType.java
@@ -134,6 +134,15 @@ Expression fieldReference(Expression expression, int field,
*/
Expression generateAccessorWithoutNulls(List<Integer> fields);
+ /**
+ * Similar to {@link #generateAccessor(List)} and {@link
#generateAccessorWithoutNulls(List)},
+ * but it's null-aware. It returns a Expression which evaluates to null (if
one of
+ * field is null and it isn't null-safe) or a list of
+ * fields that may contain null (no field is null, or there are fields with
null but they are
+ * null-safe) at runtime.
+ */
+ Expression generateNullAwareAccessor(List<Integer> fields, List<Boolean>
nullExclusionFlags);
+
/** Generates a selector for the given fields from an expression, with the
* default row format. */
Expression generateSelector(
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java
index ab51dd3e35..e3fdd4d366 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java
@@ -642,6 +642,21 @@ private List<Expression> fieldReferences(
}
}
+ private static Expression getListExpressionAllowSingleElement(
+ Expressions.FluentList<Expression> list) {
+ assert list.size() > 0;
+
+ if (list.size() == 1) {
+ return Expressions.call(
+ List.class,
+ null,
+ BuiltInMethod.LIST1.method,
+ list);
+ } else {
+ return getListExpression(list);
+ }
+ }
+
private static Expression
getListExpression(Expressions.FluentList<Expression> list) {
assert list.size() >= 2;
@@ -713,6 +728,41 @@ private static Expression
getListExpression(Expressions.FluentList<Expression> l
return Expressions.lambda(Function1.class, exp, v1);
}
+ @Override public Expression generateNullAwareAccessor(
+ List<Integer> fields,
+ List<Boolean> nullExclusionFlags) {
+ assert fields.size() == nullExclusionFlags.size();
+ ParameterExpression v1 = Expressions.parameter(javaRowClass, "v1");
+ if (fields.isEmpty()) {
+ return Expressions.lambda(
+ Function1.class,
+ Expressions.field(
+ null,
+ BuiltInMethod.COMPARABLE_EMPTY_LIST.field),
+ v1);
+ }
+ Expressions.FluentList<Expression> list = Expressions.list();
+ for (int field : fields) {
+ list.add(fieldReference(v1, field));
+ }
+
+ // in the HashJoin key selector scenario, when there is exactly one join
key and it is
+ // null-safe, a row whose join key is null must still be correctly
recognized and extracted.
+ // Therefore, when list.size() == 1, this method returns a list containing
a single
+ // element (which may be null) rather than returning the element directly.
+ Expression exp = getListExpressionAllowSingleElement(list);
+ for (int i = list.size() - 1; i >= 0; i--) {
+ if (nullExclusionFlags.get(i)) {
+ exp =
+ Expressions.condition(
+ Expressions.equal(list.get(i), Expressions.constant(null)),
+ Expressions.constant(null),
+ exp);
+ }
+ }
+ return Expressions.lambda(Function1.class, exp, v1);
+ }
+
@Override public Expression fieldReference(
Expression expression, int field) {
return fieldReference(expression, field, null);
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index 34724a8adf..274e91f14d 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -1470,11 +1470,24 @@ private static void splitJoinCondition(
nonEquiList.add(condition);
}
- /** Builds an equi-join condition from a set of left and right keys. */
+ /** Builds an equi-join condition by conjoining EQUALS operator for each
corresponding pair of
+ * leftKeys and rightKeys. */
public static RexNode createEquiJoinCondition(
final RelNode left, final List<Integer> leftKeys,
final RelNode right, final List<Integer> rightKeys,
final RexBuilder rexBuilder) {
+ List<Boolean> filterNulls = Collections.nCopies(leftKeys.size(),
Boolean.TRUE);
+ return createHashJoinCondition(left, leftKeys, right, rightKeys,
+ filterNulls, rexBuilder);
+ }
+
+ /** Builds an equi-join condition by conjoining operators for each
corresponding pair of
+ * leftKeys and rightKeys. The operator is EQUALS if filterNulls is true for
that
+ * position, otherwise IS NOT DISTINCT FROM. */
+ public static RexNode createHashJoinCondition(
+ final RelNode left, final List<Integer> leftKeys,
+ final RelNode right, final List<Integer> rightKeys,
+ final List<Boolean> filterNulls, final RexBuilder rexBuilder) {
final List<RelDataType> leftTypes =
RelOptUtil.getFieldTypeList(left.getRowType());
final List<RelDataType> rightTypes =
@@ -1484,7 +1497,11 @@ public static RexNode createEquiJoinCondition(
@Override public RexNode get(int index) {
final int leftKey = leftKeys.get(index);
final int rightKey = rightKeys.get(index);
- return rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
+ final SqlOperator operator =
+ filterNulls.get(index)
+ ? SqlStdOperatorTable.EQUALS
+ : SqlStdOperatorTable.IS_NOT_DISTINCT_FROM;
+ return rexBuilder.makeCall(operator,
rexBuilder.makeInputRef(leftTypes.get(leftKey), leftKey),
rexBuilder.makeInputRef(rightTypes.get(rightKey),
leftTypes.size() + rightKey));
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Join.java
b/core/src/main/java/org/apache/calcite/rel/core/Join.java
index 1a56edbed0..1b81717aed 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Join.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Join.java
@@ -104,7 +104,7 @@ protected Join(
this.condition = requireNonNull(condition, "condition");
this.variablesSet = ImmutableSet.copyOf(variablesSet);
this.joinType = requireNonNull(joinType, "joinType");
- this.joinInfo = JoinInfo.createWithStrictEquality(left, right, condition);
+ this.joinInfo = JoinInfo.of(left, right, condition);
this.hints = ImmutableList.copyOf(hints);
}
diff --git a/core/src/main/java/org/apache/calcite/rel/core/JoinInfo.java
b/core/src/main/java/org/apache/calcite/rel/core/JoinInfo.java
index f092a73ea9..0d470cdde2 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/JoinInfo.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/JoinInfo.java
@@ -29,6 +29,7 @@
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import static java.util.Objects.requireNonNull;
@@ -36,9 +37,10 @@
/** An analyzed join condition.
*
* <p>It is useful for the many algorithms that care whether a join has an
- * equi-join condition.
+ * equi-join condition (contains EQUALS and IS NOT DISTINCT FROM).
*
- * <p>You can create one using {@link #createWithStrictEquality}, or call
+ * <p>You can create one using {@link #of(RelNode, RelNode, RexNode)},
+ * {@link #createWithStrictEquality}, or call
* {@link Join#analyzeCondition()}; many kinds of join cache their
* join info, especially those that are equi-joins.
*
@@ -46,28 +48,36 @@
public class JoinInfo {
public final ImmutableIntList leftKeys;
public final ImmutableIntList rightKeys;
+ // for each join key, whether it filters out nulls. If TRUE, the join key
uses EQUALS semantics
+ // (not null-safe); if FALSE, it uses IS NOT DISTINCT FROM semantics
(null-safe).
+ public final ImmutableList<Boolean> nullExclusionFlags;
+ // non-equi parts of join condition.
+ // after CALCITE-7327, IS NOT DISTINCT FROM can be treated as a hash join
key and is no longer
+ // part of nonEquiConditions.
public final ImmutableList<RexNode> nonEquiConditions;
/** Creates a JoinInfo. */
protected JoinInfo(ImmutableIntList leftKeys, ImmutableIntList rightKeys,
- ImmutableList<RexNode> nonEquiConditions) {
+ ImmutableList<Boolean> nullExclusionFlags, ImmutableList<RexNode>
nonEquiConditions) {
this.leftKeys = requireNonNull(leftKeys, "leftKeys");
this.rightKeys = requireNonNull(rightKeys, "rightKeys");
+ this.nullExclusionFlags = requireNonNull(nullExclusionFlags,
"nullExclusionFlags");
this.nonEquiConditions =
requireNonNull(nonEquiConditions, "nonEquiConditions");
- assert leftKeys.size() == rightKeys.size();
+ assert leftKeys.size() == rightKeys.size() && leftKeys.size() ==
nullExclusionFlags.size();
}
/** Creates a {@code JoinInfo} by analyzing a condition. */
public static JoinInfo of(RelNode left, RelNode right, RexNode condition) {
final List<Integer> leftKeys = new ArrayList<>();
final List<Integer> rightKeys = new ArrayList<>();
- final List<Boolean> filterNulls = new ArrayList<>();
+ final List<Boolean> nullExclusionFlags = new ArrayList<>();
final List<RexNode> nonEquiList = new ArrayList<>();
RelOptUtil.splitJoinCondition(left, right, condition, leftKeys, rightKeys,
- filterNulls, nonEquiList);
+ nullExclusionFlags, nonEquiList);
return new JoinInfo(ImmutableIntList.copyOf(leftKeys),
- ImmutableIntList.copyOf(rightKeys), ImmutableList.copyOf(nonEquiList));
+ ImmutableIntList.copyOf(rightKeys),
ImmutableList.copyOf(nullExclusionFlags),
+ ImmutableList.copyOf(nonEquiList));
}
/** Creates a {@code JoinInfo} by analyzing a condition.
@@ -82,14 +92,18 @@ public static JoinInfo createWithStrictEquality(RelNode
left,
final List<RexNode> nonEquiList = new ArrayList<>();
RelOptUtil.splitJoinCondition(left, right, condition, leftKeys, rightKeys,
null, nonEquiList);
+ List<Boolean> nullExclusionFlags = Collections.nCopies(leftKeys.size(),
Boolean.TRUE);
return new JoinInfo(ImmutableIntList.copyOf(leftKeys),
- ImmutableIntList.copyOf(rightKeys), ImmutableList.copyOf(nonEquiList));
+ ImmutableIntList.copyOf(rightKeys),
ImmutableList.copyOf(nullExclusionFlags),
+ ImmutableList.copyOf(nonEquiList));
}
- /** Creates an equi-join. */
+ /** Creates an equi-join (only considers EQUALS operations). */
public static JoinInfo of(ImmutableIntList leftKeys,
ImmutableIntList rightKeys) {
- return new JoinInfo(leftKeys, rightKeys, ImmutableList.of());
+ List<Boolean> nullExclusionFlags = Collections.nCopies(leftKeys.size(),
Boolean.TRUE);
+ return new JoinInfo(leftKeys, rightKeys,
+ ImmutableList.copyOf(nullExclusionFlags), ImmutableList.of());
}
/** Returns whether this is an equi-join. */
@@ -117,7 +131,7 @@ public RexNode getRemaining(RexBuilder rexBuilder) {
public RexNode getEquiCondition(RelNode left, RelNode right,
RexBuilder rexBuilder) {
- return RelOptUtil.createEquiJoinCondition(left, leftKeys, right, rightKeys,
+ return RelOptUtil.createHashJoinCondition(left, leftKeys, right,
rightKeys, nullExclusionFlags,
rexBuilder);
}
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java
index 75056dd981..16cb367ac2 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java
@@ -2053,7 +2053,7 @@ public static boolean isRemovableSelfJoin(Join joinRel) {
*/
private static boolean areSelfJoinKeysUnique(RelMetadataQuery mq,
RelNode leftRel, RelNode rightRel, RexNode joinFilters) {
- final JoinInfo joinInfo = JoinInfo.createWithStrictEquality(leftRel,
rightRel, joinFilters);
+ final JoinInfo joinInfo = JoinInfo.of(leftRel, rightRel, joinFilters);
// Make sure each key on the left maps to the same simple column as the
// corresponding key on the right
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
b/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
index e9d5020b0d..724884641d 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
@@ -264,7 +264,7 @@ private static int isSuitableFilter(
RelNode factRel = multiJoin.getJoinFactor(factIdx);
RelNode dimRel = multiJoin.getJoinFactor(dimIdx);
- final JoinInfo joinInfo = JoinInfo.createWithStrictEquality(factRel,
dimRel, semiJoinCondition);
+ final JoinInfo joinInfo = JoinInfo.of(factRel, dimRel, semiJoinCondition);
assert !joinInfo.leftKeys.isEmpty();
// mutable copies
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
index 2a07d7fb95..4a10ec533a 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
@@ -108,9 +108,9 @@ protected void perform(RelOptRuleCall call, @Nullable
Project project,
final ImmutableIntList newRightKeys =
ImmutableIntList.copyOf(newRightKeyBuilder);
relBuilder.push(aggregate.getInput());
final RexNode newCondition =
- RelOptUtil.createEquiJoinCondition(relBuilder.peek(2, 0),
+ RelOptUtil.createHashJoinCondition(relBuilder.peek(2, 0),
joinInfo.leftKeys, relBuilder.peek(2, 1), newRightKeys,
- rexBuilder);
+ joinInfo.nullExclusionFlags, rexBuilder);
relBuilder.semiJoin(newCondition).hints(join.getHints());
break;
diff --git a/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
b/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
index 544a8926f0..872bc76a49 100644
--- a/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
+++ b/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
@@ -58,6 +58,19 @@ public static <T> List<T> of(T t0) {
return new Flat1List<>(t0);
}
+ /**
+ * Creates a flat list with 1 element. This is different from {@link
#of(Object)}, because
+ * it prevents overload from {@link #of(List)}. This may be useful when you
create a flat list
+ * with 1 element of List type.
+ *
+ * @param t0 Element
+ * @param <T> Element type
+ * @return List containing the given members
+ */
+ public static <T> List<T> ofSingle(T t0) {
+ return new Flat1List<>(t0);
+ }
+
/** Creates a flat list with 2 elements. */
public static <T> List<T> of(T t0, T t1) {
return new Flat2List<>(t0, t1);
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 14cb12d3e7..3834409640 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -306,6 +306,7 @@ public enum BuiltInMethod {
FlatProductInputType[].class),
FLAT_LIST(SqlFunctions.class, "flatList"),
LIST_N(FlatLists.class, "copyOf", Comparable[].class),
+ LIST1(FlatLists.class, "ofSingle", Object.class),
LIST2(FlatLists.class, "of", Object.class, Object.class),
LIST3(FlatLists.class, "of", Object.class, Object.class, Object.class),
LIST4(FlatLists.class, "of", Object.class, Object.class, Object.class,
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 6ef217fb0f..e25c6f31cc 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -4155,7 +4155,7 @@ public void checkOrderBy(final boolean desc,
+ "on \"t1\".\"commission\" is not distinct from
\"t2\".\"commission\"";
CalciteAssert.hr()
.query(sql)
- .explainContains("NestedLoopJoin(condition=[IS NOT DISTINCT FROM($0,
$1)]")
+ .explainContains("HashJoin(condition=[IS NOT DISTINCT FROM($0, $1)]")
.returnsUnordered("commission=1000",
"commission=250",
"commission=500",
diff --git
a/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableHashJoinTest.java
b/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableHashJoinTest.java
index 614ed98e03..c589f6eea5 100644
---
a/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableHashJoinTest.java
+++
b/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableHashJoinTest.java
@@ -320,6 +320,137 @@ class EnumerableHashJoinTest {
"empid=200");
}
+ @Test void hashJoinWithIsNotDistinctFrom() {
+ String tempTableSql = "WITH t1(id, sal) as ( VALUES (1,10), (2,NULL),
(3,30), (5, NULL)),"
+ + "t2(id, sal) as ( VALUES (1,10), (2,NULL), (4,40), (5, 50) ) ";
+ // t1 t2
+ // id | sal id | sal
+ // 1 | 10 1 | 10
+ // 2 | NULL 2 | NULL
+ // 3 | 30 4 | 40
+ // 5 | NULL 5 | 50
+
+ // inner join: t1.sal IS NOT DISTINCT FROM t2.sal
+ tester(false, new HrSchema())
+ .query(
+ tempTableSql
+ + "select t1.id, t1.sal, t2.id, t2.sal from t1 join t2"
+ + " on t1.sal is not distinct from t2.sal")
+ .withHook(Hook.PLANNER, (Consumer<RelOptPlanner>) planner ->
+ planner.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE))
+ .explainContains(
+ "EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($1, $3)],
joinType=[inner])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 3, 30
}, { 5, null }]])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 4, 40
}, { 5, 50 }]])\n")
+ .returnsUnordered(
+ "id=1; sal=10; id=1; sal=10",
+ "id=2; sal=null; id=2; sal=null",
+ "id=5; sal=null; id=2; sal=null");
+
+ // inner join: t1.sal = t2.sal
+ tester(false, new HrSchema())
+ .query(
+ tempTableSql
+ + "select t1.id, t1.sal, t2.id, t2.sal from t1 join t2"
+ + " on t1.sal = t2.sal")
+ .withHook(Hook.PLANNER, (Consumer<RelOptPlanner>) planner ->
+ planner.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE))
+ .explainContains("EnumerableHashJoin(condition=[=($1, $3)],
joinType=[inner])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 3, 30 },
{ 5, null }]])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 4, 40 },
{ 5, 50 }]])\n")
+ .returnsUnordered(
+ "id=1; sal=10; id=1; sal=10");
+
+ // inner join: t1.id = t2.id && t1.sal IS NOT DISTINCT FROM t2.sal
+ tester(false, new HrSchema())
+ .query(
+ tempTableSql
+ + "select t1.id, t1.sal, t2.id, t2.sal from t1 join t2"
+ + " on t1.id = t2.id and t1.sal is not distinct from t2.sal")
+ .withHook(Hook.PLANNER, (Consumer<RelOptPlanner>) planner ->
+ planner.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE))
+ .explainContains(
+ "EnumerableHashJoin(condition=[AND(=($0, $2), IS NOT DISTINCT
FROM($1, $3))], joinType=[inner])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 3, 30
}, { 5, null }]])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 4, 40
}, { 5, 50 }]])\n")
+ .returnsUnordered(
+ "id=1; sal=10; id=1; sal=10",
+ "id=2; sal=null; id=2; sal=null");
+
+ // semi join: t1.sal IS NOT DISTINCT FROM t2.sal
+ tester(true, new HrSchema())
+ .withRel(builder -> {
+ builder
+ .values(new String[]{"id1", "sal1"}, 1, 10, 2, null, 3, 30, 5,
null)
+ .values(new String[]{"id2", "sal2"}, 1, 10, 2, null, 4, 40, 5,
50)
+ .semiJoin(
+ builder.isNotDistinctFrom(
+ builder.field(2, 0, "sal1"),
+ builder.field(2, 1, "sal2")));
+ return builder.build();
+ })
+ .withHook(Hook.PLANNER, (Consumer<RelOptPlanner>) planner -> {
+ planner.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE);
+ })
+ .explainHookMatches(
+ "EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($1, $3)],
joinType=[semi])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 3, 30
}, { 5, null }]])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 4, 40
}, { 5, 50 }]])\n")
+ .returnsUnordered(
+ "id1=1; sal1=10",
+ "id1=2; sal1=null",
+ "id1=5; sal1=null");
+
+ // semi join: t1.sal = t2.sal
+ tester(true, new HrSchema())
+ .withRel(builder -> {
+ builder
+ .values(new String[]{"id1", "sal1"}, 1, 10, 2, null, 3, 30, 5,
null)
+ .values(new String[]{"id2", "sal2"}, 1, 10, 2, null, 4, 40, 5,
50)
+ .semiJoin(
+ builder.equals(
+ builder.field(2, 0, "sal1"),
+ builder.field(2, 1, "sal2")));
+ return builder.build();
+ })
+ .withHook(Hook.PLANNER, (Consumer<RelOptPlanner>) planner -> {
+ planner.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE);
+ })
+ .explainHookMatches(
+ "EnumerableHashJoin(condition=[=($1, $3)], joinType=[semi])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 3, 30
}, { 5, null }]])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 4, 40
}, { 5, 50 }]])\n")
+ .returnsUnordered(
+ "id1=1; sal1=10");
+
+ // semi join: t1.id = t2.id && t1.sal IS NOT DISTINCT FROM t2.sal
+ tester(true, new HrSchema())
+ .withRel(builder -> {
+ builder
+ .values(new String[]{"id1", "sal1"}, 1, 10, 2, null, 3, 30, 5,
null)
+ .values(new String[]{"id2", "sal2"}, 1, 10, 2, null, 4, 40, 5,
50)
+ .semiJoin(
+ builder.and(
+ builder.equals(
+ builder.field(2, 0, "id1"),
+ builder.field(2, 1, "id2")),
+ builder.isNotDistinctFrom(
+ builder.field(2, 0, "sal1"),
+ builder.field(2, 1, "sal2"))));
+ return builder.build();
+ })
+ .withHook(Hook.PLANNER, (Consumer<RelOptPlanner>) planner -> {
+ planner.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE);
+ })
+ .explainHookMatches(
+ "EnumerableHashJoin(condition=[AND(=($0, $2), IS NOT DISTINCT
FROM($1, $3))], joinType=[semi])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 3, 30
}, { 5, null }]])\n"
+ + " EnumerableValues(tuples=[[{ 1, 10 }, { 2, null }, { 4, 40
}, { 5, 50 }]])\n")
+ .returnsUnordered(
+ "id1=1; sal1=10",
+ "id1=2; sal1=null");
+ }
+
private CalciteAssert.AssertThat tester(boolean forceDecorrelate,
Object schema) {
return CalciteAssert.that()
diff --git a/core/src/test/resources/sql/blank.iq
b/core/src/test/resources/sql/blank.iq
index 882d430412..9c200caf7e 100644
--- a/core/src/test/resources/sql/blank.iq
+++ b/core/src/test/resources/sql/blank.iq
@@ -92,10 +92,10 @@ select i, j from table1 where table1.j NOT IN (select i
from table2 where table1
EnumerableCalc(expr#0..7=[{inputs}], expr#8=[0], expr#9=[=($t3, $t8)],
expr#10=[IS NULL($t1)], expr#11=[IS NOT NULL($t7)], expr#12=[<($t4, $t3)],
expr#13=[OR($t10, $t11, $t12)], expr#14=[IS NOT TRUE($t13)], expr#15=[OR($t9,
$t14)], proj#0..1=[{exprs}], $condition=[$t15])
EnumerableMergeJoin(condition=[AND(=($0, $6), =($1, $5))], joinType=[left])
EnumerableSort(sort0=[$0], sort1=[$1], dir0=[ASC], dir1=[ASC])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $2)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $2)],
joinType=[left])
EnumerableTableScan(table=[[BLANK, TABLE1]])
EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NOT NULL($t2)],
expr#5=[0], expr#6=[CASE($t4, $t2, $t5)], expr#7=[IS NOT NULL($t3)],
expr#8=[CASE($t7, $t3, $t5)], J=[$t0], c=[$t6], ck=[$t8])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableAggregate(group=[{0}])
EnumerableTableScan(table=[[BLANK, TABLE1]])
EnumerableAggregate(group=[{1}], c=[COUNT()], ck=[COUNT($0)])
diff --git a/core/src/test/resources/sql/planner.iq
b/core/src/test/resources/sql/planner.iq
index a24181cadb..0461cc644b 100644
--- a/core/src/test/resources/sql/planner.iq
+++ b/core/src/test/resources/sql/planner.iq
@@ -54,7 +54,7 @@ select * from t as t2 where t2.i > 0;
!ok
-EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[semi])
+EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)], joinType=[semi])
EnumerableValues(tuples=[[{ 0 }, { 1 }]])
EnumerableCalc(expr#0=[{inputs}], expr#1=[0], expr#2=[>($t0, $t1)],
EXPR$0=[$t0], $condition=[$t2])
EnumerableValues(tuples=[[{ 0 }, { 1 }]])
@@ -74,7 +74,7 @@ select * from t as t2 where t2.i > 0;
!ok
-EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[semi])
+EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)], joinType=[semi])
EnumerableValues(tuples=[[{ 0 }, { 1 }]])
EnumerableCalc(expr#0=[{inputs}], expr#1=[0], expr#2=[>($t0, $t1)],
EXPR$0=[$t0], $condition=[$t2])
EnumerableValues(tuples=[[{ 0 }, { 1 }]])
@@ -215,14 +215,13 @@ select a from (values (1.0), (4.0), (null)) as t3 (a);
!ok
EnumerableAggregate(group=[{0}])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[semi])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)], joinType=[semi])
EnumerableCalc(expr#0=[{inputs}], expr#1=[CAST($t0):DECIMAL(11, 1)],
A=[$t1])
- EnumerableAggregate(group=[{0}])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[semi])
- EnumerableCalc(expr#0=[{inputs}], expr#1=[CAST($t0):DECIMAL(11, 1)
NOT NULL], A=[$t1])
- EnumerableValues(tuples=[[{ 1.0 }, { 2.0 }, { 3.0 }, { 4.0 }, {
5.0 }]])
- EnumerableCalc(expr#0=[{inputs}], expr#1=[CAST($t0):DECIMAL(11, 1)
NOT NULL], A=[$t1])
- EnumerableValues(tuples=[[{ 1 }, { 2 }]])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[semi])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=[CAST($t0):DECIMAL(11, 1)],
A=[$t1])
+ EnumerableValues(tuples=[[{ 1.0 }, { 2.0 }, { 3.0 }, { 4.0 }, { 5.0
}]])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=[CAST($t0):DECIMAL(11, 1)],
A=[$t1])
+ EnumerableValues(tuples=[[{ 1 }, { 2 }]])
EnumerableCalc(expr#0=[{inputs}], expr#1=[CAST($t0):DECIMAL(11, 1)],
A=[$t1])
EnumerableValues(tuples=[[{ 1.0 }, { 4.0 }, { null }]])
!plan
diff --git a/core/src/test/resources/sql/sub-query.iq
b/core/src/test/resources/sql/sub-query.iq
index a12b087ebb..9f320c7ef0 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -536,7 +536,7 @@ EnumerableCalc(expr#0..9=[{inputs}], expr#10=[0],
expr#11=[=($t5, $t10)], expr#1
EnumerableTableScan(table=[[scott, DEPT]])
EnumerableSort(sort0=[$0], dir0=[ASC])
EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NOT NULL($t2)],
expr#5=[0], expr#6=[CASE($t4, $t2, $t5)], expr#7=[IS NOT NULL($t3)],
expr#8=[CASE($t7, $t3, $t5)], DEPTNO0=[$t0], c=[$t6], ck=[$t8])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableAggregate(group=[{1}])
EnumerableHashJoin(condition=[=($1, $2)], joinType=[semi])
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0],
DEPTNO=[$t7])
@@ -2666,11 +2666,11 @@ EnumerableAggregate(group=[{}], C=[COUNT()])
EnumerableMergeJoin(condition=[AND(=($3, $5), =($4, $6))], joinType=[left])
EnumerableSort(sort0=[$3], sort1=[$4], dir0=[ASC], dir1=[ASC])
EnumerableCalc(expr#0..6=[{inputs}], expr#7=[100], expr#8=[+($t2,
$t7)], expr#9=[CAST($t1):VARCHAR(14)], SAL=[$t2], c=[$t4], ck=[$t5], $f5=[$t8],
ENAME0=[$t9])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($3, $6)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($3, $6)],
joinType=[left])
EnumerableCalc(expr#0..7=[{inputs}],
expr#8=[CAST($t1):VARCHAR(14)], proj#0..1=[{exprs}], SAL=[$t5], ENAME0=[$t8])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..2=[{inputs}], expr#3=[IS NOT NULL($t2)],
expr#4=[0], expr#5=[CASE($t3, $t2, $t4)], c=[$t5], ck=[$t5], DNAME=[$t0])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0,
$1)], joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableAggregate(group=[{0}])
EnumerableCalc(expr#0..7=[{inputs}],
expr#8=[CAST($t1):VARCHAR(14)], ENAME0=[$t8])
EnumerableTableScan(table=[[scott, EMP]])
@@ -2686,9 +2686,9 @@ select empno from "scott".emp as e
where e.empno > ANY(
select 2 from "scott".dept e2 where e2.deptno = e.deptno) ;
EnumerableCalc(expr#0..6=[{inputs}], EMPNO=[$t5])
- EnumerableNestedLoopJoin(condition=[AND(IS NOT DISTINCT FROM($6, $4),
OR(AND(>($5, $0), IS NOT TRUE(OR(IS NULL($3), =($1, 0)))), AND(>($5, $0), IS
NOT TRUE(OR(IS NULL($3), =($1, 0))), IS NOT TRUE(>($5, $0)), <=($1, $2))))],
joinType=[inner])
+ EnumerableHashJoin(condition=[AND(IS NOT DISTINCT FROM($4, $6), OR(AND(>($5,
$0), IS NOT TRUE(OR(IS NULL($3), =($1, 0)))), AND(>($5, $0), IS NOT TRUE(OR(IS
NULL($3), =($1, 0))), IS NOT TRUE(>($5, $0)), <=($1, $2))))], joinType=[inner])
EnumerableCalc(expr#0..4=[{inputs}], expr#5=[IS NOT NULL($t3)],
expr#6=[0], expr#7=[CASE($t5, $t3, $t6)], m=[$t2], c=[$t7], d=[$t7],
trueLiteral=[$t4], DEPTNO=[$t0])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableAggregate(group=[{7}])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..2=[{inputs}], expr#3=[2], expr#4=[1:BIGINT],
expr#5=[true], DEPTNO=[$t0], EXPR$0=[$t3], $f2=[$t4], $f3=[$t5])
@@ -2726,7 +2726,7 @@ EnumerableCalc(expr#0..6=[{inputs}], expr#7=[>($t1,
$t2)], expr#8=[IS TRUE($t7)]
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], DEPTNO=[$t7])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..4=[{inputs}], expr#5=[IS NOT NULL($t3)],
expr#6=[0], expr#7=[CASE($t5, $t3, $t6)], m=[$t2], c=[$t7], d=[$t7],
trueLiteral=[$t4], DEPTNO0=[$t0])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableAggregate(group=[{0}], m=[MIN($1)], c=[COUNT()],
trueLiteral=[LITERAL_AGG(true)])
@@ -2801,7 +2801,7 @@ EnumerableCalc(expr#0..6=[{inputs}], expr#7=[<>($t2,
$t1)], expr#8=[1], expr#9=[
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..5=[{inputs}], expr#6=[IS NOT NULL($t2)],
expr#7=[0], expr#8=[CASE($t6, $t2, $t7)], expr#9=[IS NOT NULL($t3)],
expr#10=[CASE($t9, $t3, $t7)], c=[$t8], d=[$t8], dd=[$t10], m=[$t4],
trueLiteral=[$t5], EMPNO1=[$t0])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..7=[{inputs}], expr#8=[1:BIGINT], expr#9=[true],
EMPNO1=[$t0], $f1=[$t8], $f2=[$t8], EMPNO=[$t0], $f4=[$t9])
@@ -2845,10 +2845,10 @@ select *
from "scott".emp emp1
where empno <> some (select comm from "scott".emp where deptno = emp1.deptno);
EnumerableCalc(expr#0..13=[{inputs}], expr#14=[<>($t10, $t9)], expr#15=[1],
expr#16=[<=($t11, $t15)], expr#17=[AND($t14, $t16)], expr#18=[=($t11, $t15)],
expr#19=[OR($t17, $t18)], expr#20=[<>($t0, $t12)], expr#21=[IS NULL($t13)],
expr#22=[0], expr#23=[=($t9, $t22)], expr#24=[OR($t21, $t23)], expr#25=[IS NOT
TRUE($t24)], expr#26=[AND($t19, $t20, $t25)], expr#27=[IS NOT TRUE($t19)],
expr#28=[AND($t25, $t27)], expr#29=[OR($t26, $t28)], proj#0..7=[{exprs}],
$condition=[$t29])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($7, $8)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($7, $8)], joinType=[left])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..6=[{inputs}], expr#7=[IS NOT NULL($t2)],
expr#8=[0], expr#9=[CASE($t7, $t2, $t8)], expr#10=[IS NOT NULL($t3)],
expr#11=[CASE($t10, $t3, $t8)], expr#12=[IS NOT NULL($t4)], expr#13=[CASE($t12,
$t4, $t8)], DEPTNO=[$t0], c=[$t9], d=[$t11], dd=[$t13], m=[$t5],
trueLiteral=[$t6])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableAggregate(group=[{7}])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..5=[{inputs}], expr#6=[CAST($t1):BIGINT NOT
NULL], expr#7=[CAST($t2):BIGINT NOT NULL], expr#8=[CAST($t5):BOOLEAN NOT NULL],
DEPTNO=[$t0], c=[$t6], d=[$t7], dd=[$t3], m=[$t4], trueLiteral=[$t8])
@@ -2905,7 +2905,7 @@ EnumerableCalc(expr#0..13=[{inputs}], expr#14=[<>($t9,
$t8)], expr#15=[1], expr#
EnumerableHashJoin(condition=[=($0, $13)], joinType=[left])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..5=[{inputs}], expr#6=[IS NOT NULL($t2)],
expr#7=[0], expr#8=[CASE($t6, $t2, $t7)], expr#9=[IS NOT NULL($t3)],
expr#10=[CASE($t9, $t3, $t7)], c=[$t8], d=[$t8], dd=[$t10], m=[$t4],
trueLiteral=[$t5], DEPTNO0=[$t0])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..4=[{inputs}], expr#5=[CAST($t1):BIGINT NOT
NULL], expr#6=[CAST($t3):INTEGER NOT NULL], expr#7=[CAST($t4):BOOLEAN NOT
NULL], DEPTNO0=[$t0], c=[$t5], dd=[$t2], m=[$t6], trueLiteral=[$t7])
@@ -2956,7 +2956,7 @@ EnumerableCalc(expr#0..13=[{inputs}], expr#14=[<>($t9,
$t8)], expr#15=[1], expr#
EnumerableHashJoin(condition=[=($0, $13)], joinType=[left])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..5=[{inputs}], expr#6=[IS NOT NULL($t2)],
expr#7=[0], expr#8=[CASE($t6, $t2, $t7)], expr#9=[IS NOT NULL($t3)],
expr#10=[CASE($t9, $t3, $t7)], c=[$t8], d=[$t8], dd=[$t10], m=[$t4],
trueLiteral=[$t5], DEPTNO0=[$t0])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..4=[{inputs}], expr#5=[CAST($t1):BIGINT NOT
NULL], expr#6=[CAST($t3):INTEGER NOT NULL], expr#7=[CAST($t4):BOOLEAN NOT
NULL], DEPTNO0=[$t0], c=[$t5], dd=[$t2], m=[$t6], trueLiteral=[$t7])
@@ -3004,10 +3004,10 @@ select *
from "scott".emp emp1
where emp1.comm <> some (select comm from "scott".emp emp2 where emp2.sal =
emp1.sal);
EnumerableCalc(expr#0..13=[{inputs}], expr#14=[<>($t10, $t9)], expr#15=[1],
expr#16=[<=($t11, $t15)], expr#17=[AND($t14, $t16)], expr#18=[=($t11, $t15)],
expr#19=[OR($t17, $t18)], expr#20=[<>($t6, $t12)], expr#21=[IS NULL($t13)],
expr#22=[IS NULL($t6)], expr#23=[0], expr#24=[=($t9, $t23)], expr#25=[OR($t21,
$t22, $t24)], expr#26=[IS NOT TRUE($t25)], expr#27=[AND($t19, $t20, $t26)],
expr#28=[IS NOT TRUE($t19)], expr#29=[AND($t26, $t28)], expr#30=[OR($t27,
$t29)], proj#0..7=[{exprs}], $con [...]
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($5, $8)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($5, $8)], joinType=[left])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..6=[{inputs}], expr#7=[IS NOT NULL($t2)],
expr#8=[0], expr#9=[CASE($t7, $t2, $t8)], expr#10=[IS NOT NULL($t3)],
expr#11=[CASE($t10, $t3, $t8)], expr#12=[IS NOT NULL($t4)], expr#13=[CASE($t12,
$t4, $t8)], SAL=[$t0], c=[$t9], d=[$t11], dd=[$t13], m=[$t5], trueLiteral=[$t6])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableAggregate(group=[{5}])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..5=[{inputs}], expr#6=[CAST($t1):BIGINT NOT
NULL], expr#7=[CAST($t2):BIGINT NOT NULL], expr#8=[CAST($t5):BOOLEAN NOT NULL],
SAL=[$t0], c=[$t6], d=[$t7], dd=[$t3], m=[$t4], trueLiteral=[$t8])
@@ -5388,10 +5388,10 @@ select * from emp where deptno <> (select count(deptno)
from dept where dept.dep
!ok
EnumerableCalc(expr#0..4=[{inputs}], expr#5=[IS NULL($t4)],
expr#6=[CAST($t1):BIGINT], expr#7=[0:BIGINT], expr#8=[<>($t6, $t7)],
expr#9=[AND($t5, $t8)], expr#10=[<>($t6, $t4)], expr#11=[OR($t9, $t10)],
proj#0..2=[{exprs}], $condition=[$t11])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($1, $3)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($1, $3)], joinType=[left])
EnumerableValues(tuples=[[{ 'Jane ', 10, 'F' }, { 'Bob ', 10, 'M' }, {
'Eric ', 20, 'M' }, { 'Susan', 30, 'F' }, { 'Alice', 30, 'F' }, { 'Adam ', 50,
'M' }, { 'Eve ', 50, 'F' }, { 'Grace', 60, 'F' }, { 'Wilma', null, 'F' }]])
EnumerableCalc(expr#0..2=[{inputs}], expr#3=[IS NOT NULL($t2)],
expr#4=[0], expr#5=[CASE($t3, $t2, $t4)], DEPTNO=[$t0], EXPR$0=[$t5])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)],
joinType=[left])
EnumerableAggregate(group=[{0}])
EnumerableValues(tuples=[[{ 10 }, { 10 }, { 20 }, { 30 }, { 30 }, {
50 }, { 50 }, { 60 }, { null }]])
EnumerableCalc(expr#0..1=[{inputs}], expr#2=[1:BIGINT], DEPTNO=[$t0],
$f1=[$t2])
@@ -5414,7 +5414,7 @@ select * from emp where deptno <> (select count(deptno) +
10 from dept where de
!ok
EnumerableCalc(expr#0..4=[{inputs}], expr#5=[CAST($t1):BIGINT], expr#6=[IS
NULL($t4)], expr#7=[0:BIGINT], expr#8=[CASE($t6, $t7, $t4)], expr#9=[10],
expr#10=[+($t8, $t9)], expr#11=[<>($t5, $t10)], proj#0..2=[{exprs}],
$condition=[$t11])
- EnumerableNestedLoopJoin(condition=[IS NOT DISTINCT FROM($1, $3)],
joinType=[left])
+ EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($1, $3)], joinType=[left])
EnumerableValues(tuples=[[{ 'Jane ', 10, 'F' }, { 'Bob ', 10, 'M' }, {
'Eric ', 20, 'M' }, { 'Susan', 30, 'F' }, { 'Alice', 30, 'F' }, { 'Adam ', 50,
'M' }, { 'Eve ', 50, 'F' }, { 'Grace', 60, 'F' }, { 'Wilma', null, 'F' }]])
EnumerableCalc(expr#0=[{inputs}], expr#1=[0], expr#2=[CAST($t1):BIGINT NOT
NULL], DEPTNO=[$t0], $f1=[$t2])
EnumerableAggregate(group=[{0}])
diff --git
a/linq4j/src/main/java/org/apache/calcite/linq4j/EnumerableDefaults.java
b/linq4j/src/main/java/org/apache/calcite/linq4j/EnumerableDefaults.java
index 335fbc89b2..14309bdfd6 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/EnumerableDefaults.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/EnumerableDefaults.java
@@ -1543,18 +1543,15 @@ private static <TSource, TInner, TKey, TResult>
Enumerable<TResult> hashEquiJoin
}
final TSource outer = outers.current();
final Enumerable<TInner> innerEnumerable;
- if (outer == null) {
+ // if the key is null-safe, still extract outerKey and probe
even if outer is NULL.
+ final TKey outerKey = outerKeySelector.apply(outer);
+ if (outerKey == null) {
innerEnumerable = null;
} else {
- final TKey outerKey = outerKeySelector.apply(outer);
- if (outerKey == null) {
- innerEnumerable = null;
- } else {
- if (unmatchedKeys != null) {
- unmatchedKeys.remove(outerKey);
- }
- innerEnumerable = innerLookup.get(outerKey);
+ if (unmatchedKeys != null) {
+ unmatchedKeys.remove(outerKey);
}
+ innerEnumerable = innerLookup.get(outerKey);
}
if (innerEnumerable == null
|| !innerEnumerable.any()) {
@@ -1639,30 +1636,27 @@ private static <TSource, TInner, TKey, TResult>
Enumerable<TResult> hashJoinWith
}
final TSource outer = outers.current();
Enumerable<TInner> innerEnumerable;
- if (outer == null) {
+ // if the key is null-safe, still extract outerKey and probe
even if outer is NULL.
+ final TKey outerKey = outerKeySelector.apply(outer);
+ if (outerKey == null) {
innerEnumerable = null;
} else {
- final TKey outerKey = outerKeySelector.apply(outer);
- if (outerKey == null) {
- innerEnumerable = null;
- } else {
- innerEnumerable = innerLookup.get(outerKey);
- // apply predicate to filter per-row
- if (innerEnumerable != null) {
- final List<TInner> matchedInners = new ArrayList<>();
- try (Enumerator<TInner> innerEnumerator =
- innerEnumerable.enumerator()) {
- while (innerEnumerator.moveNext()) {
- final TInner inner = innerEnumerator.current();
- if (predicate.apply(outer, inner)) {
- matchedInners.add(inner);
- }
+ innerEnumerable = innerLookup.get(outerKey);
+ // apply predicate to filter per-row
+ if (innerEnumerable != null) {
+ final List<TInner> matchedInners = new ArrayList<>();
+ try (Enumerator<TInner> innerEnumerator =
+ innerEnumerable.enumerator()) {
+ while (innerEnumerator.moveNext()) {
+ final TInner inner = innerEnumerator.current();
+ if (predicate.apply(outer, inner)) {
+ matchedInners.add(inner);
}
}
- innerEnumerable = Linq4j.asEnumerable(matchedInners);
- if (innersUnmatched != null) {
- innersUnmatched.removeAll(matchedInners);
- }
+ }
+ innerEnumerable = Linq4j.asEnumerable(matchedInners);
+ if (innersUnmatched != null) {
+ innersUnmatched.removeAll(matchedInners);
}
}
}