This is an automated email from the ASF dual-hosted git repository.

alexpl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 9f52415b2aa IGNITE-26048 SQL Calcite: Fix incorrect processing of IS 
NOT DISTINCT condition in MergeJoin - Fixes #12284.
9f52415b2aa is described below

commit 9f52415b2aafa56a4d72785135fbcd2c4d6f9bd0
Author: Vladimir Steshin <[email protected]>
AuthorDate: Tue Oct 28 17:52:11 2025 +0300

    IGNITE-26048 SQL Calcite: Fix incorrect processing of IS NOT DISTINCT 
condition in MergeJoin - Fixes #12284.
    
    Signed-off-by: Aleksey Plekhanov <[email protected]>
---
 .../query/calcite/exec/LogicalRelImplementor.java  | 40 ++++++++++--
 .../query/calcite/exec/exp/ExpressionFactory.java  | 11 ++--
 .../calcite/exec/exp/ExpressionFactoryImpl.java    | 17 +++--
 .../query/calcite/rel/IgniteJoinInfo.java          | 76 ++++++++++++++++++++++
 .../query/calcite/rel/IgniteMergeJoin.java         | 25 +++++--
 .../query/calcite/rule/MergeJoinConverterRule.java | 13 ++--
 .../calcite/integration/JoinIntegrationTest.java   | 43 ++++++++++++
 7 files changed, 200 insertions(+), 25 deletions(-)

diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
index 100f9cb0996..9e05f5bf2c2 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
@@ -27,6 +27,7 @@ import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Intersect;
 import org.apache.calcite.rel.core.JoinRelType;
@@ -37,6 +38,7 @@ import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.mapping.IntPair;
 import org.apache.ignite.internal.processors.failure.FailureProcessor;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
@@ -116,8 +118,6 @@ import 
org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
 import org.apache.ignite.internal.util.typedef.F;
 
 import static org.apache.calcite.rel.RelDistribution.Type.HASH_DISTRIBUTED;
-import static org.apache.calcite.sql.SqlKind.IS_DISTINCT_FROM;
-import static org.apache.calcite.sql.SqlKind.IS_NOT_DISTINCT_FROM;
 import static 
org.apache.ignite.internal.processors.query.calcite.util.TypeUtils.combinedRowType;
 
 /**
@@ -298,12 +298,40 @@ public class LogicalRelImplementor<Row> implements 
IgniteRelVisitor<Node<Row>> {
         RelDataType rightType = rel.getRight().getRowType();
         JoinRelType joinType = rel.getJoinType();
 
-        int pairsCnt = rel.analyzeCondition().pairs().size();
+        List<IntPair> joinPairs = rel.analyzeCondition().pairs();
+        int pairsCnt = joinPairs.size();
+
+        List<RelFieldCollation> leftCollations = 
rel.leftCollation().getFieldCollations();
+        List<RelFieldCollation> rightCollations = 
rel.rightCollation().getFieldCollations();
+
+        ImmutableBitSet allowNulls = rel.allowNulls();
+        ImmutableBitSet.Builder collsAllowNullsBuilder = 
ImmutableBitSet.builder();
+        int lastCollField = -1;
+
+        for (int c = 0; c < Math.min(leftCollations.size(), 
rightCollations.size()); ++c) {
+            RelFieldCollation leftColl = leftCollations.get(c);
+            RelFieldCollation rightColl = rightCollations.get(c);
+            collsAllowNullsBuilder.set(c);
+
+            for (int p = 0; p < pairsCnt; ++p) {
+                IntPair pair = joinPairs.get(p);
+
+                if (pair.source == leftColl.getFieldIndex() && pair.target == 
rightColl.getFieldIndex()) {
+                    lastCollField = c;
+
+                    if (!allowNulls.get(p)) {
+                        collsAllowNullsBuilder.clear(c);
+
+                        break;
+                    }
+                }
+            }
+        }
 
         Comparator<Row> comp = expressionFactory.comparator(
-            rel.leftCollation().getFieldCollations().subList(0, pairsCnt),
-            rel.rightCollation().getFieldCollations().subList(0, pairsCnt),
-            rel.getCondition().getKind() == IS_NOT_DISTINCT_FROM || 
rel.getCondition().getKind() == IS_DISTINCT_FROM
+            leftCollations.subList(0, lastCollField + 1),
+            rightCollations.subList(0, lastCollField + 1),
+            collsAllowNullsBuilder.build()
         );
 
         Node<Row> node = MergeJoinNode.create(ctx, outType, leftType, 
rightType, joinType, comp, hasExchange(rel));
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java
index 3fb64377322..3fe58949d01 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java
@@ -23,13 +23,13 @@ import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
-
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ImmutableBitSet;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorWrapper;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggregateType;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
@@ -60,11 +60,14 @@ public interface ExpressionFactory<Row> {
      *
      * @param left Collations of left row.
      * @param right Collations of right row.
-     * @param nullsEqual If {@code true}, nulls are considered equal. Usually, 
NULL <> NULL in SQL. So, the value should
-     *                   be {@code false}. Except cases with IS DISTINCT / IS 
NOT DISTINCT.
+     * @param allowNulls Matching null fields. Usually, NULL <> NULL in SQL. 
Except IS DISTINCT / IS NOT DISTINCT.
      * @return Rows comparator.
      */
