This is an automated email from the ASF dual-hosted git repository.
zstan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new d9cbd44f8b IGNITE-18442 Sql. JOIN with DISTINCT FROM fails - Fixes
#1476.
d9cbd44f8b is described below
commit d9cbd44f8bfc05cadd994e0fd155bfb4e0f45383
Author: zstan <[email protected]>
AuthorDate: Thu Jan 5 16:57:07 2023 +0300
IGNITE-18442 Sql. JOIN with DISTINCT FROM fails - Fixes #1476.
Signed-off-by: zstan <[email protected]>
---
.../sql/engine/AbstractBasicIntegrationTest.java | 42 +++
.../ignite/internal/sql/engine/ItDmlTest.java | 13 +-
.../ignite/internal/sql/engine/ItJoinTest.java | 91 +++--
.../internal/sql/engine/ItSecondaryIndexTest.java | 4 +
.../ignite/internal/sql/engine/ItSetOpTest.java | 2 +-
.../internal/sql/engine/ItSqlOperatorsTest.java | 2 +
.../ignite/internal/sqllogic/ItSqlLogicTest.java | 6 -
.../sql/join/test_not_distinct_from.test | 30 ++
.../sql/join/test_not_distinct_from.test_ignore | 2 +-
.../internal/sql/engine/exec/ExecutionContext.java | 7 -
.../sql/engine/exec/LogicalRelImplementor.java | 36 +-
.../internal/sql/engine/exec/RuntimeHashIndex.java | 9 +-
.../sql/engine/exec/exp/ExpressionFactory.java | 4 +-
.../sql/engine/exec/exp/ExpressionFactoryImpl.java | 97 +++---
.../sql/engine/exec/exp/RangeCondition.java | 8 +-
.../internal/sql/engine/exec/exp/RexImpTable.java | 5 +
.../sql/engine/exec/rel/IndexSpoolNode.java | 5 +-
.../sql/engine/rel/IgniteHashIndexSpool.java | 22 +-
.../internal/sql/engine/rel/IgniteMergeJoin.java | 5 +-
.../engine/rel/logical/IgniteLogicalIndexScan.java | 4 +-
.../rule/FilterSpoolMergeToHashIndexSpoolRule.java | 20 +-
.../FilterSpoolMergeToSortedIndexSpoolRule.java | 2 +-
.../sql/engine/rule/MergeJoinConverterRule.java | 3 +-
.../ignite/internal/sql/engine/util/Commons.java | 8 +-
.../internal/sql/engine/util/IgniteMethod.java | 6 +-
.../ignite/internal/sql/engine/util/RexUtils.java | 72 ++--
.../exec/rel/HashIndexSpoolExecutionTest.java | 6 +-
.../engine/exec/rel/MergeJoinExecutionTest.java | 388 +++++++++++++--------
.../prepare/ddl/DdlSqlToCommandConverterTest.java | 13 -
.../internal/storage/impl/TestMvTableStorage.java | 2 +-
30 files changed, 606 insertions(+), 308 deletions(-)
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/AbstractBasicIntegrationTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/AbstractBasicIntegrationTest.java
index 32dac50f4b..58201db721 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/AbstractBasicIntegrationTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/AbstractBasicIntegrationTest.java
@@ -31,11 +31,14 @@ import static org.junit.jupiter.api.Assertions.assertSame;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import java.util.stream.Stream;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgnitionManager;
import org.apache.ignite.internal.app.IgniteImpl;
@@ -207,6 +210,45 @@ public class AbstractBasicIntegrationTest extends
BaseIgniteAbstractTest {
};
}
+ /**
+ * Used for join checks, disables other join rules for executing exact
join algo.
+ *
+ * @param qry Query for check.
+ * @param joinType Type of join algo.
+ * @param rules Additional rules need to be disabled.
+ */
+ static QueryChecker assertQuery(String qry, JoinType joinType, String...
rules) {
+ return
AbstractBasicIntegrationTest.assertQuery(qry.replaceAll("(?i)^select", "select "
+ + Stream.concat(Arrays.stream(joinType.disabledRules),
Arrays.stream(rules))
+ .collect(Collectors.joining("','", "/*+ DISABLE_RULE('", "')
*/"))));
+ }
+
+ enum JoinType {
+ NESTED_LOOP(
+ "CorrelatedNestedLoopJoin",
+ "JoinCommuteRule",
+ "MergeJoinConverter"
+ ),
+
+ MERGE(
+ "CorrelatedNestedLoopJoin",
+ "JoinCommuteRule",
+ "NestedLoopJoinConverter"
+ ),
+
+ CORRELATED(
+ "MergeJoinConverter",
+ "JoinCommuteRule",
+ "NestedLoopJoinConverter"
+ );
+
+ private final String[] disabledRules;
+
+ JoinType(String... disabledRules) {
+ this.disabledRules = disabledRules;
+ }
+ }
+
protected static void createAndPopulateTable() {
sql("CREATE TABLE person (id INT PRIMARY KEY, name VARCHAR, salary
DOUBLE)");
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
index ab5665a40f..a77adc0454 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
@@ -29,15 +29,13 @@ import java.util.List;
import java.util.stream.Collectors;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.ignite.internal.sql.engine.exec.rel.AbstractNode;
-import org.apache.ignite.internal.sql.engine.util.Commons;
-import org.apache.ignite.internal.testframework.IgniteTestUtils;
import org.apache.ignite.internal.testframework.WithSystemProperty;
import org.apache.ignite.lang.ErrorGroups.Sql;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.sql.SqlException;
import org.apache.ignite.tx.Transaction;
-import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
@@ -64,9 +62,10 @@ public class ItDmlTest extends AbstractBasicIntegrationTest {
super.tearDownBase(testInfo);
}
- @AfterAll
- public static void resetStaticState() {
- IgniteTestUtils.setFieldValue(Commons.class, "implicitPkEnabled",
null);
+ @BeforeAll
+ public static void beforeTestsStarted() {
+ // IMPLICIT_PK_ENABLED hashed sys property needs
+ sql("CREATE TABLE fake_tbl (id INT PRIMARY KEY, c1 INT NOT NULL)");
}
@Test
@@ -408,8 +407,6 @@ public class ItDmlTest extends AbstractBasicIntegrationTest
{
@Test
@WithSystemProperty(key = "IMPLICIT_PK_ENABLED", value = "true")
public void implicitPk() {
- IgniteTestUtils.setFieldValue(Commons.class, "implicitPkEnabled",
null);
-
sql("CREATE TABLE T(VAL INT)");
sql("INSERT INTO t VALUES (1), (2), (3)");
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItJoinTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItJoinTest.java
index 043daefe4e..369c46b09c 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItJoinTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItJoinTest.java
@@ -17,14 +17,19 @@
package org.apache.ignite.internal.sql.engine;
+import static
org.apache.ignite.internal.sql.engine.AbstractBasicIntegrationTest.JoinType.CORRELATED;
+
import java.util.Arrays;
import java.util.List;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.ignite.internal.sql.engine.util.QueryChecker;
+import org.apache.ignite.internal.testframework.WithSystemProperty;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
/**
* Check JOIN on basic cases.
@@ -784,34 +789,66 @@ public class ItJoinTest extends
AbstractBasicIntegrationTest {
// .check();
}
- protected QueryChecker assertQuery(String qry, JoinType joinType) {
- return AbstractBasicIntegrationTest.assertQuery(qry.replace("select",
"select "
- +
Arrays.stream(joinType.disabledRules).collect(Collectors.joining("','", "/*+
DISABLE_RULE('", "') */"))));
- }
+ /** Check IS NOT DISTINCT execution correctness and IndexSpool presence. */
+ @ParameterizedTest(name = "join algo : {0}, index present: {1}")
+ @MethodSource("joinTypes")
+ @WithSystemProperty(key = "IMPLICIT_PK_ENABLED", value = "true")
+ public void testIsNotDistinctFrom(JoinType joinType, boolean indexScan) {
+ if (indexScan) {
+ // TODO: https://issues.apache.org/jira/browse/IGNITE-18468 Index
scan eventually return partial data.
+ return;
+ }
- enum JoinType {
- NESTED_LOOP(
- "CorrelatedNestedLoopJoin",
- "JoinCommuteRule",
- "MergeJoinConverter"
- ),
-
- MERGE(
- "CorrelatedNestedLoopJoin",
- "JoinCommuteRule",
- "NestedLoopJoinConverter"
- ),
-
- CORRELATED(
- "MergeJoinConverter",
- "JoinCommuteRule",
- "NestedLoopJoinConverter"
- );
+ try {
+ sql("CREATE TABLE t11(i1 INTEGER, i2 INTEGER)");
+
+ if (indexScan) {
+ sql("CREATE INDEX t11_idx ON t11(i1)");
+ }
+
+ sql("INSERT INTO t11 VALUES (1, null), (2, 2), (null, 3), (3,
null), (5, null)");
+
+ sql("CREATE TABLE t22(i3 INTEGER, i4 INTEGER)");
+
+ if (indexScan) {
+ sql("CREATE INDEX t22_idx ON t22(i3)");
+ }
+
+ sql("INSERT INTO t22 VALUES (1, 1), (2, 2), (null, 3), (4, null),
(5, null)");
+
+ String sql = "SELECT i1, i4 FROM t11 JOIN t22 ON i1 IS NOT
DISTINCT FROM i3";
+
+ assertQuery(sql, joinType, indexScan ?
"LogicalTableScanConverterRule" : null)
+ .matches(joinType == CORRELATED ?
QueryChecker.containsSubPlan("IgniteHashIndexSpool")
+ : QueryChecker.matches("(?i).*IS NOT DISTINCT.*"))
+ .matches(indexScan ?
QueryChecker.containsIndexScan("PUBLIC", "T11") :
+ QueryChecker.containsTableScan("PUBLIC", "T11"))
+ .returns(1, 1)
+ .returns(2, 2)
+ .returns(5, null)
+ .returns(null, 3)
+ .check();
+
+ sql = "SELECT i1, i4 FROM t11 JOIN t22 ON i1 IS NOT DISTINCT FROM
i3 AND i2 = i4";
+
+ assertQuery(sql, joinType, indexScan ?
"LogicalTableScanConverterRule" : null)
+ .matches(joinType == CORRELATED ?
QueryChecker.containsSubPlan("IgniteHashIndexSpool")
+ : QueryChecker.matches("(?i).*IS NOT DISTINCT.*"))
+ .matches(indexScan ?
QueryChecker.containsIndexScan("PUBLIC", "T11") :
+ QueryChecker.containsTableScan("PUBLIC", "T11"))
+ .returns(2, 2)
+ .returns(null, 3)
+ .check();
+ } finally {
+ sql("DROP TABLE IF EXISTS t11");
+ sql("DROP TABLE IF EXISTS t22");
+ }
+ }
- private final String[] disabledRules;
+ private static Stream<Arguments> joinTypes() {
+ Stream<Arguments> types = Arrays.stream(JoinType.values())
+ .flatMap(v -> Stream.of(Arguments.of(v, false),
Arguments.of(v, true)));
- JoinType(String... disabledRules) {
- this.disabledRules = disabledRules;
- }
+ return types;
}
}
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSecondaryIndexTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSecondaryIndexTest.java
index 12b8f071dd..8b2b3e777e 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSecondaryIndexTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSecondaryIndexTest.java
@@ -27,6 +27,7 @@ import static org.hamcrest.Matchers.not;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
@@ -770,6 +771,7 @@ public class ItSecondaryIndexTest extends
AbstractBasicIntegrationTest {
}
@Test
+ @Disabled("https://issues.apache.org/jira/browse/IGNITE-18468")
public void testNullCondition1() {
assertQuery("SELECT * FROM T1 WHERE val is null")
.matches(containsIndexScan("PUBLIC", "T1", "T1_IDX"))
@@ -781,6 +783,7 @@ public class ItSecondaryIndexTest extends
AbstractBasicIntegrationTest {
}
@Test
+ @Disabled("https://issues.apache.org/jira/browse/IGNITE-18468")
public void testNullCondition2() {
assertQuery("SELECT * FROM T1 WHERE (val <= 5) or (val is null)")
.matches(containsIndexScan("PUBLIC", "T1", "T1_IDX"))
@@ -795,6 +798,7 @@ public class ItSecondaryIndexTest extends
AbstractBasicIntegrationTest {
}
@Test
+ @Disabled("https://issues.apache.org/jira/browse/IGNITE-18468")
public void testNullCondition3() {
assertQuery("SELECT * FROM T1 WHERE (val >= 5) or (val is null)")
.matches(containsIndexScan("PUBLIC", "T1", "T1_IDX"))
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSetOpTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSetOpTest.java
index 84e9d346be..8613fef5f7 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSetOpTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSetOpTest.java
@@ -105,7 +105,7 @@ public class ItSetOpTest extends
AbstractBasicIntegrationTest {
}
@Test
- @Disabled
+ @Disabled("https://issues.apache.org/jira/browse/IGNITE-18469")
public void testSetOpBigBatch() {
sql("CREATE TABLE big_table1(key INT PRIMARY KEY, val INT)");
sql("CREATE TABLE big_table2(key INT PRIMARY KEY, val INT)");
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlOperatorsTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlOperatorsTest.java
index f65441c6a7..73f2f51c4c 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlOperatorsTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlOperatorsTest.java
@@ -108,6 +108,8 @@ public class ItSqlOperatorsTest extends
AbstractBasicIntegrationTest {
assertExpression("1=1 IS NOT TRUE").returns(false).check();
assertExpression("1=1 IS FALSE").returns(false).check();
assertExpression("1=1 IS NOT FALSE").returns(true).check();
+ assertExpression("NULL IS DISTINCT FROM NULL").returns(false).check();
+ assertExpression("NULL IS NOT DISTINCT FROM
NULL").returns(true).check();
}
@Test
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sqllogic/ItSqlLogicTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sqllogic/ItSqlLogicTest.java
index 3fb955e8c0..9979ac72a0 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sqllogic/ItSqlLogicTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sqllogic/ItSqlLogicTest.java
@@ -41,9 +41,7 @@ import org.apache.ignite.Ignite;
import org.apache.ignite.IgnitionManager;
import org.apache.ignite.internal.logger.IgniteLogger;
import org.apache.ignite.internal.logger.Loggers;
-import org.apache.ignite.internal.sql.engine.util.Commons;
import org.apache.ignite.internal.sqllogic.SqlLogicTestEnvironment.RestartMode;
-import org.apache.ignite.internal.testframework.IgniteTestUtils;
import org.apache.ignite.internal.testframework.SystemPropertiesExtension;
import org.apache.ignite.internal.testframework.WithSystemProperty;
import org.apache.ignite.internal.testframework.WorkDirectory;
@@ -161,16 +159,12 @@ public class ItSqlLogicTest {
static void init() {
config();
- IgniteTestUtils.setFieldValue(Commons.class, "implicitPkEnabled",
null);
-
startNodes();
}
@AfterAll
static void shutdown() throws Exception {
stopNodes();
-
- IgniteTestUtils.setFieldValue(Commons.class, "implicitPkEnabled",
null);
}
@TestFactory
diff --git
a/modules/runner/src/integrationTest/sql/join/test_not_distinct_from.test
b/modules/runner/src/integrationTest/sql/join/test_not_distinct_from.test
index a5662eccef..37fbff5d2c 100644
--- a/modules/runner/src/integrationTest/sql/join/test_not_distinct_from.test
+++ b/modules/runner/src/integrationTest/sql/join/test_not_distinct_from.test
@@ -189,3 +189,33 @@ query T
SELECT INTERVAL '30' DAY is not distinct from NULL
----
false
+
+# Use on a bigger table (~10K elements)
+statement ok
+CREATE table big(a int);
+
+statement ok
+insert into big select x from table(system_range(1, 9999));
+
+statement ok
+insert into big values (NULL)
+
+query I
+select count(*) from big inner join tbl_2 on (a IS NOT DISTINCT FROM tbl_2.b)
+----
+3
+
+# Use with filter (e.g. SELECT x IS NOT DISTINCT FROM y FROM tbl WHERE z%2=0,
where the expression filters like 50% of the rows)
+query I
+select count(*) from big inner join tbl_2 on (a IS NOT DISTINCT FROM tbl_2.b)
where a >1 and a < 5000
+----
+1
+
+# Use in subqueries with correlated expressions
+query II
+SELECT a, (select count(*) from tbl_1 where tbl_1.a is distinct from t.b) from
tbl_1 as t ORDER BY a NULLS LAST
+----
+1 2
+2 3
+NULL 2
+
diff --git
a/modules/runner/src/integrationTest/sql/join/test_not_distinct_from.test_ignore
b/modules/runner/src/integrationTest/sql/join/test_not_distinct_from.test_ignore
index e972585f63..72d8ba926e 100644
---
a/modules/runner/src/integrationTest/sql/join/test_not_distinct_from.test_ignore
+++
b/modules/runner/src/integrationTest/sql/join/test_not_distinct_from.test_ignore
@@ -1,7 +1,7 @@
# name: test/sql/join/test_not_distinct_from.test
# description: Test join on is not distinct from query
# group: [join]
-# Ignore https://issues.apache.org/jira/browse/IGNITE-15574
+# Ignore https://issues.apache.org/jira/browse/IGNITE-18444
statement ok
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
index ec2d373c02..8d1bf48059 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
@@ -189,13 +189,6 @@ public class ExecutionContext<RowT> extends
AbstractQueryContext implements Data
return fragmentDesc.mapping().findGroup(sourceId);
}
- /**
- * Get keep binary flag.
- */
- public boolean keepBinary() {
- return true; // TODO
- }
-
/**
* Get handler to access row fields.
*/
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java
index 12298c3425..18cdd49fe7 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/LogicalRelImplementor.java
@@ -30,6 +30,7 @@ import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
+import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Intersect;
@@ -37,8 +38,11 @@ import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.core.Minus;
import org.apache.calcite.rel.core.Spool;
import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.ignite.internal.sql.engine.exec.RowHandler.RowFactory;
import org.apache.ignite.internal.sql.engine.exec.exp.ExpressionFactory;
@@ -269,9 +273,36 @@ public class LogicalRelImplementor<RowT> implements
IgniteRelVisitor<Node<RowT>>
int pairsCnt = rel.analyzeCondition().pairs().size();
+ ImmutableBitSet leftKeys = rel.analyzeCondition().leftSet();
+
+ List<RexNode> conjunctions =
RelOptUtil.conjunctions(rel.getCondition());
+
+ ImmutableBitSet.Builder nullCompAsEqualBuilder =
ImmutableBitSet.builder();
+
+ ImmutableBitSet nullCompAsEqual;
+ RexShuttle shuttle = new RexShuttle() {
+ @Override
+ public RexNode visitInputRef(RexInputRef ref) {
+ int idx = ref.getIndex();
+ if (leftKeys.get(idx)) {
+ nullCompAsEqualBuilder.set(idx);
+ }
+ return ref;
+ }
+ };
+
+ for (RexNode expr : conjunctions) {
+ if (expr.getKind() == SqlKind.IS_NOT_DISTINCT_FROM) {
+ shuttle.apply(expr);
+ }
+ }
+
+ nullCompAsEqual = nullCompAsEqualBuilder.build();
+
Comparator<RowT> comp = expressionFactory.comparator(
rel.leftCollation().getFieldCollations().subList(0, pairsCnt),
- rel.rightCollation().getFieldCollations().subList(0, pairsCnt)
+ rel.rightCollation().getFieldCollations().subList(0, pairsCnt),
+ nullCompAsEqual
);
Node<RowT> node = MergeJoinNode.create(ctx, leftType, rightType,
joinType, comp);
@@ -481,7 +512,8 @@ public class LogicalRelImplementor<RowT> implements
IgniteRelVisitor<Node<RowT>>
ctx,
ImmutableBitSet.of(rel.keys()),
filter,
- searchRow
+ searchRow,
+ rel.allowNulls()
);
Node<RowT> input = visit(rel.getInput());
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/RuntimeHashIndex.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/RuntimeHashIndex.java
index 15c6147489..cc457120fe 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/RuntimeHashIndex.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/RuntimeHashIndex.java
@@ -50,19 +50,24 @@ public class RuntimeHashIndex<RowT> implements
RuntimeIndex<RowT> {
/** Rows. */
private HashMap<GroupKey, List<RowT>> rows;
+ /** Allow NULL values. */
+ private final boolean allowNulls;
+
/**
* Constructor.
* TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
*/
public RuntimeHashIndex(
ExecutionContext<RowT> ectx,
- ImmutableBitSet keys
+ ImmutableBitSet keys,
+ boolean allowNulls
) {
this.ectx = ectx;
assert !nullOrEmpty(keys);
this.keys = keys;
+ this.allowNulls = allowNulls;
rows = new HashMap<>();
}
@@ -95,7 +100,7 @@ public class RuntimeHashIndex<RowT> implements
RuntimeIndex<RowT> {
for (Integer field : keys) {
Object fieldVal = ectx.rowHandler().get(field, r);
- if (fieldVal == null) {
+ if (fieldVal == null && !allowNulls) {
return NULL_KEY;
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactory.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactory.java
index 0940ebe5ca..45454f35b4 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactory.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactory.java
@@ -29,6 +29,7 @@ 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.sql.engine.exec.exp.agg.AccumulatorWrapper;
import org.apache.ignite.internal.sql.engine.exec.exp.agg.AggregateType;
import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds;
@@ -59,9 +60,10 @@ public interface ExpressionFactory<RowT> {
*
* @param left Collations of left row.
* @param right Collations of right row.
+ * @param equalNulls Bitset with null comparison strategy, use in case of
NOT DISTINCT FROM syntax.
* @return Rows comparator.
*/
- Comparator<RowT> comparator(List<RelFieldCollation> left,
List<RelFieldCollation> right);
+ Comparator<RowT> comparator(List<RelFieldCollation> left,
List<RelFieldCollation> right, ImmutableBitSet equalNulls);
/**
* Creates a Filter predicate.
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactoryImpl.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactoryImpl.java
index 2cc3a39d43..a9fa55dd62 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactoryImpl.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactoryImpl.java
@@ -63,6 +63,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.sql.engine.exec.ExecutionContext;
import org.apache.ignite.internal.sql.engine.exec.RowHandler;
import org.apache.ignite.internal.sql.engine.exec.RowHandler.RowFactory;
@@ -139,42 +140,39 @@ public class ExpressionFactoryImpl<RowT> implements
ExpressionFactory<RowT> {
return null;
}
- return new Comparator<RowT>() {
- @Override
- public int compare(RowT o1, RowT o2) {
- RowHandler<RowT> hnd = ctx.rowHandler();
- Object unspecifiedVal =
RexImpTable.UNSPECIFIED_VALUE_PLACEHOLDER;
+ return (o1, o2) -> {
+ RowHandler<RowT> hnd = ctx.rowHandler();
+ Object unspecifiedVal = RexImpTable.UNSPECIFIED_VALUE_PLACEHOLDER;
- for (RelFieldCollation field : collation.getFieldCollations())
{
- int fieldIdx = field.getFieldIndex();
- int nullComparison = field.nullDirection.nullComparison;
+ for (RelFieldCollation field : collation.getFieldCollations()) {
+ int fieldIdx = field.getFieldIndex();
+ int nullComparison = field.nullDirection.nullComparison;
- Object c1 = hnd.get(fieldIdx, o1);
- Object c2 = hnd.get(fieldIdx, o2);
+ Object c1 = hnd.get(fieldIdx, o1);
+ Object c2 = hnd.get(fieldIdx, o2);
- // If filter for some field is unspecified, assume
equality for this field and all subsequent fields.
- if (c1 == unspecifiedVal || c2 == unspecifiedVal) {
- return 0;
- }
+ // If filter for some field is unspecified, assume equality
for this field and all subsequent fields.
+ if (c1 == unspecifiedVal || c2 == unspecifiedVal) {
+ return 0;
+ }
- int res = (field.direction ==
RelFieldCollation.Direction.ASCENDING)
- ?
- ExpressionFactoryImpl.compare(c1, c2,
nullComparison) :
- ExpressionFactoryImpl.compare(c2, c1,
-nullComparison);
+ int res = (field.direction ==
RelFieldCollation.Direction.ASCENDING)
+ ?
+ compare(c1, c2, nullComparison) :
+ compare(c2, c1, -nullComparison);
- if (res != 0) {
- return res;
- }
+ if (res != 0) {
+ return res;
}
-
- return 0;
}
+
+ return 0;
};
}
/** {@inheritDoc} */
@Override
- public Comparator<RowT> comparator(List<RelFieldCollation> left,
List<RelFieldCollation> right) {
+ public Comparator<RowT> comparator(List<RelFieldCollation> left,
List<RelFieldCollation> right, ImmutableBitSet equalNulls) {
if (nullOrEmpty(left) || nullOrEmpty(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"));
@@ -191,42 +189,39 @@ public class ExpressionFactoryImpl<RowT> implements
ExpressionFactory<RowT> {
}
}
- return new Comparator<RowT>() {
- @Override
- public int compare(RowT o1, RowT o2) {
- boolean hasNulls = false;
- RowHandler<RowT> hnd = ctx.rowHandler();
+ return (o1, o2) -> {
+ boolean hasNulls = false;
+ RowHandler<RowT> hnd = ctx.rowHandler();
- for (int i = 0; i < left.size(); i++) {
- RelFieldCollation leftField = left.get(i);
- RelFieldCollation rightField = right.get(i);
+ for (int i = 0; i < left.size(); i++) {
+ RelFieldCollation leftField = left.get(i);
+ RelFieldCollation rightField = right.get(i);
- int leftIdx = leftField.getFieldIndex();
- int rightIdx = rightField.getFieldIndex();
+ int leftIdx = leftField.getFieldIndex();
+ int rightIdx = rightField.getFieldIndex();
- Object c1 = hnd.get(leftIdx, o1);
- Object c2 = hnd.get(rightIdx, o2);
+ Object c1 = hnd.get(leftIdx, o1);
+ Object c2 = hnd.get(rightIdx, o2);
- if (c1 == null && c2 == null) {
- hasNulls = true;
- continue;
- }
+ if (!equalNulls.get(leftIdx) && c1 == null && c2 == null) {
+ hasNulls = true;
+ continue;
+ }
- int nullComparison =
leftField.nullDirection.nullComparison;
+ int nullComparison = leftField.nullDirection.nullComparison;
- int res = leftField.direction ==
RelFieldCollation.Direction.ASCENDING
- ?
- ExpressionFactoryImpl.compare(c1, c2,
nullComparison) :
- ExpressionFactoryImpl.compare(c2, c1,
-nullComparison);
+ int res = leftField.direction ==
RelFieldCollation.Direction.ASCENDING
+ ? compare(c1, c2, nullComparison)
+ : compare(c2, c1, -nullComparison);
- if (res != 0) {
- return res;
- }
+ if (res != 0) {
+ return res;
}
-
- // If compared rows contain NULLs, they shouldn't be treated
as equals, since NULL <> NULL in SQL.
- return hasNulls ? 1 : 0;
}
+
+ // If compared rows contain NULLs, they shouldn't be treated as
equals, since NULL <> NULL in SQL.
+ // Expect for cases with IS NOT DISTINCT
+ return hasNulls ? 1 : 0;
};
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RangeCondition.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RangeCondition.java
index 91c9cf5b1a..11c30292bf 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RangeCondition.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RangeCondition.java
@@ -24,14 +24,14 @@ package org.apache.ignite.internal.sql.engine.exec.exp;
*/
public interface RangeCondition<RowT> {
/** Lower search row. */
- public RowT lower();
+ RowT lower();
/** Upper search row. */
- public RowT upper();
+ RowT upper();
/** Inlusive search by lower row. */
- public boolean lowerInclude();
+ boolean lowerInclude();
/** Inlusive search by upper row. */
- public boolean upperInclude();
+ boolean upperInclude();
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
index bac45c0a5e..3a21b89194 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
@@ -110,6 +110,7 @@ import static
org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_JSON_ARRAY;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_JSON_OBJECT;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_JSON_SCALAR;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_JSON_VALUE;
+import static
org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_DISTINCT_FROM;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_EMPTY;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_FALSE;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_JSON_ARRAY;
@@ -498,6 +499,10 @@ public class RexImpTable {
map.put(LOCALTIMESTAMP, systemFunctionImplementor);
map.put(TYPEOF, systemFunctionImplementor);
+
+ // Operator IS_NOT_DISTINCT_FROM is removed by RexSimplify, but still
possible in join conditions, so
+ // implementation required.
+ defineMethod(IS_NOT_DISTINCT_FROM,
IgniteMethod.IS_NOT_DISTINCT_FROM.method(), NullPolicy.NONE);
}
private void defineMethod(SqlOperator operator, String functionName,
NullPolicy nullPolicy) {
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexSpoolNode.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexSpoolNode.java
index 26957fcfea..e3450824f7 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexSpoolNode.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/IndexSpoolNode.java
@@ -197,9 +197,10 @@ public class IndexSpoolNode<RowT> extends
AbstractNode<RowT> implements SingleNo
ExecutionContext<RowT> ctx,
ImmutableBitSet keys,
@Nullable Predicate<RowT> filter,
- Supplier<RowT> searchRow
+ Supplier<RowT> searchRow,
+ boolean allowNulls
) {
- RuntimeHashIndex<RowT> idx = new RuntimeHashIndex<>(ctx, keys);
+ RuntimeHashIndex<RowT> idx = new RuntimeHashIndex<>(ctx, keys,
allowNulls);
ScanNode<RowT> scan = new ScanNode<>(
ctx,
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteHashIndexSpool.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteHashIndexSpool.java
index 926b3e8efa..945194154c 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteHashIndexSpool.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteHashIndexSpool.java
@@ -48,6 +48,9 @@ public class IgniteHashIndexSpool extends AbstractIgniteSpool
implements Interna
/** Condition (used to calculate selectivity). */
private final RexNode cond;
+ /** Allow NULL values. */
+ private final boolean allowNulls;
+
/**
* Constructor.
* TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
@@ -57,7 +60,8 @@ public class IgniteHashIndexSpool extends AbstractIgniteSpool
implements Interna
RelTraitSet traits,
RelNode input,
List<RexNode> searchRow,
- RexNode cond
+ RexNode cond,
+ boolean allowNulls
) {
super(cluster, traits, Type.LAZY, input);
@@ -65,6 +69,7 @@ public class IgniteHashIndexSpool extends AbstractIgniteSpool
implements Interna
this.searchRow = searchRow;
this.cond = cond;
+ this.allowNulls = allowNulls;
keys = ImmutableBitSet.of(RexUtils.notNullKeys(searchRow));
}
@@ -79,7 +84,8 @@ public class IgniteHashIndexSpool extends AbstractIgniteSpool
implements Interna
input.getTraitSet().replace(IgniteConvention.INSTANCE),
input.getInputs().get(0),
input.getExpressionList("searchRow"),
- input.getExpression("condition")
+ input.getExpression("condition"),
+ input.getBoolean("allowNulls", false)
);
}
@@ -91,13 +97,13 @@ public class IgniteHashIndexSpool extends
AbstractIgniteSpool implements Interna
@Override
public IgniteRel clone(RelOptCluster cluster, List<IgniteRel> inputs) {
- return new IgniteHashIndexSpool(cluster, getTraitSet(), inputs.get(0),
searchRow, cond);
+ return new IgniteHashIndexSpool(cluster, getTraitSet(), inputs.get(0),
searchRow, cond, allowNulls);
}
/** {@inheritDoc} */
@Override
protected Spool copy(RelTraitSet traitSet, RelNode input, Type readType,
Type writeType) {
- return new IgniteHashIndexSpool(getCluster(), traitSet, input,
searchRow, cond);
+ return new IgniteHashIndexSpool(getCluster(), traitSet, input,
searchRow, cond, allowNulls);
}
/** {@inheritDoc} */
@@ -112,7 +118,8 @@ public class IgniteHashIndexSpool extends
AbstractIgniteSpool implements Interna
return writer
.item("searchRow", searchRow)
- .item("condition", cond);
+ .item("condition", cond)
+ .item("allowNulls", allowNulls);
}
/** {@inheritDoc} */
@@ -157,4 +164,9 @@ public class IgniteHashIndexSpool extends
AbstractIgniteSpool implements Interna
public RexNode condition() {
return cond;
}
+
+ /** Allow {@code null} comparison as equal, use in case of NOT DISTINCT
FROM syntax. */
+ public boolean allowNulls() {
+ return allowNulls;
+ }
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteMergeJoin.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteMergeJoin.java
index 0c1e68033c..7112ea67a9 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteMergeJoin.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteMergeJoin.java
@@ -82,8 +82,9 @@ public class IgniteMergeJoin extends AbstractIgniteJoin {
}
/**
- * Constructor.
- * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
+ * Constructor used for deserialization.
+ *
+ * @param input Serialized representation.
*/
public IgniteMergeJoin(RelInput input) {
this(
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/logical/IgniteLogicalIndexScan.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/logical/IgniteLogicalIndexScan.java
index 6f9a93b7d8..e9694c488f 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/logical/IgniteLogicalIndexScan.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/logical/IgniteLogicalIndexScan.java
@@ -131,7 +131,7 @@ public class IgniteLogicalIndexScan extends
AbstractIndexScan {
return null;
}
- return RexUtils.buildSortedIndexConditions(
+ return RexUtils.buildSortedSearchBounds(
cluster,
collation,
cond,
@@ -147,7 +147,7 @@ public class IgniteLogicalIndexScan extends
AbstractIndexScan {
RexNode cond,
@Nullable ImmutableBitSet requiredColumns
) {
- return RexUtils.buildHashIndexConditions(
+ return RexUtils.buildHashSearchBounds(
cluster,
collation,
cond,
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/FilterSpoolMergeToHashIndexSpoolRule.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/FilterSpoolMergeToHashIndexSpoolRule.java
index 0b6e051ae7..a39a27a96a 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/FilterSpoolMergeToHashIndexSpoolRule.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/FilterSpoolMergeToHashIndexSpoolRule.java
@@ -30,11 +30,15 @@ import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.Spool;
import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.ignite.internal.sql.engine.prepare.bounds.ExactBounds;
+import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.sql.engine.rel.IgniteFilter;
import org.apache.ignite.internal.sql.engine.rel.IgniteHashIndexSpool;
import org.apache.ignite.internal.sql.engine.rel.IgniteTableSpool;
import org.apache.ignite.internal.sql.engine.trait.CorrelationTrait;
import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
+import org.apache.ignite.internal.sql.engine.util.Commons;
import org.apache.ignite.internal.sql.engine.util.RexUtils;
import org.immutables.value.Value;
@@ -67,22 +71,30 @@ public class FilterSpoolMergeToHashIndexSpoolRule extends
RelRule<FilterSpoolMer
final RelNode input = spool.getInput();
- List<RexNode> searchRow = RexUtils.buildHashSearchRow(
+ List<SearchBounds> searchBounds = RexUtils.buildHashSearchBounds(
cluster,
filter.getCondition(),
- spool.getRowType()
+ spool.getRowType(),
+ null
);
- if (nullOrEmpty(searchRow)) {
+ if (nullOrEmpty(searchBounds)) {
return;
}
+ List<RexNode> searchRow = Commons.transform(searchBounds, b -> {
+ assert b == null || b instanceof ExactBounds : b;
+
+ return b == null ? null : ((ExactBounds) b).bound();
+ });
+
RelNode res = new IgniteHashIndexSpool(
cluster,
trait.replace(RelCollations.EMPTY),
input,
searchRow,
- filter.getCondition()
+ filter.getCondition(),
+ searchBounds.stream().anyMatch(b -> b != null &&
b.condition().getKind() == SqlKind.IS_NOT_DISTINCT_FROM)
);
call.transformTo(res);
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/FilterSpoolMergeToSortedIndexSpoolRule.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/FilterSpoolMergeToSortedIndexSpoolRule.java
index f0048d28a6..e117d97e5b 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/FilterSpoolMergeToSortedIndexSpoolRule.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/FilterSpoolMergeToSortedIndexSpoolRule.java
@@ -76,7 +76,7 @@ public class FilterSpoolMergeToSortedIndexSpoolRule extends
RelRule<FilterSpoolM
RelCollation inCollation = TraitUtils.collation(input);
- List<SearchBounds> searchBounds = RexUtils.buildSortedIndexConditions(
+ List<SearchBounds> searchBounds = RexUtils.buildSortedSearchBounds(
cluster,
inCollation,
filter.getCondition(),
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/MergeJoinConverterRule.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/MergeJoinConverterRule.java
index 58a2e7fd74..9453b716f8 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/MergeJoinConverterRule.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/MergeJoinConverterRule.java
@@ -70,6 +70,7 @@ public class MergeJoinConverterRule extends
AbstractIgniteConverterRule<LogicalJ
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(),
+ rel.getVariablesSet(), rel.getJoinType());
}
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
index 447c5a59bb..1d388ebe67 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
@@ -191,8 +191,6 @@ public final class Commons {
.traitDefs(DISTRIBUTED_TRAITS_SET)
.build();
- private static Boolean implicitPkEnabled;
-
private Commons() {
}
@@ -893,10 +891,6 @@ public final class Commons {
* @return A {@code true} if implicit pk mode is enabled, {@code false}
otherwise.
*/
public static boolean implicitPkEnabled() {
- if (implicitPkEnabled == null) {
- implicitPkEnabled =
IgniteSystemProperties.getBoolean("IMPLICIT_PK_ENABLED", false);
- }
-
- return implicitPkEnabled;
+ return IgniteSystemProperties.getBoolean("IMPLICIT_PK_ENABLED", false);
}
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
index c63290af52..55c5bfc9c2 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.sql.engine.util;
import java.lang.reflect.Method;
+import java.util.Objects;
import org.apache.calcite.DataContext;
import org.apache.calcite.avatica.util.ByteString;
import org.apache.calcite.linq4j.tree.Types;
@@ -77,7 +78,10 @@ public enum IgniteMethod {
STRING_TO_BYTESTRING(IgniteSqlFunctions.class, "toByteString",
String.class),
/** See {@link IgniteSqlFunctions#currentTime(DataContext)}. */
- CURRENT_TIME(IgniteSqlFunctions.class, "currentTime", DataContext.class);
+ CURRENT_TIME(IgniteSqlFunctions.class, "currentTime", DataContext.class),
+
+ /** See {@link Objects#equals(Object, Object)}. */
+ IS_NOT_DISTINCT_FROM(Objects.class, "equals", Object.class, Object.class);
private final Method method;
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/RexUtils.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/RexUtils.java
index 7db4e839e0..f2bb8c5c43 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/RexUtils.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/RexUtils.java
@@ -19,9 +19,11 @@ package org.apache.ignite.internal.sql.engine.util;
import static org.apache.calcite.rex.RexUtil.removeCast;
import static org.apache.calcite.rex.RexUtil.sargRef;
+import static org.apache.calcite.sql.SqlKind.BINARY_COMPARISON;
import static org.apache.calcite.sql.SqlKind.EQUALS;
import static org.apache.calcite.sql.SqlKind.GREATER_THAN;
import static org.apache.calcite.sql.SqlKind.GREATER_THAN_OR_EQUAL;
+import static org.apache.calcite.sql.SqlKind.IS_NOT_DISTINCT_FROM;
import static org.apache.calcite.sql.SqlKind.IS_NOT_NULL;
import static org.apache.calcite.sql.SqlKind.IS_NULL;
import static org.apache.calcite.sql.SqlKind.LESS_THAN;
@@ -30,6 +32,7 @@ import static org.apache.calcite.sql.SqlKind.SEARCH;
import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -97,6 +100,9 @@ public class RexUtils {
/** Maximum amount of search bounds tuples per scan. */
public static final int MAX_SEARCH_BOUNDS_COMPLEXITY = 100;
+ /** Hash index permitted search operations. */
+ private static final EnumSet<SqlKind> HASH_SEARCH_OPS = EnumSet.of(EQUALS,
IS_NOT_DISTINCT_FROM);
+
/**
* MakeCast.
* TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
@@ -195,10 +201,6 @@ public class RexUtils {
return true;
}
- /** Binary comparison operations. */
- private static final Set<SqlKind> BINARY_COMPARISON =
- EnumSet.of(EQUALS, LESS_THAN, GREATER_THAN, GREATER_THAN_OR_EQUAL,
LESS_THAN_OR_EQUAL);
-
/** Supported index operations. */
private static final Set<SqlKind> TREE_INDEX_COMPARISON =
EnumSet.of(
@@ -206,13 +208,14 @@ public class RexUtils {
IS_NULL,
IS_NOT_NULL,
EQUALS,
+ IS_NOT_DISTINCT_FROM,
LESS_THAN, GREATER_THAN,
GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL);
/**
* Builds sorted index search bounds.
*/
- public static @Nullable List<SearchBounds> buildSortedIndexConditions(
+ public static @Nullable List<SearchBounds> buildSortedSearchBounds(
RelOptCluster cluster,
RelCollation collation,
@Nullable RexNode condition,
@@ -304,7 +307,7 @@ public class RexUtils {
/**
* Builds hash index search bounds.
*/
- public static List<SearchBounds> buildHashIndexConditions(
+ public static List<SearchBounds> buildHashSearchBounds(
RelOptCluster cluster,
RelCollation collation,
RexNode condition,
@@ -361,12 +364,13 @@ public class RexUtils {
}
/**
- * Builds index conditions.
+ * Builds hash index search bounds.
*/
- public static List<RexNode> buildHashSearchRow(
+ public static List<SearchBounds> buildHashSearchBounds(
RelOptCluster cluster,
RexNode condition,
- RelDataType rowType
+ RelDataType rowType,
+ @Nullable ImmutableBitSet requiredColumns
) {
condition = RexUtil.toCnf(builder(cluster), condition);
@@ -376,31 +380,46 @@ public class RexUtils {
return null;
}
- List<RexNode> searchPreds = null;
+ List<SearchBounds> bounds = null;
+
+ List<RelDataType> types = RelOptUtil.getFieldTypeList(rowType);
+
+ Mappings.TargetMapping mapping = null;
+
+ if (requiredColumns != null) {
+ mapping = Commons.inverseTrimmingMapping(types.size(),
requiredColumns);
+ }
+
+ for (Entry<List<RexCall>> fld :
fieldsToPredicates.int2ObjectEntrySet()) {
+ List<RexCall> collFldPreds = fld.getValue();
- for (List<RexCall> collFldPreds : fieldsToPredicates.values()) {
if (nullOrEmpty(collFldPreds)) {
break;
}
for (RexCall pred : collFldPreds) {
- if (pred.getOperator().kind != SqlKind.EQUALS) {
+ if (!pred.isA(HASH_SEARCH_OPS)) {
return null;
}
- if (searchPreds == null) {
- searchPreds = new ArrayList<>();
+ if (bounds == null) {
+ bounds = Arrays.asList(new SearchBounds[types.size()]);
}
- searchPreds.add(pred);
- }
- }
+ int fldIdx;
- if (searchPreds == null) {
- return null;
+ if (mapping != null) {
+ fldIdx = mapping.getSourceOpt(fld.getIntKey());
+ } else {
+ fldIdx = fld.getIntKey();
+ }
+
+ bounds.set(fldIdx, new ExactBounds(pred,
+ makeCast(builder(cluster),
removeCast(pred.operands.get(1)), types.get(fldIdx))));
+ }
}
- return asBound(cluster, searchPreds, rowType, null);
+ return bounds;
}
/** Create index search bound by conditions of the field. */
@@ -437,6 +456,8 @@ public class RexUtils {
if (op.kind == EQUALS) {
return new ExactBounds(pred, val);
+ } else if (op.kind == IS_NOT_DISTINCT_FROM) {
+ return new ExactBounds(pred,
builder.makeCall(SqlStdOperatorTable.COALESCE, val, nullVal));
} else if (op.kind == IS_NULL) {
return new ExactBounds(pred, nullVal);
} else if (op.kind == SEARCH) {
@@ -557,7 +578,7 @@ public class RexUtils {
// Let RexLocalRef be on the left side.
if (refOnTheRight(predCall)) {
- predCall = (RexCall) RexUtil.invert(builder(cluster),
predCall);
+ predCall = (RexCall) invert(builder(cluster), predCall);
}
} else {
ref = (RexSlot) extractRefFromOperand(predCall, cluster, 0);
@@ -575,6 +596,15 @@ public class RexUtils {
return res;
}
+ /** Extended version of {@link RexUtil#invert(RexBuilder, RexCall)} with
additional operators support. */
+ private static RexNode invert(RexBuilder rexBuilder, RexCall call) {
+ if (call.getOperator() == SqlStdOperatorTable.IS_NOT_DISTINCT_FROM) {
+ return rexBuilder.makeCall(call.getOperator(),
call.getOperands().get(1), call.getOperands().get(0));
+ } else {
+ return RexUtil.invert(rexBuilder, call);
+ }
+ }
+
private static RexNode extractRefFromBinary(RexCall call, RelOptCluster
cluster) {
assert isBinaryComparison(call);
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/HashIndexSpoolExecutionTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/HashIndexSpoolExecutionTest.java
index 56ad62f0c1..15b677b32e 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/HashIndexSpoolExecutionTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/HashIndexSpoolExecutionTest.java
@@ -106,7 +106,8 @@ public class HashIndexSpoolExecutionTest extends
AbstractExecutionTest {
ctx,
ImmutableBitSet.of(0),
testFilter,
- () -> searchRow
+ () -> searchRow,
+ false
);
spool.register(singletonList(scan));
@@ -147,7 +148,8 @@ public class HashIndexSpoolExecutionTest extends
AbstractExecutionTest {
ctx,
ImmutableBitSet.of(0, 1),
null,
- () -> searchRow
+ () -> searchRow,
+ false
);
spool.register(scan);
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/MergeJoinExecutionTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/MergeJoinExecutionTest.java
index 9628c92d73..ee4680ed19 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/MergeJoinExecutionTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/MergeJoinExecutionTest.java
@@ -23,96 +23,117 @@ import static
org.apache.calcite.rel.core.JoinRelType.INNER;
import static org.apache.calcite.rel.core.JoinRelType.LEFT;
import static org.apache.calcite.rel.core.JoinRelType.RIGHT;
import static org.apache.calcite.rel.core.JoinRelType.SEMI;
+import static
org.apache.ignite.internal.sql.engine.util.BaseQueryContext.CALCITE_CONNECTION_CONFIG;
import static org.apache.ignite.internal.util.ArrayUtils.asList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.RelFieldCollation.Direction;
+import org.apache.calcite.rel.RelFieldCollation.NullDirection;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeSystem;
+import org.apache.calcite.sql.validate.SqlConformanceEnum;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.sql.engine.exec.ArrayRowHandler;
import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
+import org.apache.ignite.internal.sql.engine.exec.exp.ExpressionFactoryImpl;
+import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
+import org.apache.ignite.internal.sql.engine.type.IgniteTypeSystem;
+import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
import org.apache.ignite.internal.sql.engine.util.TypeUtils;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
/**
* MergeJoinExecutionTest;
* TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
*/
public class MergeJoinExecutionTest extends AbstractExecutionTest {
- @Test
- public void joinEmptyTables() {
- verifyJoin(EMPTY, EMPTY, INNER, EMPTY);
- verifyJoin(EMPTY, EMPTY, LEFT, EMPTY);
- verifyJoin(EMPTY, EMPTY, RIGHT, EMPTY);
- verifyJoin(EMPTY, EMPTY, FULL, EMPTY);
- verifyJoin(EMPTY, EMPTY, SEMI, EMPTY);
- verifyJoin(EMPTY, EMPTY, ANTI, EMPTY);
+ @ParameterizedTest(name = "treat nulls as equals: {0}")
+ @ValueSource(booleans = {true, false})
+ public void joinEmptyTables(boolean equalNulls) {
+ verifyJoin(EMPTY, EMPTY, INNER, EMPTY, equalNulls);
+ verifyJoin(EMPTY, EMPTY, LEFT, EMPTY, equalNulls);
+ verifyJoin(EMPTY, EMPTY, RIGHT, EMPTY, equalNulls);
+ verifyJoin(EMPTY, EMPTY, FULL, EMPTY, equalNulls);
+ verifyJoin(EMPTY, EMPTY, SEMI, EMPTY, equalNulls);
+ verifyJoin(EMPTY, EMPTY, ANTI, EMPTY, equalNulls);
}
- @Test
- public void joinEmptyLeftTable() {
+ @ParameterizedTest(name = "treat nulls as equals: {0}")
+ @ValueSource(booleans = {true, false})
+ public void joinEmptyLeftTable(boolean equalNulls) {
Object[][] right = {
{1, "Core"},
{1, "OLD_Core"},
{2, "SQL"}
};
- verifyJoin(EMPTY, right, INNER, EMPTY);
- verifyJoin(EMPTY, right, LEFT, EMPTY);
+ verifyJoin(EMPTY, right, INNER, EMPTY, equalNulls);
+ verifyJoin(EMPTY, right, LEFT, EMPTY, equalNulls);
verifyJoin(EMPTY, right, RIGHT, new Object[][]{
{null, null, "Core"},
{null, null, "OLD_Core"},
{null, null, "SQL"}
- });
+ }, equalNulls);
verifyJoin(EMPTY, right, FULL, new Object[][]{
{null, null, "Core"},
{null, null, "OLD_Core"},
{null, null, "SQL"}
- });
- verifyJoin(EMPTY, right, SEMI, EMPTY);
- verifyJoin(EMPTY, right, ANTI, EMPTY);
+ }, equalNulls);
+ verifyJoin(EMPTY, right, SEMI, EMPTY, equalNulls);
+ verifyJoin(EMPTY, right, ANTI, EMPTY, equalNulls);
}
- @Test
- public void joinEmptyRightTable() {
+ @ParameterizedTest(name = "treat nulls as equals: {0}")
+ @ValueSource(booleans = {true, false})
+ public void joinEmptyRightTable(boolean equalNulls) {
Object[][] left = {
{1, "Roman", null},
{2, "Igor", 1},
{3, "Alexey", 2}
};
- verifyJoin(left, EMPTY, INNER, EMPTY);
+ verifyJoin(left, EMPTY, INNER, EMPTY, equalNulls);
verifyJoin(left, EMPTY, LEFT, new Object[][]{
{1, "Roman", null},
{2, "Igor", null},
{3, "Alexey", null}
- });
- verifyJoin(left, EMPTY, RIGHT, EMPTY);
+ }, equalNulls);
+ verifyJoin(left, EMPTY, RIGHT, EMPTY, equalNulls);
verifyJoin(left, EMPTY, FULL, new Object[][]{
{1, "Roman", null},
{2, "Igor", null},
{3, "Alexey", null}
- });
- verifyJoin(left, EMPTY, SEMI, EMPTY);
+ }, equalNulls);
+ verifyJoin(left, EMPTY, SEMI, EMPTY, equalNulls);
verifyJoin(left, EMPTY, ANTI, new Object[][]{
{1, "Roman"},
{2, "Igor"},
{3, "Alexey"}
- });
+ }, equalNulls);
}
- @Test
- public void joinOneToMany() {
+ @ParameterizedTest(name = "treat nulls as equals: {0}")
+ @ValueSource(booleans = {true, false})
+ public void joinOneToMany(boolean equalNulls) {
Object[][] left = {
{1, "Roman", null},
{2, "Igor", 1},
- {3, "Alexey", 2}
+ {3, "Alexey", 2},
+ {4, "Taras", 5}
};
Object[][] right = {
+ {null, "None"},
{1, "Core"},
{1, "OLD_Core"},
{1, "NEW_Core"},
@@ -120,45 +141,91 @@ public class MergeJoinExecutionTest extends
AbstractExecutionTest {
{3, "Arch"}
};
- verifyJoin(left, right, INNER, new Object[][] {
- {2, "Igor", "Core"},
- {2, "Igor", "OLD_Core"},
- {2, "Igor", "NEW_Core"},
- {3, "Alexey", "SQL"}
- });
- verifyJoin(left, right, LEFT, new Object[][] {
- {1, "Roman", null},
- {2, "Igor", "Core"},
- {2, "Igor", "OLD_Core"},
- {2, "Igor", "NEW_Core"},
- {3, "Alexey", "SQL"}
- });
- verifyJoin(left, right, RIGHT, new Object[][] {
- {2, "Igor", "Core"},
- {2, "Igor", "OLD_Core"},
- {2, "Igor", "NEW_Core"},
- {3, "Alexey", "SQL"},
- {null, null, "Arch"}
- });
- verifyJoin(left, right, FULL, new Object[][] {
- {1, "Roman", null},
- {2, "Igor", "Core"},
- {2, "Igor", "OLD_Core"},
- {2, "Igor", "NEW_Core"},
- {3, "Alexey", "SQL"},
- {null, null, "Arch"}
- });
- verifyJoin(left, right, SEMI, new Object[][] {
- {2, "Igor"},
- {3, "Alexey"}
- });
- verifyJoin(left, right, ANTI, new Object[][] {
- {1, "Roman"}
- });
+ verifyJoin(left, right, INNER, equalNulls
+ ? new Object[][] {
+ {1, "Roman", "None"},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {2, "Igor", "NEW_Core"},
+ {3, "Alexey", "SQL"}}
+ : new Object[][]{
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {2, "Igor", "NEW_Core"},
+ {3, "Alexey", "SQL"}},
+ equalNulls);
+ verifyJoin(left, right, LEFT, equalNulls
+ ? new Object[][] {
+ {1, "Roman", "None"},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {2, "Igor", "NEW_Core"},
+ {3, "Alexey", "SQL"},
+ {4, "Taras", null}}
+ : new Object[][] {
+ {1, "Roman", null},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {2, "Igor", "NEW_Core"},
+ {3, "Alexey", "SQL"},
+ {4, "Taras", null}},
+ equalNulls);
+ verifyJoin(left, right, RIGHT, equalNulls
+ ? new Object[][] {
+ {1, "Roman", "None"},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {2, "Igor", "NEW_Core"},
+ {3, "Alexey", "SQL"},
+ {null, null, "Arch"}}
+ : new Object[][] {
+ {null, null, "None"},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {2, "Igor", "NEW_Core"},
+ {3, "Alexey", "SQL"},
+ {null, null, "Arch"}},
+ equalNulls);
+ verifyJoin(left, right, FULL, equalNulls
+ ? new Object[][] {
+ {1, "Roman", "None"},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {2, "Igor", "NEW_Core"},
+ {3, "Alexey", "SQL"},
+ {null, null, "Arch"},
+ {4, "Taras", null}}
+ : new Object[][] {
+ {null, null, "None"},
+ {1, "Roman", null},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {2, "Igor", "NEW_Core"},
+ {3, "Alexey", "SQL"},
+ {null, null, "Arch"},
+ {4, "Taras", null}},
+ equalNulls);
+ verifyJoin(left, right, SEMI, equalNulls
+ ? new Object[][] {
+ {1, "Roman"},
+ {2, "Igor"},
+ {3, "Alexey"}}
+ : new Object[][]{
+ {2, "Igor"},
+ {3, "Alexey"}},
+ equalNulls);
+ verifyJoin(left, right, ANTI, equalNulls
+ ? new Object[][] {
+ {4, "Taras"}}
+ : new Object[][] {
+ {1, "Roman"},
+ {4, "Taras"}},
+ equalNulls);
}
- @Test
- public void joinOneToMany2() {
+ @ParameterizedTest(name = "treat nulls as equals: {0}")
+ @ValueSource(booleans = {true, false})
+ public void joinOneToMany2(boolean equalNulls) {
Object[][] left = {
{1, "Roman", null},
{2, "Igor", 1},
@@ -181,7 +248,7 @@ public class MergeJoinExecutionTest extends
AbstractExecutionTest {
{2, "Igor", "OLD_Core"},
{3, "Alexey", "SQL"},
{5, "Taras", "Arch"}
- });
+ }, equalNulls);
verifyJoin(left, right, LEFT, new Object[][]{
{1, "Roman", null},
{2, "Igor", "Core"},
@@ -190,14 +257,14 @@ public class MergeJoinExecutionTest extends
AbstractExecutionTest {
{4, "Ivan", null},
{5, "Taras", "Arch"},
{6, "Lisa", null}
- });
+ }, equalNulls);
verifyJoin(left, right, RIGHT, new Object[][]{
{2, "Igor", "Core"},
{2, "Igor", "OLD_Core"},
{3, "Alexey", "SQL"},
{null, null, "QA"},
{5, "Taras", "Arch"}
- });
+ }, equalNulls);
verifyJoin(left, right, FULL, new Object[][]{
{1, "Roman", null},
{2, "Igor", "Core"},
@@ -207,21 +274,22 @@ public class MergeJoinExecutionTest extends
AbstractExecutionTest {
{4, "Ivan", null},
{5, "Taras", "Arch"},
{6, "Lisa", null}
- });
+ }, equalNulls);
verifyJoin(left, right, SEMI, new Object[][]{
{2, "Igor"},
{3, "Alexey"},
{5, "Taras"}
- });
+ }, equalNulls);
verifyJoin(left, right, ANTI, new Object[][]{
{1, "Roman"},
{4, "Ivan"},
{6, "Lisa"}
- });
+ }, equalNulls);
}
- @Test
- public void joinManyToMany() {
+ @ParameterizedTest(name = "treat nulls as equals: {0}")
+ @ValueSource(booleans = {true, false})
+ public void joinManyToMany(boolean equalNulls) {
Object[][] left = {
{1, "Roman", null},
{2, "Igor", 1},
@@ -250,7 +318,7 @@ public class MergeJoinExecutionTest extends
AbstractExecutionTest {
{5, "Ivan", "OLD_QA"},
{6, "Andrey", "QA"},
{6, "Andrey", "OLD_QA"}
- });
+ }, equalNulls);
verifyJoin(left, right, LEFT, new Object[][]{
{1, "Roman", null},
{2, "Igor", "Core"},
@@ -262,7 +330,7 @@ public class MergeJoinExecutionTest extends
AbstractExecutionTest {
{5, "Ivan", "OLD_QA"},
{6, "Andrey", "QA"},
{6, "Andrey", "OLD_QA"}
- });
+ }, equalNulls);
verifyJoin(left, right, RIGHT, new Object[][]{
{2, "Igor", "Core"},
{2, "Igor", "OLD_Core"},
@@ -274,7 +342,7 @@ public class MergeJoinExecutionTest extends
AbstractExecutionTest {
{5, "Ivan", "OLD_QA"},
{6, "Andrey", "QA"},
{6, "Andrey", "OLD_QA"}
- });
+ }, equalNulls);
verifyJoin(left, right, FULL, new Object[][]{
{1, "Roman", null},
{2, "Igor", "Core"},
@@ -287,21 +355,22 @@ public class MergeJoinExecutionTest extends
AbstractExecutionTest {
{5, "Ivan", "OLD_QA"},
{6, "Andrey", "QA"},
{6, "Andrey", "OLD_QA"}
- });
+ }, equalNulls);
verifyJoin(left, right, SEMI, new Object[][]{
{2, "Igor"},
{3, "Taras"},
{4, "Alexey"},
{5, "Ivan"},
{6, "Andrey"},
- });
+ }, equalNulls);
verifyJoin(left, right, ANTI, new Object[][]{
{1, "Roman"}
- });
+ }, equalNulls);
}
- @Test
- public void joinOnNullField() {
+ @ParameterizedTest(name = "treat nulls as equals: {0}")
+ @ValueSource(booleans = {true, false})
+ public void joinOnNullField(boolean equalNulls) {
Object[][] left = {
{1, "Roman", null},
{2, "Igor", null},
@@ -316,43 +385,84 @@ public class MergeJoinExecutionTest extends
AbstractExecutionTest {
{2, "QA"}
};
- verifyJoin(left, right, INNER, new Object[][]{
- {3, "Alexey", "SQL"},
- {4, "Ivan", "QA"},
- });
-
- verifyJoin(left, right, LEFT, new Object[][]{
- {1, "Roman", null},
- {2, "Igor", null},
- {3, "Alexey", "SQL"},
- {4, "Ivan", "QA"},
- });
-
- verifyJoin(left, right, RIGHT, new Object[][]{
- {null, null, "Core"},
- {null, null, "OLD_Core"},
- {3, "Alexey", "SQL"},
- {4, "Ivan", "QA"},
- });
-
- verifyJoin(left, right, FULL, new Object[][]{
- {null, null, "Core"},
- {null, null, "OLD_Core"},
- {1, "Roman", null},
- {2, "Igor", null},
- {3, "Alexey", "SQL"},
- {4, "Ivan", "QA"},
- });
-
- verifyJoin(left, right, SEMI, new Object[][]{
- {3, "Alexey"},
- {4, "Ivan"},
- });
-
- verifyJoin(left, right, ANTI, new Object[][]{
- {1, "Roman"},
- {2, "Igor"},
- });
+ verifyJoin(left, right, INNER, equalNulls
+ ? new Object[][]{
+ {1, "Roman", "Core"},
+ {1, "Roman", "OLD_Core"},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {3, "Alexey", "SQL"},
+ {4, "Ivan", "QA"}}
+ : new Object[][]{
+ {3, "Alexey", "SQL"},
+ {4, "Ivan", "QA"}},
+ equalNulls);
+
+ verifyJoin(left, right, LEFT, equalNulls
+ ? new Object[][]{
+ {1, "Roman", "Core"},
+ {1, "Roman", "OLD_Core"},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {3, "Alexey", "SQL"},
+ {4, "Ivan", "QA"}}
+ : new Object[][]{
+ {1, "Roman", null},
+ {2, "Igor", null},
+ {3, "Alexey", "SQL"},
+ {4, "Ivan", "QA"}},
+ equalNulls);
+
+ verifyJoin(left, right, RIGHT, equalNulls
+ ? new Object[][]{
+ {1, "Roman", "Core"},
+ {1, "Roman", "OLD_Core"},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {3, "Alexey", "SQL"},
+ {4, "Ivan", "QA"}}
+ : new Object[][]{
+ {null, null, "Core"},
+ {null, null, "OLD_Core"},
+ {3, "Alexey", "SQL"},
+ {4, "Ivan", "QA"}},
+ equalNulls);
+
+ verifyJoin(left, right, FULL, equalNulls
+ ? new Object[][]{
+ {1, "Roman", "Core"},
+ {1, "Roman", "OLD_Core"},
+ {2, "Igor", "Core"},
+ {2, "Igor", "OLD_Core"},
+ {3, "Alexey", "SQL"},
+ {4, "Ivan", "QA"}}
+ : new Object[][]{
+ {null, null, "Core"},
+ {null, null, "OLD_Core"},
+ {1, "Roman", null},
+ {2, "Igor", null},
+ {3, "Alexey", "SQL"},
+ {4, "Ivan", "QA"}},
+ equalNulls);
+
+ verifyJoin(left, right, SEMI,
+ equalNulls ? new Object[][]{
+ {1, "Roman"},
+ {2, "Igor"},
+ {3, "Alexey"},
+ {4, "Ivan"},
+ } :
+ new Object[][]{
+ {3, "Alexey"},
+ {4, "Ivan"}, },
+ equalNulls);
+
+ verifyJoin(left, right, ANTI,
+ equalNulls ? new Object[][]{}
+ : new Object[][]{
+ {1, "Roman"},
+ {2, "Igor"}, },
+ equalNulls);
}
/**
@@ -363,31 +473,37 @@ public class MergeJoinExecutionTest extends
AbstractExecutionTest {
* @param joinType Join type.
* @param expRes Expected result.
*/
- private void verifyJoin(Object[][] left, Object[][] right, JoinRelType
joinType, Object[][] expRes) {
+ private void verifyJoin(Object[][] left, Object[][] right, JoinRelType
joinType, Object[][] expRes, boolean equalNulls) {
ExecutionContext<Object[]> ctx = executionContext(true);
RelDataType leftType = TypeUtils.createRowType(ctx.getTypeFactory(),
int.class, String.class, Integer.class);
ScanNode<Object[]> leftNode = new ScanNode<>(ctx, Arrays.asList(left));
- RelDataType rightType = TypeUtils.createRowType(ctx.getTypeFactory(),
int.class, String.class);
+ RelDataType rightType = TypeUtils.createRowType(ctx.getTypeFactory(),
Integer.class, String.class);
ScanNode<Object[]> rightNode = new ScanNode<>(ctx,
Arrays.asList(right));
- MergeJoinNode<Object[]> join = MergeJoinNode.create(ctx, leftType,
rightType, joinType, (r1, r2) -> {
- Object o1 = r1[2];
- Object o2 = r2[0];
-
- if (o1 == null || o2 == null) {
- if (o1 != null) {
- return 1;
- } else if (o2 != null) {
- return -1;
- } else {
- return 1;
- }
- }
-
- return Integer.compare((Integer) o1, (Integer) o2);
- });
+ RelDataTypeSystem typeSys =
CALCITE_CONNECTION_CONFIG.typeSystem(RelDataTypeSystem.class,
IgniteTypeSystem.INSTANCE);
+
+ IgniteTypeFactory typeFactory = new IgniteTypeFactory(typeSys);
+
+ ExecutionContext<Object[]> ectx =
+ new
ExecutionContext<>(BaseQueryContext.builder().logger(log).build(), null, null,
null,
+ null, null, ArrayRowHandler.INSTANCE, null, null);
+
+ ExpressionFactoryImpl<Object[]> expFactory = new
ExpressionFactoryImpl<>(ectx, typeFactory, SqlConformanceEnum.DEFAULT);
+
+ RelFieldCollation colLeft = new RelFieldCollation(2,
Direction.ASCENDING, NullDirection.FIRST);
+ RelFieldCollation colRight = new RelFieldCollation(0,
Direction.ASCENDING, NullDirection.FIRST);
+
+ ImmutableBitSet.Builder nullComparison = ImmutableBitSet.builder();
+
+ if (equalNulls && left.length > 0) {
+ nullComparison.set(0, left[0].length);
+ }
+
+ Comparator<Object[]> comp = expFactory.comparator(List.of(colLeft),
List.of(colRight), nullComparison.build());
+
+ MergeJoinNode<Object[]> join = MergeJoinNode.create(ctx, leftType,
rightType, joinType, comp);
join.register(asList(leftNode, rightNode));
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
index 4978465b89..cd2d8da7b5 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
@@ -39,24 +39,17 @@ import org.apache.calcite.sql.type.SqlTypeName;
import
org.apache.ignite.internal.sql.engine.prepare.ddl.DefaultValueDefinition.FunctionCall;
import
org.apache.ignite.internal.sql.engine.prepare.ddl.DefaultValueDefinition.Type;
import org.apache.ignite.internal.sql.engine.util.Commons;
-import org.apache.ignite.internal.testframework.IgniteTestUtils;
import org.apache.ignite.internal.testframework.WithSystemProperty;
import org.apache.ignite.lang.IgniteException;
import org.hamcrest.CustomMatcher;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
-import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
/**
* For {@link DdlSqlToCommandConverter} testing.
*/
public class DdlSqlToCommandConverterTest extends
AbstractDdlSqlToCommandConverterTest {
- @AfterAll
- public static void resetStaticState() {
- IgniteTestUtils.setFieldValue(Commons.class, "implicitPkEnabled",
null);
- }
-
@Test
void testCollectDataStorageNames() {
assertThat(collectDataStorageNames(Set.of()), equalTo(Map.of()));
@@ -105,8 +98,6 @@ public class DdlSqlToCommandConverterTest extends
AbstractDdlSqlToCommandConvert
@Test
public void tableWithoutPkShouldThrowErrorWhenSysPropDefault() throws
SqlParseException {
- IgniteTestUtils.setFieldValue(Commons.class, "implicitPkEnabled",
null);
-
var node = parse("CREATE TABLE t (val int)");
assertThat(node, instanceOf(SqlDdl.class));
@@ -122,8 +113,6 @@ public class DdlSqlToCommandConverterTest extends
AbstractDdlSqlToCommandConvert
@Test
@WithSystemProperty(key = "IMPLICIT_PK_ENABLED", value = "false")
public void tableWithoutPkShouldThrowErrorWhenSysPropDisabled() throws
SqlParseException {
- IgniteTestUtils.setFieldValue(Commons.class, "implicitPkEnabled",
null);
-
var node = parse("CREATE TABLE t (val int)");
assertThat(node, instanceOf(SqlDdl.class));
@@ -139,8 +128,6 @@ public class DdlSqlToCommandConverterTest extends
AbstractDdlSqlToCommandConvert
@Test
@WithSystemProperty(key = "IMPLICIT_PK_ENABLED", value = "true")
public void tableWithoutPkShouldInjectImplicitPkWhenSysPropEnabled()
throws SqlParseException {
- IgniteTestUtils.setFieldValue(Commons.class, "implicitPkEnabled",
null);
-
var node = parse("CREATE TABLE t (val int)");
assertThat(node, instanceOf(SqlDdl.class));
diff --git
a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/impl/TestMvTableStorage.java
b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/impl/TestMvTableStorage.java
index 21c84b1051..d543fa74c9 100644
---
a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/impl/TestMvTableStorage.java
+++
b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/impl/TestMvTableStorage.java
@@ -92,7 +92,7 @@ public class TestMvTableStorage implements MvTableStorage {
}
}
- /** Costructor. */
+ /** Constructor. */
public TestMvTableStorage(TableConfiguration tableCfg, TablesConfiguration
tablesCfg) {
this.tableCfg = tableCfg;
this.tablesCfg = tablesCfg;