Skip to content

Commit ecb0496

Browse files
committed
Fix 'IS NOT DISTINCT FROM' not working in JOIN when nulls are used
The changes try to enable Domain with only null value to be used. Modify JoinDomainBuilder to take into consideration the nulls from the value blocks.
1 parent 5a57103 commit ecb0496

File tree

4 files changed

+174
-11
lines changed

4 files changed

+174
-11
lines changed

core/trino-main/src/main/java/io/trino/operator/JoinDomainBuilder.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ public class JoinDomainBuilder
9999

100100
private long retainedSizeInBytes = INSTANCE_SIZE;
101101

102+
private boolean nullsAllowed;
103+
102104
public JoinDomainBuilder(
103105
Type type,
104106
int maxDistinctValues,
@@ -160,6 +162,9 @@ public boolean isCollecting()
160162

161163
public void add(Block block)
162164
{
165+
if (block.hasNull()) {
166+
nullsAllowed = true;
167+
}
163168
if (collectDistinctValues) {
164169
switch (block) {
165170
case ValueBlock valueBlock -> {
@@ -290,8 +295,7 @@ public Domain build()
290295
}
291296
}
292297
}
293-
// Inner and right join doesn't match rows with null key column values.
294-
return Domain.create(ValueSet.copyOf(type, values.build()), false);
298+
return Domain.create(ValueSet.copyOf(type, values.build()), nullsAllowed);
295299
}
296300
if (collectMinMax) {
297301
if (minValue == null) {
@@ -307,7 +311,6 @@ public Domain build()
307311

308312
private void add(ValueBlock block, int position)
309313
{
310-
// Inner and right join doesn't match rows with null key column values.
311314
if (block.isNull(position)) {
312315
return;
313316
}

core/trino-main/src/main/java/io/trino/sql/DynamicFilters.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,19 +295,18 @@ public Domain applyComparison(Domain domain)
295295
if (domain.isAll()) {
296296
return domain;
297297
}
298-
if (domain.isNone()) {
299-
// Dynamic filter collection skips nulls
298+
if (domain.getValues().isNone()) {
300299
// In case of IS NOT DISTINCT FROM, an empty Domain should still allow null
301300
if (nullAllowed) {
302301
return Domain.onlyNull(domain.getType());
303302
}
304-
return domain;
303+
return Domain.none(domain.getType());
305304
}
306305
Range span = domain.getValues().getRanges().getSpan();
307306
return switch (operator) {
308307
case EQUAL -> {
309-
if (nullAllowed) {
310-
yield Domain.create(domain.getValues(), true);
308+
if (!nullAllowed && domain.isNullAllowed()) {
309+
yield Domain.create(domain.getValues(), false);
311310
}
312311
yield domain;
313312
}

core/trino-main/src/test/java/io/trino/operator/TestDynamicFilterSourceOperator.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ public void testCollectWithNulls()
287287

288288
assertThat(partitions.build()).isEqualTo(ImmutableList.of(
289289
TupleDomain.withColumnDomains(ImmutableMap.of(
290-
new DynamicFilterId("0"), Domain.create(ValueSet.of(INTEGER, 1L, 2L, 3L, 4L, 5L), false)))));
290+
new DynamicFilterId("0"), Domain.create(ValueSet.of(INTEGER, 1L, 2L, 3L, 4L, 5L), true)))));
291291
}
292292

293293
@Test
@@ -490,7 +490,13 @@ public void testMultipleColumnsCollectMinMaxWithNulls()
490490
maxDistinctValues,
491491
ImmutableList.of(BIGINT, BIGINT),
492492
ImmutableList.of(largePage),
493-
ImmutableList.of(TupleDomain.none()));
493+
ImmutableList.of(TupleDomain.withColumnDomains(ImmutableMap.of(
494+
new DynamicFilterId("0"),
495+
Domain.onlyNull(BIGINT),
496+
new DynamicFilterId("1"),
497+
Domain.create(
498+
ValueSet.ofRanges(range(BIGINT, 200L, true, 300L, true)),
499+
false)))));
494500
}
495501

496502
@Test
@@ -570,7 +576,7 @@ public void testCollectDeduplication()
570576
ImmutableList.of(largePage, nullsPage),
571577
ImmutableList.of(TupleDomain.withColumnDomains(ImmutableMap.of(
572578
new DynamicFilterId("0"),
573-
Domain.create(ValueSet.of(BIGINT, 7L), false)))));
579+
Domain.create(ValueSet.of(BIGINT, 7L), true)))));
574580
}
575581

