Skip to content

Commit 0f360f7

Browse files
xkrogenartem-sokolov
authored andcommitted
Handle NullPointerException in StatementAnalyzer createMergeAnalysis
Co-authored-by: Artem Sokolov <[email protected]>
1 parent 47646fe commit 0f360f7

File tree

2 files changed

+98
-1
lines changed

2 files changed

+98
-1
lines changed

core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@
323323
import static io.trino.spi.StandardErrorCode.FUNCTION_IMPLEMENTATION_ERROR;
324324
import static io.trino.spi.StandardErrorCode.FUNCTION_NOT_FOUND;
325325
import static io.trino.spi.StandardErrorCode.FUNCTION_NOT_WINDOW;
326+
import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR;
326327
import static io.trino.spi.StandardErrorCode.INVALID_ARGUMENTS;
327328
import static io.trino.spi.StandardErrorCode.INVALID_CATALOG_PROPERTY;
328329
import static io.trino.spi.StandardErrorCode.INVALID_CHECK_CONSTRAINT;
@@ -3767,7 +3768,18 @@ private void createMergeAnalysis(
37673768
List<ColumnHandle> redistributionColumnHandles = redistributionColumnHandlesBuilder.build();
37683769

37693770
List<Integer> insertPartitioningArgumentIndexes = partitioningColumnNames.stream()
3770-
.map(fieldIndexes::get)
3771+
.map(partitioningColumnName -> {
3772+
Integer value = fieldIndexes.get(partitioningColumnName);
3773+
// This shouldn't happen, as the connector should only return partitioning columns that are present in the
3774+
// table schema, but validation is performed here to avoid NPEs in case of a bug in the connector
3775+
if (value == null) {
3776+
throw new TrinoException(GENERIC_INTERNAL_ERROR, format(
3777+
"Unable to determine field index for partitioning column '%s' (available columns: [%s])",
3778+
partitioningColumnName,
3779+
fieldIndexes.keySet().stream().map(key -> format("'%s'", key)).collect(Collectors.joining(", "))));
3780+
}
3781+
return value;
3782+
})
37713783
.collect(toImmutableList());
37723784

37733785
Set<ColumnHandle> nonNullableColumnHandles = metadata.getTableMetadata(session, handle).columns().stream()
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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.sql.query;
15+
16+
import com.google.common.collect.ImmutableList;
17+
import com.google.common.collect.ImmutableMap;
18+
import io.trino.Session;
19+
import io.trino.connector.MockConnectorFactory;
20+
import io.trino.connector.MockConnectorPlugin;
21+
import io.trino.spi.connector.ConnectorTableLayout;
22+
import io.trino.spi.security.Identity;
23+
import io.trino.testing.QueryRunner;
24+
import io.trino.testing.StandaloneQueryRunner;
25+
import org.junit.jupiter.api.AfterAll;
26+
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.api.TestInstance;
28+
import org.junit.jupiter.api.parallel.Execution;
29+
30+
import java.util.Optional;
31+
32+
import static io.trino.connector.MockConnectorEntities.TPCH_NATION_SCHEMA;
33+
import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME;
34+
import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR;
35+
import static io.trino.testing.TestingSession.testSessionBuilder;
36+
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
38+
import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT;
39+
40+
@TestInstance(PER_CLASS)
41+
@Execution(CONCURRENT)
42+
public class TestMetadataMismatch
43+
{
44+
private static final String LOCAL_CATALOG = "local";
45+
private static final String MOCK_CATALOG = "mock";
46+
private static final String USER = "user";
47+
48+
private static final Session SESSION = testSessionBuilder()
49+
.setCatalog(LOCAL_CATALOG)
50+
.setSchema(TINY_SCHEMA_NAME)
51+
.setIdentity(Identity.forUser(USER).build())
52+
.build();
53+
54+
private final QueryAssertions assertions;
55+
56+
public TestMetadataMismatch()
57+
{
58+
QueryRunner runner = new StandaloneQueryRunner(SESSION);
59+
60+
runner.installPlugin(new MockConnectorPlugin(MockConnectorFactory.builder()
61+
.withGetColumns(_ -> TPCH_NATION_SCHEMA)
62+
.withGetInsertLayout((_, _) ->
63+
Optional.of(new ConnectorTableLayout(ImmutableList.of("year")))) // nonexistent column in TPCH_NATION_SCHEMA
64+
.build()));
65+
runner.createCatalog(MOCK_CATALOG, "mock", ImmutableMap.of());
66+
67+
assertions = new QueryAssertions(runner);
68+
}
69+
70+
@AfterAll
71+
public void teardown()
72+
{
73+
assertions.close();
74+
}
75+
76+
@Test
77+
public void testGetInsertLayoutMismatchAgainstColumns()
78+
{
79+
assertThat(assertions.query("DELETE FROM mock.tiny.nation WHERE nationkey < 3"))
80+
.failure()
81+
.hasErrorCode(GENERIC_INTERNAL_ERROR)
82+
// use regex to match the error message to accommodate any ordering of the columns being printed
83+
.hasMessageMatching("Unable to determine field index for partitioning column 'year' \\(available columns: \\[('(nationkey|regionkey|name|comment)', ){3}'(nationkey|regionkey|name|comment)']\\)");
84+
}
85+
}

0 commit comments

Comments
 (0)