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

Reply via email to