576582
@Test
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.tests;
15+
16+
import io.trino.Session;
17+
import io.trino.plugin.memory.MemoryPlugin;
18+
import io.trino.testing.AbstractTestQueryFramework;
19+
import io.trino.testing.QueryRunner;
20+
import io.trino.tests.tpch.TpchQueryRunner;
21+
import org.junit.jupiter.api.Test;
22+
23+
import static io.trino.testing.TestingNames.randomNameSuffix;
24+
import static io.trino.testing.TestingSession.testSessionBuilder;
25+
import static java.lang.String.format;
26+
27+
public class TestJoinIsNotDistinct
28+
extends AbstractTestQueryFramework
29+
{
30+
private final Session memorySession = testSessionBuilder()
31+
.setCatalog("memory")
32+
.setSchema("default")
33+
.build();
34+
35+
@Override
36+
protected QueryRunner createQueryRunner()
37+
throws Exception
38+
{
39+
QueryRunner queryRunner = TpchQueryRunner.builder().build();
40+
41+
queryRunner.installPlugin(new MemoryPlugin());
42+
queryRunner.createCatalog("memory", "memory");
43+
44+
return queryRunner;
45+
}
46+
47+
@Test
48+
public void testJoinWithIsNotDistinctFromOnNulls()
49+
{
50+
String tableName1 = "test_tab_" + randomNameSuffix();
51+
getQueryRunner().execute(memorySession, format("CREATE TABLE %s (k1 INT, k2 INT)", tableName1));
52+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (1, NULL)", tableName1));
53+
54+
String tableName2 = "test_tab_" + randomNameSuffix();
55+
getQueryRunner().execute(memorySession, format("CREATE TABLE %s (k1 INT, k2 INT)", tableName2));
56+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (1, NULL)", tableName2));
57+
58+
assertQuery(
59+
memorySession,
60+
format("SELECT *" +
61+
" FROM %s t" +
62+
" INNER JOIN %s AS s" +
63+
" ON s.k1 IS NOT DISTINCT FROM t.k1" +
64+
" AND s.k2 IS NOT DISTINCT FROM t.k2", tableName1, tableName2),
65+
"VALUES (1, NULL, 1, NULL)");
66+
}
67+
68+
@Test
69+
public void testJoinWithIsNotDistinctFromOnNulls2()
70+
{
71+
String tableName1 = "test_tab_" + randomNameSuffix();
72+
getQueryRunner().execute(memorySession, format("CREATE TABLE %s (k1 INT, k2 INT)", tableName1));
73+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (1, NULL)", tableName1));
74+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (NULL, 2)", tableName1));
75+
76+
String tableName2 = "test_tab_" + randomNameSuffix();
77+
getQueryRunner().execute(memorySession, format("CREATE TABLE %s (k1 INT, k2 INT)", tableName2));
78+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (1, NULL)", tableName2));
79+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (3, NULL)", tableName2));
80+
81+
assertQuery(
82+
memorySession,
83+
format("SELECT *" +
84+
" FROM %s t" +
85+
" INNER JOIN %s AS s" +
86+
" ON s.k1 IS NOT DISTINCT FROM t.k1" +
87+
" AND s.k2 IS NOT DISTINCT FROM t.k2", tableName1, tableName2),
88+
"VALUES (1, NULL, 1, NULL)");
89+
}
90+
91+
@Test
92+
public void testJoinWithIsNotDistinctFromOnNulls3()
93+
{
94+
String tableName1 = "test_tab_" + randomNameSuffix();
95+
getQueryRunner().execute(memorySession, format("CREATE TABLE %s (k1 INT, k2 INT)", tableName1));
96+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (1, NULL)", tableName1));
97+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (NULL, 2)", tableName1));
98+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (NULL, NULL)", tableName1));
99+
100+
String tableName2 = "test_tab_" + randomNameSuffix();
101+
getQueryRunner().execute(memorySession, format("CREATE TABLE %s (k1 INT, k2 INT)", tableName2));
102+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (1, NULL)", tableName2));
103+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (3, NULL)", tableName2));
104+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (NULL, NULL)", tableName2));
105+
106+
assertQuery(
107+
memorySession,
108+
format("SELECT *" +
109+
" FROM %s t" +
110+
" INNER JOIN %s AS s" +
111+
" ON s.k1 IS NOT DISTINCT FROM t.k1" +
112+
" AND s.k2 IS NOT DISTINCT FROM t.k2", tableName1, tableName2),
113+
"VALUES (1, NULL, 1, NULL), (NULL, NULL, NULL, NULL)");
114+
}
115+
116+
@Test
117+
public void testJoinWithIsNotDistinctFromOnNulls4()
118+
{
119+
String tableName1 = "test_tab_" + randomNameSuffix();
120+
getQueryRunner().execute(memorySession, format("CREATE TABLE %s (k1 INT, k2 INT)", tableName1));
121+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (1, NULL)", tableName1));
122+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (NULL, 2)", tableName1));
123+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (NULL, NULL)", tableName1));
124+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (2, 2)", tableName1));
125+
126+
String tableName2 = "test_tab_" + randomNameSuffix();
127+
getQueryRunner().execute(memorySession, format("CREATE TABLE %s (k1 INT, k2 INT)", tableName2));
128+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (1, NULL)", tableName2));
129+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (3, NULL)", tableName2));
130+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (NULL, NULL)", tableName2));
131+
getQueryRunner().execute(memorySession, format("INSERT INTO %s VALUES (2, 2)", tableName2));
132+
133+
assertQuery(
134+
memorySession,
135+
format("SELECT *" +
136+
" FROM %s t" +
137+
" INNER JOIN %s AS s" +
138+
" ON s.k1 IS NOT DISTINCT FROM t.k1" +
139+
" AND s.k2 IS NOT DISTINCT FROM t.k2", tableName1, tableName2),
140+
"VALUES (1, NULL, 1, NULL), (NULL, NULL, NULL, NULL), (2, 2, 2, 2)");
141+
}
142+
143+
@Test
144+
public void testJoinWithIsNotDistinctFromOnNullsInMemory()
145+
{
146+
assertQuery(
147+
memorySession,
148+
"SELECT *" +
149+
" FROM (SELECT 1 AS k1, NULL AS k2) t" +
150+
" INNER JOIN (SELECT 1 AS k1, NULL AS k2) AS s" +
151+
" ON s.k1 IS NOT DISTINCT FROM t.k1" +
152+
" AND s.k2 IS NOT DISTINCT FROM t.k2",
153+
"VALUES (1, NULL, 1, NULL)");
154+
}
155+
}

0 commit comments

Comments
 (0)