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.
*/