-    Comparator<Row> comparator(List<RelFieldCollation> left, 
List<RelFieldCollation> right, boolean nullsEqual);
+    Comparator<Row> comparator(
+        List<RelFieldCollation> left,
+        List<RelFieldCollation> right,
+        ImmutableBitSet allowNulls
+    );
 
     /**
      * Creates a Filter predicate.
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
index cefe295842d..7f543385d33 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
@@ -62,6 +62,7 @@ import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SqlConformance;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.binary.BinaryUtils;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
 import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
@@ -197,7 +198,11 @@ public class ExpressionFactoryImpl<Row> implements 
ExpressionFactory<Row> {
     }
 
     /** {@inheritDoc} */
-    @Override public Comparator<Row> comparator(List<RelFieldCollation> left, 
List<RelFieldCollation> right, boolean nullsEqual) {
+    @Override public Comparator<Row> comparator(
+        List<RelFieldCollation> left,
+        List<RelFieldCollation> right,
+        ImmutableBitSet allowNulls
+    ) {
         if (F.isEmpty(left) || F.isEmpty(right) || left.size() != right.size())
             throw new IllegalArgumentException("Both inputs should be 
non-empty and have the same size: left="
                 + (left != null ? left.size() : "null") + ", right=" + (right 
!= null ? right.size() : "null"));
@@ -213,9 +218,10 @@ public class ExpressionFactoryImpl<Row> implements 
ExpressionFactory<Row> {
 
         return new Comparator<Row>() {
             @Override public int compare(Row o1, Row o2) {
-                boolean hasNulls = false;
                 RowHandler<Row> hnd = ctx.rowHandler();
 
+                boolean hasNulls = false;
+
                 for (int i = 0; i < left.size(); i++) {
                     RelFieldCollation leftField = left.get(i);
                     RelFieldCollation rightField = right.get(i);
@@ -226,8 +232,9 @@ public class ExpressionFactoryImpl<Row> implements 
ExpressionFactory<Row> {
                     Object c1 = hnd.get(lIdx, o1);
                     Object c2 = hnd.get(rIdx, o2);
 
-                    if (c1 == null && c2 == null) {
-                        hasNulls = true;
+                    if (c1 == null && c2 == null && !hasNulls) {
+                        hasNulls = !allowNulls.get(i);
+
                         continue;
                     }
 
@@ -243,7 +250,7 @@ public class ExpressionFactoryImpl<Row> implements 
ExpressionFactory<Row> {
 
                 // If compared rows contain NULLs, they shouldn't be treated 
as equals, since NULL <> NULL in SQL.
                 // Except cases with IS DISTINCT / IS NOT DISTINCT.
-                return hasNulls && !nullsEqual ? 1 : 0;
+                return hasNulls ? 1 : 0;
             }
         };
     }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteJoinInfo.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteJoinInfo.java
new file mode 100644
index 00000000000..ea3db260982
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteJoinInfo.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import java.util.ArrayList;
+import java.util.List;
+import com.google.common.collect.ImmutableList;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinInfo;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.ImmutableIntList;
+
+/** Extended {@link JoinInfo}. */
+public class IgniteJoinInfo extends JoinInfo {
+    /** Conditions with mathing nulls. It usually means presence of 'IS 
DISTINCT' / 'IS NOT DISTINCT'. */
+    private final ImmutableBitSet allowNulls;
+
+    /** */
+    protected IgniteJoinInfo(
+        ImmutableIntList leftKeys,
+        ImmutableIntList rightKeys,
+        ImmutableBitSet allowNulls,
+        ImmutableList<RexNode> nonEquis
+    ) {
+        super(leftKeys, rightKeys, nonEquis);
+
+        this.allowNulls = allowNulls;
+    }
+
+    /** */
+    public static IgniteJoinInfo of(Join join) {
+        List<Integer> leftKeys = new ArrayList<>();
+        List<Integer> rightKeys = new ArrayList<>();
+        List<Boolean> skipNulls = new ArrayList<>();
+        List<RexNode> nonEquis = new ArrayList<>();
+
+        RelOptUtil.splitJoinCondition(join.getLeft(), join.getRight(), 
join.getCondition(), leftKeys, rightKeys,
+            skipNulls, nonEquis);
+
+        ImmutableBitSet.Builder allowNulls = ImmutableBitSet.builder();
+
+        for (int i = 0; i < skipNulls.size(); ++i) {
+            if (!skipNulls.get(i))
+                allowNulls.set(i);
+        }
+
+        return new IgniteJoinInfo(
+            ImmutableIntList.copyOf(leftKeys),
+            ImmutableIntList.copyOf(rightKeys),
+            allowNulls == null ? ImmutableBitSet.of() : 
ImmutableBitSet.of(allowNulls.build()),
+            ImmutableList.copyOf(nonEquis)
+        );
+    }
+
+    /** */
+    public ImmutableBitSet allowNulls() {
+        return allowNulls;
+    }
+}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
index 15d2ee58c07..c1dae0e6f90 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java
@@ -20,7 +20,6 @@ package 
org.apache.ignite.internal.processors.query.calcite.rel;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
-
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import org.apache.calcite.plan.RelOptCluster;
@@ -45,6 +44,7 @@ import 
org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteC
 import 
org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCostFactory;
 import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.jetbrains.annotations.Nullable;
 
 import static org.apache.calcite.rel.RelCollations.EMPTY;
 import static org.apache.calcite.rel.RelCollations.containsOrderless;
@@ -64,6 +64,9 @@ public class IgniteMergeJoin extends AbstractIgniteJoin {
      */
     private final RelCollation rightCollation;
 
+    /** Mathing null conditions like 'IS NOT DISTINCT'. */
+    private final ImmutableBitSet allowNulls;
+
     /** */
     public IgniteMergeJoin(
         RelOptCluster cluster,
@@ -71,10 +74,11 @@ public class IgniteMergeJoin extends AbstractIgniteJoin {
         RelNode left,
         RelNode right,
         RexNode condition,
+        @Nullable ImmutableBitSet allowNulls,
         Set<CorrelationId> variablesSet,
         JoinRelType joinType
     ) {
-        this(cluster, traitSet, left, right, condition, variablesSet, joinType,
+        this(cluster, traitSet, left, right, condition, allowNulls, 
variablesSet, joinType,
             left.getTraitSet().getCollation(), 
right.getTraitSet().getCollation());
     }
 
@@ -86,6 +90,7 @@ public class IgniteMergeJoin extends AbstractIgniteJoin {
             input.getInputs().get(0),
             input.getInputs().get(1),
             input.getExpression("condition"),
+            input.getBitSet("allowNulls"),
             
ImmutableSet.copyOf(Commons.transform(input.getIntegerList("variablesSet"), 
CorrelationId::new)),
             input.getEnum("joinType", JoinRelType.class),
             ((RelInputEx)input).getCollation("leftCollation"),
@@ -100,6 +105,7 @@ public class IgniteMergeJoin extends AbstractIgniteJoin {
         RelNode left,
         RelNode right,
         RexNode condition,
+        @Nullable ImmutableBitSet allowNulls,
         Set<CorrelationId> variablesSet,
         JoinRelType joinType,
         RelCollation leftCollation,
@@ -109,12 +115,13 @@ public class IgniteMergeJoin extends AbstractIgniteJoin {
 
         this.leftCollation = leftCollation;
         this.rightCollation = rightCollation;
+        this.allowNulls = allowNulls == null ? ImmutableBitSet.of() : 
allowNulls;
     }
 
     /** {@inheritDoc} */
     @Override public Join copy(RelTraitSet traitSet, RexNode condition, 
RelNode left, RelNode right,
         JoinRelType joinType, boolean semiJoinDone) {
-        return new IgniteMergeJoin(getCluster(), traitSet, left, right, 
condition, variablesSet, joinType,
+        return new IgniteMergeJoin(getCluster(), traitSet, left, right, 
condition, allowNulls, variablesSet, joinType,
             left.getTraitSet().getCollation(), 
right.getTraitSet().getCollation());
     }
 
@@ -125,7 +132,7 @@ public class IgniteMergeJoin extends AbstractIgniteJoin {
 
     /** {@inheritDoc} */
     @Override public IgniteRel clone(RelOptCluster cluster, List<IgniteRel> 
inputs) {
-        return new IgniteMergeJoin(cluster, getTraitSet(), inputs.get(0), 
inputs.get(1), getCondition(),
+        return new IgniteMergeJoin(cluster, getTraitSet(), inputs.get(0), 
inputs.get(1), getCondition(), allowNulls,
             getVariablesSet(), getJoinType(), leftCollation, rightCollation);
     }
 
@@ -270,7 +277,8 @@ public class IgniteMergeJoin extends AbstractIgniteJoin {
     @Override public RelWriter explainTerms(RelWriter pw) {
         return super.explainTerms(pw)
             .item("leftCollation", leftCollation)
-            .item("rightCollation", rightCollation);
+            .item("rightCollation", rightCollation)
+            .item("allowNulls", allowNulls);
     }
 
     /**
@@ -287,6 +295,13 @@ public class IgniteMergeJoin extends AbstractIgniteJoin {
         return rightCollation;
     }
 
+    /**
+     * @return Matching null conditions.
+     */
+    public ImmutableBitSet allowNulls() {
+        return allowNulls;
+    }
+
     /** Creates pair with default collation for parent and simple yet 
sufficient collation for children nodes. */
     private Pair<RelTraitSet, List<RelTraitSet>> defaultCollationPair(
         RelTraitSet nodeTraits,
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/MergeJoinConverterRule.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/MergeJoinConverterRule.java
index 13288a808d7..68feee9084c 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/MergeJoinConverterRule.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/MergeJoinConverterRule.java
@@ -25,11 +25,11 @@ import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.PhysicalNode;
 import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.core.JoinInfo;
 import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition;
 import 
org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteJoinInfo;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteMergeJoin;
 import org.apache.ignite.internal.util.typedef.F;
 
@@ -51,16 +51,18 @@ public class MergeJoinConverterRule extends 
AbstractIgniteJoinConverterRule {
     @Override public boolean matchesJoin(RelOptRuleCall call) {
         LogicalJoin logicalJoin = call.rel(0);
 
-        JoinInfo joinInfo = JoinInfo.of(logicalJoin.getLeft(), 
logicalJoin.getRight(), logicalJoin.getCondition());
+        IgniteJoinInfo info = IgniteJoinInfo.of(logicalJoin);
 
-        return !F.isEmpty(joinInfo.pairs()) && joinInfo.isEqui();
+        return info.isEqui() && !F.isEmpty(info.pairs());
     }
 
     /** {@inheritDoc} */
     @Override protected PhysicalNode convert(RelOptPlanner planner, 
RelMetadataQuery mq, LogicalJoin rel) {
         RelOptCluster cluster = rel.getCluster();
 
-        JoinInfo joinInfo = JoinInfo.of(rel.getLeft(), rel.getRight(), 
rel.getCondition());
+        IgniteJoinInfo joinInfo = IgniteJoinInfo.of(rel);
+
+        assert joinInfo.isEqui() && !F.isEmpty(joinInfo.pairs());
 
         RelTraitSet leftInTraits = 
cluster.traitSetOf(IgniteConvention.INSTANCE)
             .replace(RelCollations.of(joinInfo.leftKeys));
@@ -71,6 +73,7 @@ public class MergeJoinConverterRule extends 
AbstractIgniteJoinConverterRule {
         RelNode left = convert(rel.getLeft(), leftInTraits);
         RelNode right = convert(rel.getRight(), rightInTraits);
 
-        return new IgniteMergeJoin(cluster, outTraits, left, right, 
rel.getCondition(), rel.getVariablesSet(), rel.getJoinType());
+        return new IgniteMergeJoin(cluster, outTraits, left, right, 
rel.getCondition(), joinInfo.allowNulls(),
+            rel.getVariablesSet(), rel.getJoinType());
     }
 }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/JoinIntegrationTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/JoinIntegrationTest.java
index d62178c56ee..e9af5c24ed8 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/JoinIntegrationTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/JoinIntegrationTest.java
@@ -65,6 +65,49 @@ public class JoinIntegrationTest extends 
AbstractBasicIntegrationTransactionalTe
         // NO-OP
     }
 
+    /** */
+    @Test
+    public void testIsNotDistinctWithEquiConditionFrom() {
+        // 'IS NOT DISTINCT' is first.
+        assertQuery("select t1.c2, t1.c3, t2.c2, t2.c3 from t1 join t2 on 
t1.c2 is not distinct from t2.c2 and t1.c3 = t2.c3 - 1")
+            .returns(null, 2, null, 3)
+            .check();
+
+        // 'IS NOT DISTINCT' is second.
+        assertQuery("select t1.c2, t1.c3, t2.c2, t2.c3 from t1 join t2 on 
t1.c3 = t2.c3 - 1 and t1.c2 is not distinct from t2.c2")
+            .returns(null, 2, null, 3)
+            .check();
+
+        // Duplicated condition.
+        assertQuery("select t1.c2, t1.c3, t2.c2, t2.c3 from t1 join t2 on 
t1.c2 is not distinct from t2.c2 " +
+            "and t1.c2 is not distinct from t2.c2 and t1.c3 = t2.c3 - 1")
+            .returns(null, 2, null, 3)
+            .check();
+        assertQuery("select t1.c2, t1.c3, t2.c2, t2.c3 from t1 join t2 on 
t1.c3 = t2.c3 - 1 and t1.c3 = t2.c3 - 1" +
+            "and t1.c2 is not distinct from t2.c2 and t1.c2 is not distinct 
from t2.c2")
+            .returns(null, 2, null, 3)
+            .check();
+
+        // Other fields.
+        assertQuery("select t1.c2, t1.c3, t2.c1, t2.c2 from t1 join t2 on 
t1.c3 is not distinct from t2.c2 - 1 " +
+            "and t1.c2 = t2.c1")
+            .returns(3, null, 3, null)
+            .check();
+        assertQuery("select t1.c2, t1.c3, t2.c2, t2.c3 from t1 join t2 " +
+            "on t1.c2 is not distinct from t2.c2 and t1.c2 = t2.c2 and t1.c3 = 
t2.c3 and t1.c3 is not distinct from t2.c3")
+            .returns(1, 1, 1, 1)
+            .returns(2, 2, 2, 2)
+            .returns(3, 3, 3, 3)
+            .returns(4, 4, 4, 4)
+            .check();
+
+        // Two 'IS NOT DISTINCT's.
+        assertQuery("select t1.c2, t1.c3, t2.c2, t2.c3 from t1 join t2 on 
t1.c3 is not distinct from t2.c3 - 1 " +
+            "and t1.c2 is not distinct from t2.c2")
+            .returns(null, 2, null, 3)
+            .check();
+    }
+
     /**
      * Test verifies result of inner join with different ordering.
      */

Reply via email to