This is an automated email from the ASF dual-hosted git repository. ppa 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 f4c33ff66f IGNITE-14865 Sql. Public SQL API should only throw public exceptions (#2345) f4c33ff66f is described below commit f4c33ff66fb78897534027e210cf37ff8d357159 Author: Pavel Pereslegin <xxt...@gmail.com> AuthorDate: Wed Aug 9 14:12:41 2023 +0300 IGNITE-14865 Sql. Public SQL API should only throw public exceptions (#2345) --- .../org/apache/ignite/sql/SqlBatchException.java | 6 +- .../client/handler/JdbcQueryEventHandlerImpl.java | 5 +- .../ignite/internal/sql/AbstractSession.java | 10 +- .../dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs | 8 +- .../dotnet/Apache.Ignite/Internal/Sql/Sql.cs | 13 -- .../ignite/internal/sql/api/ItCommonApiTest.java | 7 +- .../internal/sql/api/ItSqlAsynchronousApiTest.java | 157 +++++++++++++-------- .../internal/sql/api/ItSqlSynchronousApiTest.java | 101 +++++++------ .../internal/sql/engine/ItDataTypesTest.java | 5 +- .../ignite/internal/sql/engine/ItDmlTest.java | 9 +- .../sql/engine/ItDynamicParameterTest.java | 7 +- .../internal/sql/engine/ItLimitOffsetTest.java | 41 +++--- modules/sql-engine/build.gradle | 2 + .../ignite/internal/sql/api/SessionImpl.java | 67 ++++++--- .../internal/sql/engine/AsyncSqlCursorImpl.java | 17 +-- .../sql/engine/exec/ExchangeServiceImpl.java | 42 ++---- .../sql/engine/exec/exp/ExpressionFactoryImpl.java | 19 ++- .../internal/sql/engine/exec/rel/RootNode.java | 14 +- .../sql/engine/prepare/IgniteSqlValidator.java | 2 - .../sql/engine/prepare/PrepareServiceImpl.java | 33 ++--- .../engine/util/SqlExceptionMapperProvider.java | 63 +++++++++ .../internal/sql/engine/util/SqlTestUtils.java | 22 +++ 22 files changed, 392 insertions(+), 258 deletions(-) diff --git a/modules/api/src/main/java/org/apache/ignite/sql/SqlBatchException.java b/modules/api/src/main/java/org/apache/ignite/sql/SqlBatchException.java index 0cf0194065..3e5476eb6a 100644 --- a/modules/api/src/main/java/org/apache/ignite/sql/SqlBatchException.java +++ b/modules/api/src/main/java/org/apache/ignite/sql/SqlBatchException.java @@ -38,11 +38,13 @@ public class SqlBatchException extends SqlException { /** * Creates a grid exception with the given throwable as a cause and source of error message. * + * @param traceId Unique identifier of the exception. + * @param code Full error code. * @param updCntrs Array that describes the outcome of a batch execution. * @param cause Non-null throwable cause. */ - public SqlBatchException(int code, long[] updCntrs, Throwable cause) { - super(code, cause.getMessage(), cause); + public SqlBatchException(UUID traceId, int code, long[] updCntrs, Throwable cause) { + super(traceId, code, cause.getMessage(), cause); this.updCntrs = updCntrs != null ? updCntrs : LONG_EMPTY_ARRAY; } diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java index 92e30201b1..c04486f5ec 100644 --- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java +++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java @@ -66,6 +66,7 @@ import org.apache.ignite.internal.sql.engine.session.SessionNotFoundException; import org.apache.ignite.internal.util.ExceptionUtils; import org.apache.ignite.internal.util.Pair; import org.apache.ignite.lang.IgniteException; +import org.apache.ignite.lang.IgniteExceptionMapperUtil; import org.apache.ignite.lang.IgniteInternalCheckedException; import org.apache.ignite.lang.IgniteInternalException; import org.apache.ignite.sql.ColumnType; @@ -504,7 +505,7 @@ public class JdbcQueryEventHandlerImpl implements JdbcQueryEventHandler { return CompletableFuture.completedFuture(resAndError.getFirst()); } - Throwable error = resAndError.getSecond(); + Throwable error = ExceptionUtils.unwrapCause(resAndError.getSecond()); if (sessionExpiredError(error)) { SessionId newSessionId = recreateSession(finalSessionId); @@ -512,7 +513,7 @@ public class JdbcQueryEventHandlerImpl implements JdbcQueryEventHandler { return action.perform(newSessionId); } - return CompletableFuture.failedFuture(error); + return CompletableFuture.failedFuture(IgniteExceptionMapperUtil.mapToPublicException(error)); }); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/AbstractSession.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/AbstractSession.java index eccd9e06fa..24f8681ca0 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/sql/AbstractSession.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/AbstractSession.java @@ -60,7 +60,7 @@ public interface AbstractSession extends Session { try { return new SyncResultSetAdapter<>(executeAsync(transaction, query, arguments).join()); } catch (CompletionException e) { - throw ExceptionUtils.wrap(e); + throw ExceptionUtils.sneakyThrow(ExceptionUtils.copyExceptionWithCause(e)); } } @@ -79,7 +79,7 @@ public interface AbstractSession extends Session { try { return new SyncResultSetAdapter<>(executeAsync(transaction, statement, arguments).join()); } catch (CompletionException e) { - throw ExceptionUtils.wrap(e); + throw ExceptionUtils.sneakyThrow(ExceptionUtils.copyExceptionWithCause(e)); } } @@ -104,7 +104,7 @@ public interface AbstractSession extends Session { try { return new SyncResultSetAdapter<>(executeAsync(transaction, mapper, query, arguments).join()); } catch (CompletionException e) { - throw ExceptionUtils.wrap(e); + throw ExceptionUtils.sneakyThrow(ExceptionUtils.copyExceptionWithCause(e)); } } @@ -129,7 +129,7 @@ public interface AbstractSession extends Session { try { return new SyncResultSetAdapter<>(executeAsync(transaction, mapper, statement, arguments).join()); } catch (CompletionException e) { - throw ExceptionUtils.wrap(e); + throw ExceptionUtils.sneakyThrow(ExceptionUtils.copyExceptionWithCause(e)); } } @@ -147,7 +147,7 @@ public interface AbstractSession extends Session { try { return executeBatchAsync(transaction, dmlQuery, batch).join(); } catch (CompletionException e) { - throw ExceptionUtils.wrap(e); + throw ExceptionUtils.sneakyThrow(ExceptionUtils.copyExceptionWithCause(e)); } } } diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs index a7699e1454..1ae7b62c8f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs @@ -334,8 +334,12 @@ namespace Apache.Ignite.Tests.Sql [Test] public void TestInvalidSqlThrowsException() { - var ex = Assert.ThrowsAsync<IgniteException>(async () => await Client.Sql.ExecuteAsync(null, "select x from bad")); - StringAssert.Contains("From line 1, column 15 to line 1, column 17: Object 'BAD' not found", ex!.Message); + var ex = Assert.ThrowsAsync<SqlException>(async () => await Client.Sql.ExecuteAsync(null, "select x from bad")); + StringAssert.Contains("Invalid query, check inner exceptions for details: select x from bad", ex!.Message); + + var innerEx = ex.InnerException; + Assert.IsInstanceOf<SqlException>(innerEx); + StringAssert.Contains("From line 1, column 15 to line 1, column 17: Object 'BAD' not found", innerEx!.Message); } [Test] diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs index 2d23d8b4d3..bc7a569d28 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs @@ -152,19 +152,6 @@ namespace Apache.Ignite.Internal.Sql "Invalid query, check inner exceptions for details: " + statement.Query, e); } -#if DEBUG - catch (IgniteException e) - { - // TODO IGNITE-14865 Calcite error handling rework - // This should not happen, all parsing errors must be wrapped in SqlException. - if ((e.InnerException?.Message ?? e.Message).StartsWith("org.apache.calcite.", StringComparison.Ordinal)) - { - Console.WriteLine("SQL parsing failed: " + statement.Query); - } - - throw; - } -#endif PooledArrayBuffer Write() { diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItCommonApiTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItCommonApiTest.java index 3fa83733d8..9b7a0d5227 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItCommonApiTest.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItCommonApiTest.java @@ -23,12 +23,14 @@ import static org.apache.ignite.internal.testframework.IgniteTestUtils.await; import static org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.time.Instant; import java.time.LocalDateTime; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.apache.calcite.schema.SchemaPlus; import org.apache.ignite.Ignite; @@ -84,8 +86,9 @@ public class ItCommonApiTest extends ClusterPerClassIntegrationTest { waitForCondition(() -> queryProcessor().liveSessions().size() == 1, 10_000); // first session should no longer exist for the moment - IgniteException err = assertThrows(IgniteException.class, () -> ses1.execute(null, "SELECT 1 + 1")); - assertThat(err.getMessage(), containsString("Session not found")); + ExecutionException err = assertThrows(ExecutionException.class, () -> ses1.executeAsync(null, "SELECT 1 + 1").get()); + assertThat(err.getCause(), instanceOf(IgniteException.class)); + assertThat(err.getCause().getMessage(), containsString("Session not found")); // already started query should fail due to session has been expired assertThrowsWithCause(() -> { diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlAsynchronousApiTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlAsynchronousApiTest.java index e1b6cb0f03..694a28e8af 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlAsynchronousApiTest.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlAsynchronousApiTest.java @@ -22,7 +22,16 @@ import static org.apache.ignite.internal.sql.engine.util.QueryChecker.containsTa import static org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause; import static org.apache.ignite.internal.testframework.IgniteTestUtils.await; import static org.apache.ignite.lang.ErrorGroups.Sql.CONSTRAINT_VIOLATION_ERR; +import static org.apache.ignite.lang.ErrorGroups.Sql.CURSOR_CLOSED_ERR; +import static org.apache.ignite.lang.ErrorGroups.Sql.EXECUTION_CANCELLED_ERR; +import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_NO_RESULT_SET_ERR; +import static org.apache.ignite.lang.ErrorGroups.Sql.RUNTIME_ERR; +import static org.apache.ignite.lang.ErrorGroups.Sql.SESSION_CLOSED_ERR; +import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_PARSE_ERR; import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR; +import static org.apache.ignite.lang.ErrorGroups.Transactions.TX_FAILED_READ_WRITE_OPERATION_ERR; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -43,15 +52,19 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import javax.annotation.Nullable; import org.apache.ignite.internal.client.sql.ClientSql; import org.apache.ignite.internal.sql.api.ColumnMetadataImpl.ColumnOriginImpl; import org.apache.ignite.internal.sql.engine.ClusterPerClassIntegrationTest; import org.apache.ignite.internal.sql.engine.exec.ExecutionCancelledException; import org.apache.ignite.internal.testframework.IgniteTestUtils; +import org.apache.ignite.internal.testframework.IgniteTestUtils.RunnableX; import org.apache.ignite.internal.tx.TxManager; import org.apache.ignite.internal.util.CollectionUtils; import org.apache.ignite.lang.ColumnAlreadyExistsException; import org.apache.ignite.lang.ColumnNotFoundException; +import org.apache.ignite.lang.ErrorGroups; +import org.apache.ignite.lang.ErrorGroups.Index; import org.apache.ignite.lang.IgniteException; import org.apache.ignite.lang.IndexAlreadyExistsException; import org.apache.ignite.lang.IndexNotFoundException; @@ -102,7 +115,7 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { @Test public void ddl() throws Exception { - IgniteSql sql = CLUSTER_NODES.get(0).sql(); + IgniteSql sql = igniteSql(); Session ses = sql.createSession(); // CREATE TABLE @@ -114,7 +127,8 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { "CREATE TABLE TEST(ID INT PRIMARY KEY, VAL0 INT)" ); checkError( - IgniteException.class, + SqlException.class, + ErrorGroups.Table.TABLE_DEFINITION_ERR, "Can't create table with duplicate columns: ID, VAL, VAL", ses, "CREATE TABLE TEST1(ID INT PRIMARY KEY, VAL INT, VAL INT)" @@ -156,6 +170,7 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { checkDdl(true, ses, "CREATE INDEX TEST_IDX3 ON TEST(ID, VAL0, VAL1)"); checkError( SqlException.class, + Index.INVALID_INDEX_DEFINITION_ERR, "Can't create index on duplicate columns: VAL0, VAL0", ses, "CREATE INDEX TEST_IDX4 ON TEST(VAL0, VAL0)" @@ -163,15 +178,19 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { checkError( SqlException.class, + STMT_VALIDATION_ERR, "Can`t delete column(s). Column VAL1 is used by indexes [TEST_IDX3].", ses, "ALTER TABLE TEST DROP COLUMN val1" ); - SqlException ex = IgniteTestUtils.cause(assertThrows(Throwable.class, - () -> await(ses.executeAsync(null, "ALTER TABLE TEST DROP COLUMN (val0, val1)"))), SqlException.class); - assertNotNull(ex); - assertEquals(STMT_VALIDATION_ERR, ex.code()); + SqlException ex = checkError( + SqlException.class, + STMT_VALIDATION_ERR, + "Can`t delete column(s).", + ses, + "ALTER TABLE TEST DROP COLUMN (val0, val1)" + ); String msg = ex.getMessage(); String explainMsg = "Unexpected error message: " + msg; @@ -182,6 +201,7 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { checkError( SqlException.class, + STMT_VALIDATION_ERR, "Can`t delete column, belongs to primary key: [name=ID]", ses, "ALTER TABLE TEST DROP COLUMN id" @@ -231,7 +251,7 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { public void dml() { sql("CREATE TABLE TEST(ID INT PRIMARY KEY, VAL0 INT)"); - IgniteSql sql = CLUSTER_NODES.get(0).sql(); + IgniteSql sql = igniteSql(); Session ses = sql.createSession(); for (int i = 0; i < ROW_COUNT; ++i) { @@ -337,12 +357,13 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { // Outdated tx. Transaction outerTx0 = outerTx; - - assertThrows(SqlException.class, + IgniteException e = assertThrows(IgniteException.class, () -> checkDml(1, ses, "INSERT INTO TEST VALUES (?, ?)", outerTx0, ROW_COUNT, Integer.MAX_VALUE)); + assertEquals(TX_FAILED_READ_WRITE_OPERATION_ERR, e.code()); - assertThrows(SqlException.class, + e = assertThrows(SqlException.class, () -> checkDml(1, ses, "INSERT INTO TEST VALUES (?, ?)", ROW_COUNT, Integer.MAX_VALUE)); + assertEquals(CONSTRAINT_VIOLATION_ERR, e.code()); AsyncResultSet rs = await(ses.executeAsync(null, "SELECT VAL0 FROM TEST ORDER BY VAL0")); @@ -579,7 +600,7 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { @Test public void errors() { - sql("CREATE TABLE TEST(ID INT PRIMARY KEY, VAL0 INT)"); + sql("CREATE TABLE TEST(ID INT PRIMARY KEY, VAL0 INT NOT NULL)"); for (int i = 0; i < ROW_COUNT; ++i) { sql("INSERT INTO TEST VALUES (?, ?)", i, i); } @@ -588,33 +609,40 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { Session ses = sql.sessionBuilder().defaultPageSize(ROW_COUNT / 2).build(); // Parse error. - assertThrowsWithCause(() -> await(ses.executeAsync(null, "SELECT ID FROM")), - SqlException.class, "Failed to parse query"); + checkError(SqlException.class, STMT_PARSE_ERR, "Failed to parse query", ses, "SELECT ID FROM"); + + // Validation errors. + checkError(SqlException.class, STMT_VALIDATION_ERR, "Column 'VAL0' does not allow NULLs", ses, + "INSERT INTO TEST VALUES (2, NULL)"); + + checkError(SqlException.class, STMT_VALIDATION_ERR, "Object 'NOT_EXISTING_TABLE' not found", ses, + "SELECT * FROM NOT_EXISTING_TABLE"); + + checkError(SqlException.class, STMT_VALIDATION_ERR, "Column 'NOT_EXISTING_COLUMN' not found", ses, + "SELECT NOT_EXISTING_COLUMN FROM TEST"); - // Multiple statements error. - assertThrowsWithCause(() -> await(ses.executeAsync(null, "SELECT 1; SELECT 2")), - SqlException.class, "Multiple statements are not allowed"); + checkError(SqlException.class, STMT_VALIDATION_ERR, "Multiple statements are not allowed", ses, "SELECT 1; SELECT 2"); - // Planning error. - assertThrowsWithCause(() -> await(ses.executeAsync(null, "CREATE TABLE TEST2 (VAL INT)")), - SqlException.class, "Table without PRIMARY KEY is not supported"); + checkError(SqlException.class, STMT_VALIDATION_ERR, "Table without PRIMARY KEY is not supported", ses, + "CREATE TABLE TEST2 (VAL INT)"); // Execute error. - assertThrowsWithCause(() -> await(ses.executeAsync(null, "SELECT 1 / ?", 0)), - IgniteException.class, "/ by zero"); + checkError(SqlException.class, RUNTIME_ERR, "/ by zero", ses, "SELECT 1 / ?", 0); + checkError(SqlException.class, RUNTIME_ERR, "negative substring length not allowed", ses, "SELECT SUBSTRING('foo', 1, -3)"); // No result set error. { AsyncResultSet ars = await(ses.executeAsync(null, "CREATE TABLE TEST3 (ID INT PRIMARY KEY)")); - assertThrowsWithCause(() -> await(ars.fetchNextPage()), NoRowSetExpectedException.class, - "Query has no result set"); + assertThrowsPublicException(() -> await(ars.fetchNextPage()), + NoRowSetExpectedException.class, QUERY_NO_RESULT_SET_ERR, "Query has no result set"); } // Cursor closed error. { AsyncResultSet ars = await(ses.executeAsync(null, "SELECT * FROM TEST")); await(ars.closeAsync()); - assertThrowsWithCause(() -> await(ars.fetchNextPage()), CursorClosedException.class); + assertThrowsPublicException(() -> await(ars.fetchNextPage()), + CursorClosedException.class, CURSOR_CLOSED_ERR, null); } } @@ -629,8 +657,9 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { { Transaction tx = igniteTx().begin(); try { - assertThrowsWithCause(() -> await(ses.executeAsync(tx, "CREATE TABLE TEST2(ID INT PRIMARY KEY, VAL0 INT)")), + assertThrowsPublicException(() -> await(ses.executeAsync(tx, "CREATE TABLE TEST2(ID INT PRIMARY KEY, VAL0 INT)")), SqlException.class, + STMT_VALIDATION_ERR, "DDL doesn't support transactions." ); } finally { @@ -642,8 +671,9 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { AsyncResultSet<SqlRow> res = await(ses.executeAsync(tx, "INSERT INTO TEST VALUES (?, ?)", -1, -1)); assertEquals(1, res.affectedRows()); - assertThrowsWithCause(() -> await(ses.executeAsync(tx, "CREATE TABLE TEST2(ID INT PRIMARY KEY, VAL0 INT)")), + assertThrowsPublicException(() -> await(ses.executeAsync(tx, "CREATE TABLE TEST2(ID INT PRIMARY KEY, VAL0 INT)")), SqlException.class, + STMT_VALIDATION_ERR, "DDL doesn't support transactions." ); tx.commit(); @@ -669,16 +699,12 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { // Fetched page is available after cancel. ars0.currentPage(); - assertThrowsWithCause( - () -> await(ars0.fetchNextPage()), - ExecutionCancelledException.class - ); + SqlException sqlEx = assertThrowsPublicException(() -> await(ars0.fetchNextPage()), + SqlException.class, EXECUTION_CANCELLED_ERR, null); + assertTrue(IgniteTestUtils.hasCause(sqlEx, ExecutionCancelledException.class, null)); - assertThrowsWithCause( - () -> await(ses.executeAsync(null, "SELECT ID FROM TEST")), - SqlException.class, - "Session is closed" - ); + assertThrowsPublicException(() -> await(ses.executeAsync(null, "SELECT ID FROM TEST")), + SqlException.class, SESSION_CLOSED_ERR, "Session is closed"); } @Test @@ -703,17 +729,11 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { IntStream.range(0, ROW_COUNT).forEach(i -> assertEquals(i, res.get(i).get(0))); // Check invalid query type - assertThrowsWithCause( - () -> await(ses.executeBatchAsync(null, "SELECT * FROM TEST", args)), - SqlException.class, - "Invalid SQL statement type in the batch" - ); + assertThrowsPublicException(() -> await(ses.executeBatchAsync(null, "SELECT * FROM TEST", args)), + SqlBatchException.class, STMT_VALIDATION_ERR, "Invalid SQL statement type in the batch"); - assertThrowsWithCause( - () -> await(ses.executeBatchAsync(null, "CREATE TABLE TEST1(ID INT PRIMARY KEY, VAL0 INT)", args)), - SqlException.class, - "Invalid SQL statement type in the batch" - ); + assertThrowsPublicException(() -> await(ses.executeBatchAsync(null, "CREATE TABLE TEST1(ID INT PRIMARY KEY, VAL0 INT)", args)), + SqlBatchException.class, STMT_VALIDATION_ERR, "Invalid SQL statement type in the batch"); } @Test @@ -735,12 +755,13 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { } } - SqlBatchException ex = assertThrows( + SqlBatchException ex = assertThrowsPublicException( + () -> await(ses.executeBatchAsync(null, "INSERT INTO TEST VALUES (?, ?)", args)), SqlBatchException.class, - () -> await(ses.executeBatchAsync(null, "INSERT INTO TEST VALUES (?, ?)", args)) + CONSTRAINT_VIOLATION_ERR, + null ); - assertEquals(CONSTRAINT_VIOLATION_ERR, ex.code()); assertEquals(err, ex.updateCounters().length); IntStream.range(0, ex.updateCounters().length).forEach(i -> assertEquals(1, ex.updateCounters()[i])); } @@ -767,12 +788,19 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { checkDdl(expectedApplied, ses, sql, null); } - private static void checkError(Class<? extends Throwable> expectedException, String msg, Session ses, String sql, Object... args) { - assertThrowsWithCause(() -> await(ses.executeAsync( - null, - sql, - args - )), expectedException, msg); + private static <T extends IgniteException> T checkError(Class<T> expCls, String msg, Session ses, String sql, Object... args) { + return checkError(expCls, null, msg, ses, sql, args); + } + + private static <T extends IgniteException> T checkError( + Class<T> expCls, + @Nullable Integer code, + @Nullable String msg, + Session ses, + String sql, + Object... args + ) { + return assertThrowsPublicException(() -> await(ses.executeAsync(null, sql, args)), expCls, code, msg); } protected static void checkDml(int expectedAffectedRows, Session ses, String sql, Transaction tx, Object... args) { @@ -788,10 +816,29 @@ public class ItSqlAsynchronousApiTest extends ClusterPerClassIntegrationTest { await(asyncRes.closeAsync()); } - protected static void checkDml(int expectedAffectedRows, Session ses, String sql, Object... args) { + private static void checkDml(int expectedAffectedRows, Session ses, String sql, Object... args) { checkDml(expectedAffectedRows, ses, sql, null, args); } + static <T extends IgniteException> T assertThrowsPublicException( + RunnableX executable, + Class<T> expCls, + @Nullable Integer code, + @Nullable String msgPart + ) { + T ex = assertThrows(expCls, executable::run); + + if (code != null) { + assertEquals(new IgniteException(code).codeAsString(), ex.codeAsString()); + } + + if (msgPart != null) { + assertThat(ex.getMessage(), containsString(msgPart)); + } + + return ex; + } + static class TestPageProcessor implements Function<AsyncResultSet<SqlRow>, CompletionStage<AsyncResultSet<SqlRow>>> { private int expectedPages; diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlSynchronousApiTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlSynchronousApiTest.java index 38b8c69ad0..0a8f3f4d81 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlSynchronousApiTest.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlSynchronousApiTest.java @@ -17,12 +17,14 @@ package org.apache.ignite.internal.sql.api; +import static org.apache.ignite.internal.sql.api.ItSqlAsynchronousApiTest.assertThrowsPublicException; import static org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause; -import static org.apache.ignite.internal.testframework.IgniteTestUtils.await; +import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_NO_RESULT_SET_ERR; +import static org.apache.ignite.lang.ErrorGroups.Sql.RUNTIME_ERR; +import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_PARSE_ERR; import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -32,12 +34,14 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; +import javax.annotation.Nullable; import org.apache.ignite.internal.app.IgniteImpl; import org.apache.ignite.internal.sql.engine.ClusterPerClassIntegrationTest; -import org.apache.ignite.internal.testframework.IgniteTestUtils; import org.apache.ignite.internal.tx.TxManager; import org.apache.ignite.lang.ColumnAlreadyExistsException; import org.apache.ignite.lang.ColumnNotFoundException; +import org.apache.ignite.lang.ErrorGroups; +import org.apache.ignite.lang.ErrorGroups.Index; import org.apache.ignite.lang.ErrorGroups.Sql; import org.apache.ignite.lang.IgniteException; import org.apache.ignite.lang.IndexAlreadyExistsException; @@ -45,7 +49,6 @@ import org.apache.ignite.lang.IndexNotFoundException; import org.apache.ignite.lang.TableAlreadyExistsException; import org.apache.ignite.lang.TableNotFoundException; import org.apache.ignite.sql.BatchedArguments; -import org.apache.ignite.sql.CursorClosedException; import org.apache.ignite.sql.IgniteSql; import org.apache.ignite.sql.NoRowSetExpectedException; import org.apache.ignite.sql.ResultSet; @@ -56,7 +59,6 @@ import org.apache.ignite.sql.SqlRow; import org.apache.ignite.table.Table; import org.apache.ignite.tx.Transaction; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @@ -98,6 +100,7 @@ public class ItSqlSynchronousApiTest extends ClusterPerClassIntegrationTest { ); checkError( SqlException.class, + ErrorGroups.Table.TABLE_DEFINITION_ERR, "Can't create table with duplicate columns: ID, VAL, VAL", ses, "CREATE TABLE TEST1(ID INT PRIMARY KEY, VAL INT, VAL INT)" @@ -139,6 +142,7 @@ public class ItSqlSynchronousApiTest extends ClusterPerClassIntegrationTest { checkDdl(true, ses, "CREATE INDEX TEST_IDX3 ON TEST(ID, VAL0, VAL1)"); checkError( SqlException.class, + Index.INVALID_INDEX_DEFINITION_ERR, "Can't create index on duplicate columns: VAL0, VAL0", ses, "CREATE INDEX TEST_IDX4 ON TEST(VAL0, VAL0)" @@ -146,15 +150,19 @@ public class ItSqlSynchronousApiTest extends ClusterPerClassIntegrationTest { checkError( SqlException.class, + STMT_VALIDATION_ERR, "Can`t delete column(s). Column VAL1 is used by indexes [TEST_IDX3].", ses, "ALTER TABLE TEST DROP COLUMN val1" ); - SqlException ex = IgniteTestUtils.cause(assertThrows(Throwable.class, - () -> await(ses.executeAsync(null, "ALTER TABLE TEST DROP COLUMN (val0, val1)"))), SqlException.class); - assertNotNull(ex); - assertEquals(STMT_VALIDATION_ERR, ex.code()); + SqlException ex = checkError( + SqlException.class, + STMT_VALIDATION_ERR, + "Can`t delete column(s).", + ses, + "ALTER TABLE TEST DROP COLUMN (val0, val1)" + ); String msg = ex.getMessage(); String explainMsg = "Unexpected error message: " + msg; @@ -165,6 +173,7 @@ public class ItSqlSynchronousApiTest extends ClusterPerClassIntegrationTest { checkError( SqlException.class, + STMT_VALIDATION_ERR, "Can`t delete column, belongs to primary key: [name=ID]", ses, "ALTER TABLE TEST DROP COLUMN id" @@ -259,9 +268,8 @@ public class ItSqlSynchronousApiTest extends ClusterPerClassIntegrationTest { } @Test - @Disabled("https://issues.apache.org/jira/browse/IGNITE-19919") public void errors() throws InterruptedException { - sql("CREATE TABLE TEST(ID INT PRIMARY KEY, VAL0 INT)"); + sql("CREATE TABLE TEST(ID INT PRIMARY KEY, VAL0 INT NOT NULL)"); for (int i = 0; i < ROW_COUNT; ++i) { sql("INSERT INTO TEST VALUES (?, ?)", i, i); } @@ -270,46 +278,42 @@ public class ItSqlSynchronousApiTest extends ClusterPerClassIntegrationTest { Session ses = sql.sessionBuilder().defaultPageSize(2).build(); // Parse error. - assertThrowsWithCause( - () -> ses.execute(null, "SELECT ID FROM"), - SqlException.class, - "Failed to parse query" - ); + checkError(SqlException.class, STMT_PARSE_ERR, "Failed to parse query", ses, "SELECT ID FROM"); - // Multiple statements error. - assertThrowsWithCause( - () -> ses.execute(null, "SELECT 1; SELECT 2"), - SqlException.class, - "Multiple statements are not allowed" - ); + // Validation errors. + checkError(SqlException.class, STMT_VALIDATION_ERR, "Column 'VAL0' does not allow NULLs", ses, + "INSERT INTO TEST VALUES (2, NULL)"); - // Planning error. - assertThrowsWithCause( - () -> ses.execute(null, "CREATE TABLE TEST2 (VAL INT)"), - SqlException.class, - "Table without PRIMARY KEY is not supported" - ); + checkError(SqlException.class, STMT_VALIDATION_ERR, "Object 'NOT_EXISTING_TABLE' not found", ses, + "SELECT * FROM NOT_EXISTING_TABLE"); + + checkError(SqlException.class, STMT_VALIDATION_ERR, "Column 'NOT_EXISTING_COLUMN' not found", ses, + "SELECT NOT_EXISTING_COLUMN FROM TEST"); + + checkError(SqlException.class, STMT_VALIDATION_ERR, "Multiple statements are not allowed", ses, "SELECT 1; SELECT 2"); + + checkError(SqlException.class, STMT_VALIDATION_ERR, "Table without PRIMARY KEY is not supported", ses, + "CREATE TABLE TEST2 (VAL INT)"); // Execute error. - assertThrowsWithCause( - () -> ses.execute(null, "SELECT 1 / ?", 0), - IgniteException.class, - "/ by zero" - ); + checkError(SqlException.class, RUNTIME_ERR, "/ by zero", ses, "SELECT 1 / ?", 0); + checkError(SqlException.class, RUNTIME_ERR, "negative substring length not allowed", ses, "SELECT SUBSTRING('foo', 1, -3)"); // No result set error. { ResultSet rs = ses.execute(null, "CREATE TABLE TEST3 (ID INT PRIMARY KEY)"); - assertThrowsWithCause(rs::next, NoRowSetExpectedException.class, "Query has no result set"); + assertThrowsPublicException(rs::next, NoRowSetExpectedException.class, QUERY_NO_RESULT_SET_ERR, "Query has no result set"); } + // TODO unmute after https://issues.apache.org/jira/browse/IGNITE-19919 // Cursor closed error. - { - ResultSet rs = ses.execute(null, "SELECT * FROM TEST"); - Thread.sleep(300); // ResultSetImpl fetches next page in background, wait to it to complete to avoid flakiness. - rs.close(); - assertThrowsWithCause(() -> rs.forEachRemaining(Object::hashCode), CursorClosedException.class); - } + // { + // ResultSet rs = ses.execute(null, "SELECT * FROM TEST"); + // Thread.sleep(300); // ResultSetImpl fetches next page in background, wait to it to complete to avoid flakiness. + // rs.close(); + // assertThrowsPublicException(() -> rs.forEachRemaining(Object::hashCode), + // CursorClosedException.class, CURSOR_CLOSED_ERR, null); + // } } /** @@ -425,11 +429,22 @@ public class ItSqlSynchronousApiTest extends ClusterPerClassIntegrationTest { res.close(); } - private static void checkError(Class<? extends Throwable> expectedException, String msg, Session ses, String sql) { - assertThrowsWithCause(() -> ses.execute(null, sql), expectedException, msg); + private static <T extends IgniteException> T checkError(Class<T> expCls, String msg, Session ses, String sql, Object... args) { + return checkError(expCls, null, msg, ses, sql, args); + } + + private static <T extends IgniteException> T checkError( + Class<T> expCls, + @Nullable Integer code, + @Nullable String msg, + Session ses, + String sql, + Object... args + ) { + return assertThrowsPublicException(() -> ses.execute(null, sql, args), expCls, code, msg); } - protected static void checkDml(int expectedAffectedRows, Session ses, String sql, Object... args) { + static void checkDml(int expectedAffectedRows, Session ses, String sql, Object... args) { ResultSet res = ses.execute( null, sql, diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java index 4d54c7911d..90920a4192 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal.sql.engine; +import static org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException; import static org.apache.ignite.lang.IgniteStringFormatter.format; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -36,6 +37,7 @@ import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.type.SqlTypeUtil; import org.apache.ignite.internal.sql.engine.util.Commons; import org.apache.ignite.internal.sql.engine.util.QueryChecker; +import org.apache.ignite.lang.ErrorGroups.Sql; import org.apache.ignite.lang.IgniteException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assumptions; @@ -124,8 +126,7 @@ public class ItDataTypesTest extends ClusterPerClassIntegrationTest { assertEquals(Set.of(101), rows.stream().map(r -> r.get(0)).collect(Collectors.toSet())); - //todo: correct exception https://issues.apache.org/jira/browse/IGNITE-16095 - assertThrows(IgniteException.class, () -> sql("INSERT INTO tbl(c1, c2) VALUES (2, NULL)")); + assertThrowsSqlException(Sql.STMT_VALIDATION_ERR, () -> sql("INSERT INTO tbl(c1, c2) VALUES (2, NULL)")); } /** 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 550638e5c7..27ed03a443 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 @@ -19,6 +19,7 @@ package org.apache.ignite.internal.sql.engine; import static org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException; import static org.apache.ignite.lang.ErrorGroups.Sql.CONSTRAINT_VIOLATION_ERR; +import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -539,11 +540,9 @@ public class ItDmlTest extends ClusterPerClassIntegrationTest { public void testCheckNullValueErrorMessageForColumnWithDefaultValue() { sql("CREATE TABLE tbl(key int DEFAULT 9 primary key, val varchar)"); - var e = assertThrows(IgniteException.class, - () -> sql("INSERT INTO tbl (key, val) VALUES (NULL,'AA')")); + var expectedMessage = "Failed to validate query. From line 1, column 28 to line 1, column 45: Column 'KEY' does not allow NULLs"; - var expectedMessage = "From line 1, column 28 to line 1, column 45: Column 'KEY' does not allow NULLs"; - assertEquals(expectedMessage, e.getMessage(), "error message"); + assertThrowsSqlException(STMT_VALIDATION_ERR, expectedMessage, () -> sql("INSERT INTO tbl (key, val) VALUES (NULL,'AA')")); } private void checkQueryResult(String sql, List<Object> expectedVals) { @@ -553,7 +552,7 @@ public class ItDmlTest extends ClusterPerClassIntegrationTest { private void checkWrongDefault(String sqlType, String sqlVal) { try { assertThrows( - IgniteException.class, + SqlException.class, () -> sql("CREATE TABLE test (val " + sqlType + " DEFAULT " + sqlVal + ")"), "Cannot convert literal" ); diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java index 7b28998f8c..357120bc94 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java @@ -17,6 +17,8 @@ package org.apache.ignite.internal.sql.engine; +import static org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException; +import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR; import static org.apache.ignite.lang.IgniteStringFormatter.format; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -35,7 +37,6 @@ import org.apache.ignite.internal.sql.engine.util.Commons; import org.apache.ignite.internal.sql.engine.util.MetadataMatcher; import org.apache.ignite.internal.sql.engine.util.SqlTestUtils; import org.apache.ignite.internal.testframework.IgniteTestUtils; -import org.apache.ignite.lang.IgniteException; import org.apache.ignite.sql.ColumnType; import org.apache.ignite.sql.SqlException; import org.junit.jupiter.api.AfterEach; @@ -159,8 +160,8 @@ public class ItDynamicParameterTest extends ClusterPerClassIntegrationTest { */ @Test public void testWithDifferentParametersTypesMismatch() { - assertThrows(IgniteException.class, () -> assertQuery("SELECT COALESCE(12.2, ?)").withParams("b").check()); - assertThrows(IgniteException.class, () -> assertQuery("SELECT COALESCE(?, ?)").withParams(12.2, "b").check()); + assertThrowsSqlException(STMT_VALIDATION_ERR, () -> assertQuery("SELECT COALESCE(12.2, ?)").withParams("b").check()); + assertThrowsSqlException(STMT_VALIDATION_ERR, () -> assertQuery("SELECT COALESCE(?, ?)").withParams(12.2, "b").check()); } @Test diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItLimitOffsetTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItLimitOffsetTest.java index 08676a86af..eecebe80d1 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItLimitOffsetTest.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItLimitOffsetTest.java @@ -17,15 +17,14 @@ package org.apache.ignite.internal.sql.engine; +import static org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.util.Arrays; import java.util.List; import org.apache.ignite.internal.sql.engine.util.Commons; -import org.apache.ignite.lang.IgniteException; +import org.apache.ignite.lang.ErrorGroups.Sql; import org.apache.ignite.sql.Session; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -52,35 +51,33 @@ public class ItLimitOffsetTest extends ClusterPerClassIntegrationTest { String bigInt = BigDecimal.valueOf(10000000000L).toString(); - //todo: correct exception https://issues.apache.org/jira/browse/IGNITE-16095, here and all checks near. - IgniteException ret = assertThrows(IgniteException.class, () - -> session.execute(null, "SELECT * FROM test OFFSET " + bigInt + " ROWS")); - assertTrue(ret.getMessage().contains("Illegal value of offset")); + assertThrowsSqlException(Sql.STMT_VALIDATION_ERR, "Illegal value of offset", + () -> session.execute(null, "SELECT * FROM test OFFSET " + bigInt + " ROWS")); - ret = assertThrows(IgniteException.class, + assertThrowsSqlException(Sql.STMT_VALIDATION_ERR, "Illegal value of fetch / limit", () -> session.execute(null, "SELECT * FROM test FETCH FIRST " + bigInt + " ROWS ONLY")); - assertTrue(ret.getMessage().contains("Illegal value of fetch / limit")); - ret = assertThrows(IgniteException.class, () -> session.execute(null, "SELECT * FROM test LIMIT " + bigInt)); - assertTrue(ret.getMessage().contains("Illegal value of fetch / limit")); + assertThrowsSqlException(Sql.STMT_VALIDATION_ERR, "Illegal value of fetch / limit", + () -> session.execute(null, "SELECT * FROM test LIMIT " + bigInt)); - assertThrows(IgniteException.class, () -> session.execute(null, "SELECT * FROM test OFFSET -1 ROWS " - + "FETCH FIRST -1 ROWS ONLY")); + assertThrowsSqlException(Sql.STMT_PARSE_ERR, + () -> session.execute(null, "SELECT * FROM test OFFSET -1 ROWS FETCH FIRST -1 ROWS ONLY")); - assertThrows(IgniteException.class, () -> session.execute(null, "SELECT * FROM test OFFSET -1 ROWS")); + assertThrowsSqlException(Sql.STMT_PARSE_ERR, + () -> session.execute(null, "SELECT * FROM test OFFSET -1 ROWS")); - assertThrows(IgniteException.class, () -> session.execute(null, "SELECT * FROM test OFFSET 2+1 ROWS")); + assertThrowsSqlException(Sql.STMT_PARSE_ERR, + () -> session.execute(null, "SELECT * FROM test OFFSET 2+1 ROWS")); // Check with parameters - ret = assertThrows(IgniteException.class, () -> session.execute(null, "SELECT * FROM test OFFSET ? " - + "ROWS FETCH FIRST ? ROWS ONLY", -1, -1)); - assertTrue(ret.getMessage().contains("Illegal value of fetch / limit")); + assertThrowsSqlException(Sql.STMT_VALIDATION_ERR, "Illegal value of fetch / limit", + () -> session.execute(null, "SELECT * FROM test OFFSET ? ROWS FETCH FIRST ? ROWS ONLY", -1, -1)); - ret = assertThrows(IgniteException.class, () -> session.execute(null, "SELECT * FROM test OFFSET ? ROWS", -1)); - assertTrue(ret.getMessage().contains("Illegal value of offset")); + assertThrowsSqlException(Sql.STMT_VALIDATION_ERR, "Illegal value of offset", + () -> session.execute(null, "SELECT * FROM test OFFSET ? ROWS", -1)); - ret = assertThrows(IgniteException.class, () -> session.execute(null, "SELECT * FROM test FETCH FIRST ? ROWS ONLY", -1)); - assertTrue(ret.getMessage().contains("Illegal value of fetch / limit")); + assertThrowsSqlException(Sql.STMT_VALIDATION_ERR, "Illegal value of fetch / limit", + () -> session.execute(null, "SELECT * FROM test FETCH FIRST ? ROWS ONLY", -1)); } /** diff --git a/modules/sql-engine/build.gradle b/modules/sql-engine/build.gradle index f181612f2a..20da54336e 100644 --- a/modules/sql-engine/build.gradle +++ b/modules/sql-engine/build.gradle @@ -56,9 +56,11 @@ dependencies { } implementation libs.classgraph implementation libs.javassist + implementation libs.auto.service.annotations annotationProcessor project(':ignite-network-annotation-processor') annotationProcessor libs.value.annotation.processor + annotationProcessor libs.auto.service testAnnotationProcessor project(':ignite-network-annotation-processor') testAnnotationProcessor libs.jmh.annotation.processor diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionImpl.java index 0b60c97e9b..638ad0495b 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionImpl.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionImpl.java @@ -20,6 +20,7 @@ package org.apache.ignite.internal.sql.api; import static org.apache.ignite.internal.util.ExceptionUtils.sneakyThrow; import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR; import static org.apache.ignite.lang.ErrorGroups.Sql.SESSION_CLOSED_ERR; +import static org.apache.ignite.lang.IgniteExceptionMapperUtil.mapToPublicException; import it.unimi.dsi.fastutil.longs.LongArrayList; import java.util.ArrayList; @@ -29,6 +30,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Flow.Publisher; import java.util.concurrent.TimeUnit; @@ -47,8 +49,8 @@ import org.apache.ignite.internal.sql.engine.session.SessionProperty; import org.apache.ignite.internal.util.ArrayUtils; import org.apache.ignite.internal.util.ExceptionUtils; import org.apache.ignite.internal.util.IgniteSpinBusyLock; -import org.apache.ignite.lang.IgniteException; import org.apache.ignite.lang.IgniteInternalException; +import org.apache.ignite.lang.TraceableException; import org.apache.ignite.sql.BatchedArguments; import org.apache.ignite.sql.SqlBatchException; import org.apache.ignite.sql.SqlException; @@ -167,13 +169,15 @@ public class SessionImpl implements AbstractSession { return CompletableFuture.failedFuture(new SqlException(SESSION_CLOSED_ERR, "Session is closed.")); } + CompletableFuture<AsyncResultSet<SqlRow>> result; + try { QueryContext ctx = QueryContext.create(SqlQueryType.ALL, transaction); - CompletableFuture<AsyncResultSet<SqlRow>> result = qryProc.querySingleAsync(sessionId, ctx, query, arguments) + result = qryProc.querySingleAsync(sessionId, ctx, query, arguments) .thenCompose(cur -> cur.requestNextAsync(pageSize) .thenApply( - batchRes -> new AsyncResultSetImpl( + batchRes -> new AsyncResultSetImpl<>( cur, batchRes, pageSize, @@ -181,19 +185,22 @@ public class SessionImpl implements AbstractSession { ) ) ); - - result.whenComplete((rs, th) -> { - if (th instanceof SessionNotFoundException) { - closeInternal(); - } - }); - - return result; } catch (Exception e) { - return CompletableFuture.failedFuture(e); + return CompletableFuture.failedFuture(mapToPublicException(e)); } finally { busyLock.leaveBusy(); } + + // Closing a session must be done outside of the lock. + return result.exceptionally((th) -> { + Throwable cause = ExceptionUtils.unwrapCause(th); + + if (cause instanceof SessionNotFoundException) { + closeInternal(); + } + + throw new CompletionException(mapToPublicException(cause)); + }); } /** {@inheritDoc} */ @@ -253,7 +260,7 @@ public class SessionImpl implements AbstractSession { counters.add((long) page.items().get(0).get(0)); }) .whenComplete((v, ex) -> { - if (ex instanceof CancellationException) { + if (ExceptionUtils.unwrapCause(ex) instanceof CancellationException) { qryFut.cancel(false); } }); @@ -265,22 +272,34 @@ public class SessionImpl implements AbstractSession { .exceptionally((ex) -> { Throwable cause = ExceptionUtils.unwrapCause(ex); - throw new SqlBatchException( - cause instanceof IgniteException ? ((IgniteException) cause).code() : INTERNAL_ERR, - counters.toArray(ArrayUtils.LONG_EMPTY_ARRAY), - ex); + if (cause instanceof CancellationException) { + throw (CancellationException) cause; + } + + Throwable t = mapToPublicException(cause); + + if (t instanceof TraceableException) { + throw new SqlBatchException( + ((TraceableException) t).traceId(), + ((TraceableException) t).code(), + counters.toArray(ArrayUtils.LONG_EMPTY_ARRAY), + t); + } + + // JVM error. + throw new CompletionException(cause); }) .thenApply(v -> counters.toArray(ArrayUtils.LONG_EMPTY_ARRAY)); resFut.whenComplete((cur, ex) -> { - if (ex instanceof CancellationException) { + if (ExceptionUtils.unwrapCause(ex) instanceof CancellationException) { batchFuts.forEach(f -> f.cancel(false)); } }); return resFut; } catch (Exception e) { - return CompletableFuture.failedFuture(e); + return CompletableFuture.failedFuture(mapToPublicException(e)); } finally { busyLock.leaveBusy(); } @@ -326,7 +345,7 @@ public class SessionImpl implements AbstractSession { @Override public void close() { try { - closeAsync().toCompletableFuture().get(); + closeAsync().get(); } catch (ExecutionException e) { sneakyThrow(ExceptionUtils.copyExceptionWithCause(e)); } catch (InterruptedException e) { @@ -337,9 +356,13 @@ public class SessionImpl implements AbstractSession { /** {@inheritDoc} */ @Override public CompletableFuture<Void> closeAsync() { - closeInternal(); + try { + closeInternal(); - return qryProc.closeSession(sessionId); + return qryProc.closeSession(sessionId); + } catch (Exception e) { + return CompletableFuture.failedFuture(mapToPublicException(e)); + } } /** {@inheritDoc} */ diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/AsyncSqlCursorImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/AsyncSqlCursorImpl.java index 3efd4b14f4..2be580ace8 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/AsyncSqlCursorImpl.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/AsyncSqlCursorImpl.java @@ -18,11 +18,11 @@ package org.apache.ignite.internal.sql.engine; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import org.apache.ignite.internal.tx.InternalTransaction; import org.apache.ignite.internal.util.ExceptionUtils; -import org.apache.ignite.lang.IgniteInternalException; +import org.apache.ignite.lang.IgniteExceptionMapperUtil; import org.apache.ignite.sql.ResultSetMetadata; -import org.apache.ignite.sql.SqlException; import org.jetbrains.annotations.Nullable; /** @@ -76,7 +76,7 @@ public class AsyncSqlCursorImpl<T> implements AsyncSqlCursor<T> { implicitTx.rollback(); } - throw wrapIfNecessary(t); + throw new CompletionException(wrapIfNecessary(t)); } if (implicitTx != null && !batch.hasMore()) { @@ -94,16 +94,9 @@ public class AsyncSqlCursorImpl<T> implements AsyncSqlCursor<T> { return dataCursor.closeAsync(); } - private static RuntimeException wrapIfNecessary(Throwable t) { + private static Throwable wrapIfNecessary(Throwable t) { Throwable err = ExceptionUtils.unwrapCause(t); - if (err instanceof IgniteInternalException) { - IgniteInternalException iex = (IgniteInternalException) err; - - return new SqlException(iex.traceId(), iex.code(), iex.getMessage(), iex); - } - - // TODO https://issues.apache.org/jira/browse/IGNITE-19539 - return ExceptionUtils.wrap(t); + return IgniteExceptionMapperUtil.mapToPublicException(err); } } diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExchangeServiceImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExchangeServiceImpl.java index 996c082e37..c616389368 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExchangeServiceImpl.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExchangeServiceImpl.java @@ -35,11 +35,8 @@ import org.apache.ignite.internal.sql.engine.message.SqlQueryMessageGroup; import org.apache.ignite.internal.sql.engine.message.SqlQueryMessagesFactory; import org.apache.ignite.internal.sql.engine.util.Commons; import org.apache.ignite.internal.util.ExceptionUtils; -import org.apache.ignite.lang.IgniteException; -import org.apache.ignite.lang.IgniteInternalCheckedException; import org.apache.ignite.lang.IgniteInternalException; -import org.apache.ignite.sql.SqlException; -import org.jetbrains.annotations.NotNull; +import org.apache.ignite.lang.TraceableException; import org.jetbrains.annotations.Nullable; /** @@ -111,12 +108,16 @@ public class ExchangeServiceImpl implements ExchangeService { /** {@inheritDoc} */ @Override public CompletableFuture<Void> sendError(String nodeName, UUID queryId, long fragmentId, Throwable error) { - IgniteException errorWithCode = wrapIfNecessary(error); + Throwable traceableErr = ExceptionUtils.unwrapCause(error); - if (!(error instanceof ExecutionCancelledException)) { - LOG.info(format("Failed to execute query fragment: queryId={}, fragmentId={}", queryId, fragmentId), errorWithCode); + if (!(traceableErr instanceof TraceableException)) { + traceableErr = error = new IgniteInternalException(INTERNAL_ERR, error); + } + + if (!(traceableErr instanceof ExecutionCancelledException)) { + LOG.info(format("Failed to execute query fragment: queryId={}, fragmentId={}", queryId, fragmentId), error); } else if (LOG.isDebugEnabled()) { - LOG.debug(format("Failed to execute query fragment: queryId={}, fragmentId={}", queryId, fragmentId), errorWithCode); + LOG.debug(format("Failed to execute query fragment: queryId={}, fragmentId={}", queryId, fragmentId), error); } return messageService.send( @@ -124,32 +125,13 @@ public class ExchangeServiceImpl implements ExchangeService { FACTORY.errorMessage() .queryId(queryId) .fragmentId(fragmentId) - .traceId(errorWithCode.traceId()) - .code(errorWithCode.code()) - .message(errorWithCode.getMessage()) + .traceId(((TraceableException) traceableErr).traceId()) + .code(((TraceableException) traceableErr).code()) + .message(traceableErr.getMessage()) .build() ); } - // TODO https://issues.apache.org/jira/browse/IGNITE-19539 - private static IgniteException wrapIfNecessary(@NotNull Throwable t) { - Throwable cause = ExceptionUtils.unwrapCause(t); - - if (cause instanceof IgniteException) { - return cause == t ? (IgniteException) cause : ExceptionUtils.wrap(t); - } else if (cause instanceof IgniteInternalException) { - IgniteInternalException iex = (IgniteInternalException) cause; - - return new SqlException(iex.traceId(), iex.code(), iex.getMessage(), iex); - } else if (cause instanceof IgniteInternalCheckedException) { - IgniteInternalCheckedException iex = (IgniteInternalCheckedException) cause; - - return new SqlException(iex.traceId(), iex.code(), iex.getMessage(), iex); - } else { - return new SqlException(INTERNAL_ERR, cause); - } - } - private void onMessage(String nodeName, QueryBatchRequestMessage msg) { CompletableFuture<Outbox<?>> outboxFut = mailboxRegistry.outbox(msg.queryId(), msg.exchangeId()); 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 37161f950c..2b7e66ff7b 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 @@ -80,6 +80,8 @@ import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory; import org.apache.ignite.internal.sql.engine.util.Commons; import org.apache.ignite.internal.sql.engine.util.IgniteMethod; import org.apache.ignite.internal.sql.engine.util.Primitives; +import org.apache.ignite.lang.ErrorGroups.Sql; +import org.apache.ignite.sql.SqlException; import org.jetbrains.annotations.Nullable; /** @@ -517,18 +519,25 @@ public class ExpressionFactoryImpl<RowT> implements ExpressionFactory<RowT> { assert nodes.size() == projects.size(); + BlockBuilder tryCatchBlock = new BlockBuilder(); + for (int i = 0; i < projects.size(); i++) { Expression val = unspecifiedValues.get(i) ? Expressions.field(null, ExpressionFactoryImpl.class, "UNSPECIFIED_VALUE_PLACEHOLDER") : projects.get(i); - builder.add( - Expressions.statement( - Expressions.call(hnd, - IgniteMethod.ROW_HANDLER_SET.method(), - Expressions.constant(i), out, val))); + tryCatchBlock.add( + Expressions.statement( + Expressions.call(hnd, + IgniteMethod.ROW_HANDLER_SET.method(), + Expressions.constant(i), out, val))); } + ParameterExpression ex = Expressions.parameter(0, Exception.class, "e"); + Expression sqlException = Expressions.new_(SqlException.class, Expressions.constant(Sql.RUNTIME_ERR), ex); + + builder.add(Expressions.tryCatch(tryCatchBlock.toBlock(), Expressions.catch_(ex, Expressions.throw_(sqlException)))); + String methodName = biInParams ? IgniteMethod.BI_SCALAR_EXECUTE.method().getName() : IgniteMethod.SCALAR_EXECUTE.method().getName(); diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/RootNode.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/RootNode.java index ce2dec9e20..1d656f92b9 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/RootNode.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/RootNode.java @@ -18,7 +18,6 @@ package org.apache.ignite.internal.sql.engine.exec.rel; import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty; -import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR; import static org.apache.ignite.lang.ErrorGroups.Sql.EXECUTION_CANCELLED_ERR; import com.google.common.base.Functions; @@ -34,7 +33,7 @@ import java.util.function.Function; import org.apache.ignite.internal.sql.engine.exec.ExecutionCancelledException; import org.apache.ignite.internal.sql.engine.exec.ExecutionContext; import org.apache.ignite.internal.sql.engine.util.Commons; -import org.apache.ignite.lang.IgniteInternalException; +import org.apache.ignite.internal.util.ExceptionUtils; import org.apache.ignite.sql.SqlException; /** @@ -284,15 +283,6 @@ public class RootNode<RowT> extends AbstractNode<RowT> implements SingleNode<Row return; } - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } else { - throw new IgniteInternalException(INTERNAL_ERR, "An error occurred while query executing.", e); - } - // TODO: rework with SQL error code - // if (e instanceof IgniteSQLException) - // throw (IgniteSQLException)e; - // else - // throw new IgniteSQLException("An error occurred while query executing.", IgniteQueryErrorCode.UNKNOWN, e); + ExceptionUtils.sneakyThrow(e); } } diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java index fd58fed92e..0708443984 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java @@ -220,7 +220,6 @@ public class IgniteSqlValidator extends SqlValidatorImpl { final SqlValidatorTable table = getCatalogReader().getTable(targetTable.names); if (table == null) { - // TODO IGNITE-14865 Calcite exception should be converted/wrapped into a public ignite exception. throw newValidationError(call.getTargetTable(), RESOURCE.objectNotFound(targetTable.toString())); } @@ -267,7 +266,6 @@ public class IgniteSqlValidator extends SqlValidatorImpl { final SqlValidatorTable table = getCatalogReader().getTable(targetTable.names); if (table == null) { - // TODO IGNITE-14865 Calcite exception should be converted/wrapped into a public ignite exception. throw newValidationError(targetTable, RESOURCE.objectNotFound(targetTable.toString())); } diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java index 81b58c958f..12ccee6fdd 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java @@ -21,13 +21,13 @@ import static org.apache.ignite.internal.sql.engine.prepare.CacheKey.EMPTY_CLASS import static org.apache.ignite.internal.sql.engine.prepare.PlannerHelper.optimize; import static org.apache.ignite.internal.sql.engine.trait.TraitUtils.distributionPresent; import static org.apache.ignite.lang.ErrorGroups.Sql.PLANNING_TIMEOUT_ERR; -import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR; 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.CompletionException; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -35,7 +35,6 @@ import org.apache.calcite.plan.RelOptPlanner; import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeField; -import org.apache.calcite.runtime.CalciteContextException; import org.apache.calcite.sql.SqlDdl; import org.apache.calcite.sql.SqlExplain; import org.apache.calcite.sql.SqlExplainLevel; @@ -59,7 +58,7 @@ import org.apache.ignite.internal.sql.metrics.SqlPlanCacheMetricSource; import org.apache.ignite.internal.storage.DataStorageManager; import org.apache.ignite.internal.thread.NamedThreadFactory; import org.apache.ignite.internal.util.ExceptionUtils; -import org.apache.ignite.lang.IgniteException; +import org.apache.ignite.lang.IgniteExceptionMapperUtil; import org.apache.ignite.sql.ColumnMetadata; import org.apache.ignite.sql.ColumnType; import org.apache.ignite.sql.ResultSetMetadata; @@ -192,27 +191,23 @@ public class PrepareServiceImpl implements PrepareService, SchemaUpdateListener throw new SqlException(PLANNING_TIMEOUT_ERR); } - throw new IgniteException(th); + throw new CompletionException(IgniteExceptionMapperUtil.mapToPublicException(th)); } ); } private CompletableFuture<QueryPlan> prepareAsync0(ParsedResult parsedResult, PlanningContext planningContext) { - try { - switch (parsedResult.queryType()) { - case QUERY: - return prepareQuery(parsedResult, planningContext); - case DDL: - return prepareDdl(parsedResult, planningContext); - case DML: - return prepareDml(parsedResult, planningContext); - case EXPLAIN: - return prepareExplain(parsedResult, planningContext); - default: - throw new AssertionError("Unexpected queryType=" + parsedResult.queryType()); - } - } catch (CalciteContextException e) { - throw new SqlException(STMT_VALIDATION_ERR, "Failed to validate query. " + e.getMessage(), e); + switch (parsedResult.queryType()) { + case QUERY: + return prepareQuery(parsedResult, planningContext); + case DDL: + return prepareDdl(parsedResult, planningContext); + case DML: + return prepareDml(parsedResult, planningContext); + case EXPLAIN: + return prepareExplain(parsedResult, planningContext); + default: + throw new AssertionError("Unexpected queryType=" + parsedResult.queryType()); } } diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/SqlExceptionMapperProvider.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/SqlExceptionMapperProvider.java new file mode 100644 index 0000000000..277ac5781a --- /dev/null +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/SqlExceptionMapperProvider.java @@ -0,0 +1,63 @@ +/* + * 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.sql.engine.util; + +import static org.apache.ignite.lang.ErrorGroups.Sql.SQL_ERR_GROUP; +import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR; +import static org.apache.ignite.lang.IgniteExceptionMapper.unchecked; + +import com.google.auto.service.AutoService; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.apache.calcite.runtime.CalciteContextException; +import org.apache.ignite.internal.sql.engine.exec.ExecutionCancelledException; +import org.apache.ignite.internal.sql.engine.exec.QueryValidationException; +import org.apache.ignite.internal.sql.engine.metadata.RemoteFragmentExecutionException; +import org.apache.ignite.lang.IgniteException; +import org.apache.ignite.lang.IgniteExceptionMapper; +import org.apache.ignite.lang.IgniteExceptionMappersProvider; +import org.apache.ignite.sql.SqlException; + +/** + * SQL module exception mapper. + */ +@AutoService(IgniteExceptionMappersProvider.class) +public class SqlExceptionMapperProvider implements IgniteExceptionMappersProvider { + @Override + public Collection<IgniteExceptionMapper<?, ?>> mappers() { + List<IgniteExceptionMapper<?, ?>> mappers = new ArrayList<>(); + + mappers.add(unchecked(ExecutionCancelledException.class, err -> new SqlException(err.traceId(), err.code(), err))); + + mappers.add(unchecked(QueryValidationException.class, err -> new SqlException(err.traceId(), err.code(), err))); + + mappers.add(unchecked(RemoteFragmentExecutionException.class, err -> { + if (err.groupCode() == SQL_ERR_GROUP.groupCode()) { + return new SqlException(err.traceId(), err.code(), err.getMessage(), err); + } + + return new IgniteException(err.traceId(), err.code(), err.getMessage(), err); + })); + + mappers.add(unchecked(CalciteContextException.class, + err -> new SqlException(STMT_VALIDATION_ERR, "Failed to validate query. " + err.getMessage(), err))); + + return mappers; + } +} diff --git a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java index 1808763a72..36cd0e9ba0 100644 --- a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java +++ b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java @@ -18,7 +18,9 @@ package org.apache.ignite.internal.sql.engine.util; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.math.BigInteger; @@ -60,6 +62,26 @@ public class SqlTestUtils { return ex; } + /** + * <em>Assert</em> that execution of the supplied {@code executable} throws + * an {@link SqlException} with expected error code and message. + * + * @param expectedCode Expected error code of {@link SqlException}. + * @param expectedMessage Expected error message of {@link SqlException}. + * @param executable Supplier to execute and check thrown exception. + * @return Thrown the {@link SqlException}. + */ + public static SqlException assertThrowsSqlException(int expectedCode, String expectedMessage, Executable executable) { + SqlException ex = assertThrowsSqlException(expectedCode, executable); + + String msg = ex.getMessage(); + + assertNotNull(msg, "Error message was null, but expected '" + expectedMessage + "'."); + assertTrue(msg.contains(expectedMessage), "Error message '" + ex.getMessage() + "' doesn't contain '" + expectedMessage + "'."); + + return ex; + } + /** * Convert {@link ColumnType} to string representation of SQL type. *