This is an automated email from the ASF dual-hosted git repository.

korlov 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 91fd5e2808 IGNITE-18653: Sql. The number of dynamic parameters can be 
checked once (#1732)
91fd5e2808 is described below

commit 91fd5e2808c34409dcc00ba39ace5a19e9ab58ec
Author: Max Zhuravkov <[email protected]>
AuthorDate: Tue Mar 21 15:09:15 2023 +0400

    IGNITE-18653: Sql. The number of dynamic parameters can be checked once 
(#1732)
    
    
    Co-authored-by: korlov42 <[email protected]>
    Co-authored-by: ygerzhedovich 
<[email protected]>
---
 .../internal/cli/CliIntegrationTestBase.java       |   3 +-
 .../client/handler/JdbcQueryEventHandlerImpl.java  |  54 ++++---
 .../apache/ignite/jdbc/ItJdbcBatchSelfTest.java    |  36 +++--
 .../internal/sql/api/ItSqlAsynchronousApiTest.java |   4 +-
 .../internal/sql/api/ItSqlSynchronousApiTest.java  |   4 +-
 .../sql/engine/ClusterPerClassIntegrationTest.java |   2 +-
 .../sql/engine/ItDynamicParameterTest.java         |   3 +-
 .../ignite/internal/sql/engine/ItUuidTest.java     |   2 +-
 .../internal/sql/engine/util/QueryChecker.java     |   3 +-
 .../ignite/internal/sql/api/SessionImpl.java       |  18 +--
 .../ignite/internal/sql/engine/QueryContext.java   |  80 +++++++---
 .../internal/sql/engine/SqlQueryProcessor.java     |  87 ++++++++---
 .../ignite/internal/sql/engine/SqlQueryType.java   |  24 +--
 .../sql/engine/exec/ExecutionServiceImpl.java      |   8 +-
 .../internal/sql/engine/prepare/DdlPlan.java       |   5 +-
 .../internal/sql/engine/prepare/ExplainPlan.java   |   5 +-
 .../internal/sql/engine/prepare/FragmentPlan.java  |   6 +-
 .../internal/sql/engine/prepare/IgnitePlanner.java |  21 ++-
 .../sql/engine/prepare/IgniteSqlValidator.java     |  87 +++--------
 .../sql/engine/prepare/MultiStepDmlPlan.java       |   5 +-
 .../sql/engine/prepare/MultiStepQueryPlan.java     |   5 +-
 .../sql/engine/prepare/PrepareServiceImpl.java     |  34 ++---
 .../internal/sql/engine/prepare/QueryPlan.java     |  11 +-
 .../internal/sql/engine/sql/IgniteSqlParser.java   | 162 +++++++++++++++++++++
 .../ignite/internal/sql/engine/sql/ParseMode.java  |  48 ++++++
 .../{QueryValidator.java => sql/ParseResult.java}  |  34 +++--
 .../internal/sql/engine/sql/ScriptParseResult.java |  63 ++++++++
 .../sql/engine/sql/StatementParseResult.java       |  78 ++++++++++
 .../ignite/internal/sql/engine/util/Commons.java   |  93 ++++++------
 .../internal/sql/engine/util/IgniteResource.java   |   3 -
 .../sql/engine/benchmarks/TpchParseBenchmark.java  |   6 +-
 .../engine/benchmarks/TpchPrepareBenchmark.java    |   7 +-
 .../sql/engine/exec/ExecutionServiceImplTest.java  |  12 +-
 .../internal/sql/engine/framework/TestNode.java    |  12 +-
 .../sql/engine/sql/AbstractDdlParserTest.java      |  12 +-
 .../sql/DistributionZoneSqlDdlParserTest.java      |  38 ++---
 .../engine/sql/IgniteSqlDecimalLiteralTest.java    |  11 +-
 .../sql/engine/sql/IgniteSqlParserTest.java        |  62 ++++++++
 .../internal/sql/engine/sql/SqlDdlParserTest.java  |  50 +++----
 39 files changed, 837 insertions(+), 361 deletions(-)

diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/CliIntegrationTestBase.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/CliIntegrationTestBase.java
index 0db0d1e8e6..e570676d3c 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/CliIntegrationTestBase.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/CliIntegrationTestBase.java
@@ -32,6 +32,7 @@ import org.apache.ignite.internal.sql.engine.AsyncCursor;
 import org.apache.ignite.internal.sql.engine.AsyncCursor.BatchedResult;
 import org.apache.ignite.internal.sql.engine.QueryContext;
 import org.apache.ignite.internal.sql.engine.QueryProperty;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.property.PropertiesHolder;
 import org.apache.ignite.internal.sql.engine.session.SessionId;
 import org.apache.ignite.internal.testframework.IntegrationTestBase;
@@ -106,7 +107,7 @@ public class CliIntegrationTestBase extends 
IntegrationTestBase {
         ));
 
         try {
-            var context = tx != null ? QueryContext.of(tx) : QueryContext.of();
+            var context = QueryContext.create(SqlQueryType.ALL, tx);
 
             return getAllFromCursor(
                     await(queryEngine.querySingleAsync(sessionId, context, 
sql, args))
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 36ec19d982..9426036913 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
@@ -28,6 +28,7 @@ import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.stream.Collectors;
@@ -53,11 +54,10 @@ import org.apache.ignite.internal.jdbc.proto.event.Response;
 import org.apache.ignite.internal.sql.engine.AsyncSqlCursor;
 import org.apache.ignite.internal.sql.engine.QueryContext;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
-import org.apache.ignite.internal.sql.engine.QueryValidator;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.exec.QueryValidationException;
-import org.apache.ignite.internal.sql.engine.prepare.QueryPlan;
-import org.apache.ignite.internal.sql.engine.prepare.QueryPlan.Type;
 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.ColumnType;
@@ -67,6 +67,13 @@ import org.apache.ignite.sql.ResultSetMetadata;
  * Jdbc query event handler implementation.
  */
 public class JdbcQueryEventHandlerImpl implements JdbcQueryEventHandler {
+
+    /** {@link SqlQueryType}s allowed in JDBC select statements. **/
+    private static final Set<SqlQueryType> SELECT_STATEMENT_QUERIES = 
Set.of(SqlQueryType.QUERY, SqlQueryType.EXPLAIN);
+
+    /** {@link SqlQueryType}s allowed in JDBC update statements. **/
+    private static final Set<SqlQueryType> UPDATE_STATEMENT_QUERIES = 
Set.of(SqlQueryType.DML, SqlQueryType.DDL);
+
     /** Sql query processor. */
     private final QueryProcessor processor;
 
@@ -142,24 +149,16 @@ public class JdbcQueryEventHandlerImpl implements 
JdbcQueryEventHandler {
     }
 
     private QueryContext createQueryContext(JdbcStatementType stmtType) {
-        if (stmtType == JdbcStatementType.ANY_STATEMENT_TYPE) {
-            return QueryContext.of();
+        switch (stmtType) {
+            case ANY_STATEMENT_TYPE:
+                return QueryContext.create(SqlQueryType.ALL);
+            case SELECT_STATEMENT_TYPE:
+                return QueryContext.create(SELECT_STATEMENT_QUERIES);
+            case UPDATE_STATEMENT_TYPE:
+                return QueryContext.create(UPDATE_STATEMENT_QUERIES);
+            default:
+                throw new AssertionError("Unexpected jdbc statement type: " + 
stmtType);
         }
-
-        QueryValidator validator = (QueryPlan plan) -> {
-            if (plan.type() == Type.QUERY || plan.type() == Type.EXPLAIN) {
-                if (stmtType == JdbcStatementType.SELECT_STATEMENT_TYPE) {
-                    return;
-                }
-                throw new QueryValidationException("Given statement type does 
not match that declared by JDBC driver.");
-            }
-            if (stmtType == JdbcStatementType.UPDATE_STATEMENT_TYPE) {
-                return;
-            }
-            throw new QueryValidationException("Given statement type does not 
match that declared by JDBC driver.");
-        };
-
-        return QueryContext.of(validator);
     }
 
     /** {@inheritDoc} */
@@ -272,12 +271,19 @@ public class JdbcQueryEventHandlerImpl implements 
JdbcQueryEventHandler {
      * @return StringWriter filled with exception.
      */
     private StringWriter getWriterWithStackTrace(Throwable t) {
-        String message = ExceptionUtils.unwrapCause(t).getMessage();
+        Throwable cause = ExceptionUtils.unwrapCause(t);
         StringWriter sw = new StringWriter();
-        PrintWriter pw = new PrintWriter(sw);
 
-        pw.print(message);
-        return sw;
+        try (PrintWriter pw = new PrintWriter(sw)) {
+            // We need to remap QueryValidationException into a jdbc error.
+            if (cause instanceof IgniteException && cause.getCause() 
instanceof QueryValidationException) {
+                pw.print("Given statement type does not match that declared by 
JDBC driver.");
+            } else {
+                pw.print(cause.getMessage());
+            }
+
+            return sw;
+        }
     }
 
     /**
diff --git 
a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcBatchSelfTest.java
 
b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcBatchSelfTest.java
index 54fa194843..2dc6a7c588 100644
--- 
a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcBatchSelfTest.java
+++ 
b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcBatchSelfTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.jdbc;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -158,11 +160,31 @@ public class ItJdbcBatchSelfTest extends 
AbstractJdbcSelfTest {
 
             assertEquals(0, updCnts.length, "Invalid update counts size");
 
-            if (!e.getMessage().contains("Unexpected number of query 
parameters. Provided 1 but there is only 0 dynamic parameter(s).")) {
-                log.error("Invalid exception: ", e);
+            assertThat(e.getMessage(), containsString("Given statement type 
does not match that declared by JDBC driver"));
 
-                fail();
-            }
+            assertEquals(SqlStateCode.INTERNAL_ERROR, e.getSQLState(), 
"Invalid SQL state.");
+            assertEquals(IgniteQueryErrorCode.UNKNOWN, e.getErrorCode(), 
"Invalid error code.");
+        }
+    }
+
+    @Test
+    public void 
testPreparedStatementSupplyParametersToQueryWithoutParameters() throws 
Exception {
+        PreparedStatement preparedStatement = conn.prepareStatement("UPDATE 
Person SET firstName='NONE'");
+
+        preparedStatement.setString(1, "broken");
+        preparedStatement.addBatch();
+
+        try {
+            preparedStatement.executeBatch();
+
+            fail("BatchUpdateException must be thrown");
+        } catch (BatchUpdateException e) {
+            int[] updCnts = e.getUpdateCounts();
+
+            assertEquals(0, updCnts.length, "Invalid update counts size");
+
+            assertThat(e.getMessage(),
+                    containsString("Unexpected number of query parameters. 
Provided 1 but there is only 0 dynamic parameter(s)."));
 
             assertEquals(SqlStateCode.INTERNAL_ERROR, e.getSQLState(), 
"Invalid SQL state.");
             assertEquals(IgniteQueryErrorCode.UNKNOWN, e.getErrorCode(), 
"Invalid error code.");
@@ -196,11 +218,7 @@ public class ItJdbcBatchSelfTest extends 
AbstractJdbcSelfTest {
                 assertEquals(i + 1, updCnts[i], "Invalid update count");
             }
 
-            if (!e.getMessage().contains("Given statement type does not match 
that declared by JDBC driver")) {
-                log.error("Invalid exception: ", e);
-
-                fail();
-            }
+            assertThat(e.getMessage(), containsString("Given statement type 
does not match that declared by JDBC driver"));
 
             assertEquals(SqlStateCode.INTERNAL_ERROR, e.getSQLState(), 
"Invalid SQL state.");
             assertEquals(IgniteQueryErrorCode.UNKNOWN, e.getErrorCode(), 
"Invalid error code.");
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 f8b6cb8907..1940806887 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
@@ -571,7 +571,7 @@ public class ItSqlAsynchronousApiTest extends 
ClusterPerClassIntegrationTest {
 
         // Multiple statements error.
         assertThrowsWithCause(() -> await(ses.executeAsync(null, "SELECT 1; 
SELECT 2")),
-                SqlException.class, "Multiple statements aren't allowed");
+                SqlException.class, "Multiple statements are not allowed");
 
         // Planning error.
         assertThrowsWithCause(() -> await(ses.executeAsync(null, "CREATE TABLE 
TEST2 (VAL INT)")),
@@ -650,7 +650,7 @@ public class ItSqlAsynchronousApiTest extends 
ClusterPerClassIntegrationTest {
         assertThrowsWithCause(
                 () -> await(ses.executeBatchAsync(null, "SELECT * FROM TEST", 
args)),
                 SqlException.class,
-                "Unexpected number of query parameters. Provided 2 but there 
is only 0 dynamic parameter(s)"
+                "Invalid SQL statement type in the batch"
         );
 
         assertThrowsWithCause(
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 a3b492a560..fcc37bae01 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
@@ -246,7 +246,7 @@ public class ItSqlSynchronousApiTest extends 
ClusterPerClassIntegrationTest {
         assertThrowsWithCause(
                 () -> ses.execute(null, "SELECT 1; SELECT 2"),
                 SqlException.class,
-                "Multiple statements aren't allowed"
+                "Multiple statements are not allowed"
         );
 
         // Planning error.
@@ -309,7 +309,7 @@ public class ItSqlSynchronousApiTest extends 
ClusterPerClassIntegrationTest {
         assertThrowsWithCause(
                 () -> ses.executeBatch(null, "SELECT * FROM TEST", args),
                 SqlException.class,
-                "Unexpected number of query parameters. Provided 2 but there 
is only 0 dynamic parameter(s)"
+                "Invalid SQL statement type in the batch"
         );
 
         assertThrowsWithCause(
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ClusterPerClassIntegrationTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ClusterPerClassIntegrationTest.java
index 6cab3df26b..341ab7216a 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ClusterPerClassIntegrationTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ClusterPerClassIntegrationTest.java
@@ -411,7 +411,7 @@ public abstract class ClusterPerClassIntegrationTest 
extends IgniteIntegrationTe
         ));
 
         try {
-            var context = tx != null ? QueryContext.of(tx) : QueryContext.of();
+            var context = QueryContext.create(SqlQueryType.ALL, tx);
 
             return getAllFromCursor(
                     await(queryEngine.querySingleAsync(sessionId, context, 
sql, args))
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 1cd14647b2..b953fa7667 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
@@ -36,6 +36,7 @@ import java.util.concurrent.ThreadLocalRandom;
 import org.apache.calcite.runtime.CalciteContextException;
 import org.apache.ignite.internal.sql.engine.util.MetadataMatcher;
 import org.apache.ignite.sql.ColumnType;
+import org.apache.ignite.sql.SqlException;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
@@ -243,7 +244,7 @@ public class ItDynamicParameterTest extends 
ClusterPerClassIntegrationTest {
     }
 
     private static void assertUnexpectedNumberOfParameters(String query, 
Object... params) {
-        CalciteContextException err = 
assertThrows(CalciteContextException.class, () -> {
+        SqlException err = assertThrows(SqlException.class, () -> {
             assertQuery(query).withParams(params).check();
         }, "query: " + query);
 
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUuidTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUuidTest.java
index a80ccf87d2..7fdcf8ed5d 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUuidTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUuidTest.java
@@ -175,7 +175,7 @@ public class ItUuidTest extends 
ClusterPerClassIntegrationTest {
         // CASE <boolean> WHEN ... END
 
         var query = "SELECT id, CASE uuid_key WHEN uuid_key = ? THEN uuid_key 
ELSE ? END FROM t;";
-        var t = assertThrows(CalciteContextException.class, () -> sql(query, 
UUID_1));
+        var t = assertThrows(CalciteContextException.class, () -> sql(query, 
UUID_1, UUID_1));
         assertThat(t.getMessage(), containsString("Invalid types for 
comparison: UUID = BOOLEAN"));
     }
 
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
index 33e1ad4417..e1a9bf00de 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
@@ -44,6 +44,7 @@ import org.apache.ignite.internal.sql.engine.AsyncSqlCursor;
 import org.apache.ignite.internal.sql.engine.QueryContext;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
 import org.apache.ignite.internal.sql.engine.QueryProperty;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.property.PropertiesHolder;
 import org.apache.ignite.internal.sql.engine.session.SessionId;
 import org.apache.ignite.internal.util.CollectionUtils;
@@ -420,7 +421,7 @@ public abstract class QueryChecker {
                 Map.of(QueryProperty.DEFAULT_SCHEMA, "PUBLIC")
         ));
 
-        QueryContext context = tx != null ? QueryContext.of(tx) : 
QueryContext.of();
+        QueryContext context = QueryContext.create(SqlQueryType.ALL, tx);
 
         String qry = originalQuery;
 
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 a7c019a174..31a0a9934c 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,13 +20,13 @@ package org.apache.ignite.internal.sql.api;
 import static org.apache.ignite.lang.ErrorGroups.Common.UNEXPECTED_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.INVALID_DML_RESULT_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.OPERATION_INTERRUPTED_ERR;
-import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_INVALID_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.SESSION_NOT_FOUND_ERR;
 
 import it.unimi.dsi.fastutil.longs.LongArrayList;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
@@ -38,8 +38,7 @@ import org.apache.ignite.internal.sql.engine.AsyncCursor;
 import org.apache.ignite.internal.sql.engine.QueryContext;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
 import org.apache.ignite.internal.sql.engine.QueryProperty;
-import org.apache.ignite.internal.sql.engine.QueryValidator;
-import org.apache.ignite.internal.sql.engine.prepare.QueryPlan.Type;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.property.PropertiesHolder;
 import org.apache.ignite.internal.sql.engine.session.SessionId;
 import org.apache.ignite.internal.util.ArrayUtils;
@@ -165,7 +164,7 @@ public class SessionImpl implements Session {
         }
 
         try {
-            QueryContext ctx = QueryContext.of(transaction);
+            QueryContext ctx = QueryContext.create(SqlQueryType.ALL, 
transaction);
 
             CompletableFuture<AsyncResultSet<SqlRow>> result = 
qryProc.querySingleAsync(sessionId, ctx, query, arguments)
                     .thenCompose(cur -> cur.requestNextAsync(pageSize)
@@ -231,16 +230,9 @@ public class SessionImpl implements Session {
         }
 
         try {
-            QueryContext ctx = QueryContext.of(
-                    transaction,
-                    (QueryValidator) plan -> {
-                        if (plan.type() != Type.DML) {
-                            throw new SqlException(QUERY_INVALID_ERR, "Invalid 
SQL statement type in the batch [plan=" + plan + ']');
-                        }
-                    }
-            );
+            QueryContext ctx = QueryContext.create(Set.of(SqlQueryType.DML), 
transaction);
 
-            final var counters = new LongArrayList(batch.size());
+            var counters = new LongArrayList(batch.size());
             CompletableFuture<Void> tail = 
CompletableFuture.completedFuture(null);
             ArrayList<CompletableFuture<Void>> batchFuts = new 
ArrayList<>(batch.size());
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryContext.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryContext.java
index cc84361b40..fec3794cfd 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryContext.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryContext.java
@@ -20,24 +20,36 @@ package org.apache.ignite.internal.sql.engine;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 import org.apache.calcite.plan.Context;
 import org.apache.ignite.internal.util.ArrayUtils;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * User query context.
  */
 public class QueryContext implements Context {
+
+    /** A set of types of SQL commands that can be executed within an 
operation this context is associated with. **/
+    private final Set<SqlQueryType> allowedQueries;
+
     /** Context params. */
     private final Object[] params;
 
     /**
      * Constructor.
      *
+     * @param allowedQueries Allowed query types.
      * @param params Context params.
      */
-    private QueryContext(Object[] params) {
+    private QueryContext(Set<SqlQueryType> allowedQueries, Object[] params) {
         this.params = params;
+        this.allowedQueries = allowedQueries;
+    }
+
+    /** Returns a set of {@link SqlQueryType allowed query types}. **/
+    public Set<SqlQueryType> allowedQueryTypes() {
+        return allowedQueries;
     }
 
     /** {@inheritDoc} */
@@ -51,33 +63,61 @@ public class QueryContext implements Context {
     }
 
     /**
-     * Creates a context from a list of parameters.
+     * Creates a context that allows the given query types.
+     *
+     * @param allowedQueries A set of allowed query types.
+     * @return Query context.
+     */
+    public static QueryContext create(Set<SqlQueryType> allowedQueries) {
+        return create(allowedQueries, null, ArrayUtils.OBJECT_EMPTY_ARRAY);
+    }
+
+    /**
+     * Creates a context that allows the given query types and includes an 
optional parameter.
      *
-     * @param params Context parameters.
+     * @param allowedQueries A set of allowed query types.
+     * @param param A context parameter.
      * @return Query context.
      */
-    public static QueryContext of(Object... params) {
-        return !ArrayUtils.nullOrEmpty(params) ? new QueryContext(build(null, 
params).toArray())
-                : new QueryContext(ArrayUtils.OBJECT_EMPTY_ARRAY);
+    public static QueryContext create(Set<SqlQueryType> allowedQueries, 
@Nullable Object param) {
+        return create(allowedQueries, param, ArrayUtils.OBJECT_EMPTY_ARRAY);
     }
 
-    private static List<Object> build(List<Object> dst, Object[] src) {
-        if (dst == null) {
-            dst = new ArrayList<>();
+    /**
+     * Creates a context that allows the given query types and includes a list 
of parameters.
+     *
+     * @param allowedQueries A set of allowed query types.
+     * @param param An optional parameter.
+     * @param params Optional array of additional parameters.
+     * @return Query context.
+     */
+    public static QueryContext create(Set<SqlQueryType> allowedQueries, 
@Nullable Object param, Object... params) {
+        Object[] paramsArray = params == null ? ArrayUtils.OBJECT_EMPTY_ARRAY 
: params;
+
+        if (param == null && paramsArray.length == 0) {
+            return new QueryContext(allowedQueries, 
ArrayUtils.OBJECT_EMPTY_ARRAY);
+        } else {
+            ArrayList<Object> dst = new ArrayList<>();
+            build(dst, param);
+            build(dst, paramsArray);
+            return new QueryContext(allowedQueries, dst.toArray());
         }
+    }
 
+    private static void build(List<Object> dst, Object[] src) {
         for (Object obj : src) {
-            if (obj == null) {
-                continue;
-            }
-
-            if (obj.getClass() == QueryContext.class) {
-                build(dst, ((QueryContext) obj).params);
-            } else {
-                dst.add(obj);
-            }
+            build(dst, obj);
         }
+    }
 
-        return dst;
+    private static void build(List<Object> dst, @Nullable Object obj) {
+        if (obj == null) {
+            return;
+        }
+        if (obj.getClass() == QueryContext.class) {
+            build(dst, ((QueryContext) obj).params);
+        } else {
+            dst.add(obj);
+        }
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
index 8d084d192c..115a2d4b51 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
@@ -23,12 +23,14 @@ import static 
org.apache.ignite.lang.ErrorGroups.Sql.QUERY_INVALID_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.SCHEMA_NOT_FOUND_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.SESSION_EXPIRED_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.SESSION_NOT_FOUND_ERR;
+import static 
org.apache.ignite.lang.ErrorGroups.Sql.UNSUPPORTED_SQL_OPERATION_KIND_ERR;
 import static org.apache.ignite.lang.IgniteStringFormatter.format;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
@@ -40,7 +42,6 @@ import java.util.stream.Stream;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.util.Pair;
 import org.apache.ignite.internal.distributionzones.DistributionZoneManager;
@@ -62,6 +63,7 @@ import 
org.apache.ignite.internal.sql.engine.exec.LifecycleAware;
 import org.apache.ignite.internal.sql.engine.exec.MailboxRegistryImpl;
 import org.apache.ignite.internal.sql.engine.exec.QueryTaskExecutor;
 import org.apache.ignite.internal.sql.engine.exec.QueryTaskExecutorImpl;
+import org.apache.ignite.internal.sql.engine.exec.QueryValidationException;
 import org.apache.ignite.internal.sql.engine.exec.ddl.DdlCommandHandler;
 import org.apache.ignite.internal.sql.engine.message.MessageServiceImpl;
 import org.apache.ignite.internal.sql.engine.prepare.PrepareService;
@@ -72,6 +74,10 @@ import 
org.apache.ignite.internal.sql.engine.schema.SqlSchemaManagerImpl;
 import org.apache.ignite.internal.sql.engine.session.SessionId;
 import org.apache.ignite.internal.sql.engine.session.SessionInfo;
 import org.apache.ignite.internal.sql.engine.session.SessionManager;
+import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
+import org.apache.ignite.internal.sql.engine.sql.ParseResult;
+import org.apache.ignite.internal.sql.engine.sql.ScriptParseResult;
+import org.apache.ignite.internal.sql.engine.sql.StatementParseResult;
 import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.internal.storage.DataStorageManager;
@@ -305,7 +311,9 @@ public class SqlQueryProcessor implements QueryProcessor {
     /** {@inheritDoc} */
     @Override
     public List<CompletableFuture<AsyncSqlCursor<List<Object>>>> 
queryAsync(String schemaName, String qry, Object... params) {
-        return queryAsync(QueryContext.of(), schemaName, qry, params);
+        QueryContext context = QueryContext.create(SqlQueryType.ALL);
+
+        return queryAsync(context, schemaName, qry, params);
     }
 
     /** {@inheritDoc} */
@@ -401,13 +409,12 @@ public class SqlQueryProcessor implements QueryProcessor {
 
         CompletableFuture<AsyncSqlCursor<List<Object>>> stage = start
                 .thenApply(v -> {
-                    var nodes = Commons.parse(sql, Commons.PARSER_CONFIG);
+                    StatementParseResult parseResult = 
IgniteSqlParser.parse(sql, StatementParseResult.MODE);
+                    SqlNode sqlNode = parseResult.statement();
 
-                    if (nodes.size() > 1) {
-                        throw new SqlException(QUERY_INVALID_ERR, "Multiple 
statements aren't allowed.");
-                    }
+                    validateParsedStatement(context, parseResult, sqlNode, 
params);
 
-                    return nodes.get(0);
+                    return sqlNode;
                 })
                 .thenCompose(sqlNode -> {
                     boolean rwOp = dataModificationOp(sqlNode);
@@ -426,17 +433,17 @@ public class SqlQueryProcessor implements QueryProcessor {
 
                     return prepareSvc.prepareAsync(sqlNode, ctx)
                             .thenApply(plan -> {
-                                context.maybeUnwrap(QueryValidator.class)
-                                        .ifPresent(queryValidator -> 
queryValidator.validatePlan(plan));
-
                                 boolean implicitTxRequired = outerTx == null;
 
                                 InternalTransaction tx = implicitTxRequired ? 
txManager.begin(!rwOp) : outerTx;
 
                                 var dataCursor = executionSrvc.executePlan(tx, 
plan, ctx);
 
+                                SqlQueryType queryType = plan.type();
+                                assert queryType != null : "Expected a full 
plan but got a fragment: " + plan;
+
                                 return new AsyncSqlCursorImpl<>(
-                                        
SqlQueryType.mapPlanTypeToSqlType(plan.type()),
+                                        queryType,
                                         plan.metadata(),
                                         implicitTxRequired ? tx : null,
                                         new AsyncCursor<List<Object>>() {
@@ -483,19 +490,29 @@ public class SqlQueryProcessor implements QueryProcessor {
 
         CompletableFuture<Void> start = new CompletableFuture<>();
 
-        SqlNodeList nodes = SqlNodeList.EMPTY;
-
-        var res = new 
ArrayList<CompletableFuture<AsyncSqlCursor<List<Object>>>>(nodes.size());
+        ScriptParseResult parseResult;
+        List<CompletableFuture<AsyncSqlCursor<List<Object>>>> res;
 
         try {
-            nodes = Commons.parse(sql, FRAMEWORK_CONFIG.getParserConfig());
+            parseResult = IgniteSqlParser.parse(sql, ScriptParseResult.MODE);
+            res = new ArrayList<>(parseResult.statements().size());
         } catch (Throwable th) {
             start.completeExceptionally(th);
 
-            res.add(CompletableFuture.completedFuture(failedCursor(th)));
+            parseResult = new ScriptParseResult(Collections.emptyList(), 0);
+            res = 
Collections.singletonList(CompletableFuture.completedFuture(failedCursor(th)));
         }
 
-        for (SqlNode sqlNode : nodes) {
+        for (SqlNode sqlNode : parseResult.statements()) {
+            try {
+                validateParsedStatement(context, parseResult, sqlNode, params);
+            } catch (Exception e) {
+                start.completeExceptionally(e);
+
+                res = 
Collections.singletonList(CompletableFuture.completedFuture(failedCursor(e)));
+                return res;
+            }
+
             // Only rw transactions for now.
             InternalTransaction implicitTx = txManager.begin(false);
 
@@ -512,13 +529,14 @@ public class SqlQueryProcessor implements QueryProcessor {
                     .build();
 
             // TODO https://issues.apache.org/jira/browse/IGNITE-17746 Fix 
query execution flow.
-            CompletableFuture<AsyncSqlCursor<List<Object>>> stage = 
start.thenCompose(none -> prepareSvc.prepareAsync(sqlNode, ctx))
+            CompletableFuture<AsyncSqlCursor<List<Object>>> stage = start
+                    .thenCompose(none -> prepareSvc.prepareAsync(sqlNode, ctx))
                     .thenApply(plan -> {
-                        context.maybeUnwrap(QueryValidator.class)
-                                .ifPresent(queryValidator -> 
queryValidator.validatePlan(plan));
+                        SqlQueryType queryType = plan.type();
+                        assert queryType != null : "Expected a full plan but 
got a fragment: " + plan;
 
                         return new AsyncSqlCursorImpl<>(
-                                SqlQueryType.mapPlanTypeToSqlType(plan.type()),
+                                queryType,
                                 plan.metadata(),
                                 implicitTx,
                                 executionSrvc.executePlan(implicitTx, plan, 
ctx)
@@ -664,4 +682,31 @@ public class SqlQueryProcessor implements QueryProcessor {
     private static boolean dataModificationOp(SqlNode sqlNode) {
         return SqlKind.DML.contains(sqlNode.getKind());
     }
+
+    /** Performs additional validation of a parsed statement. **/
+    private static void validateParsedStatement(QueryContext context, 
ParseResult parseResult, SqlNode node, Object[] params) {
+        Set<SqlQueryType> allowedTypes = context.allowedQueryTypes();
+        SqlQueryType queryType = Commons.getQueryType(node);
+
+        if (queryType == null) {
+            throw new 
IgniteInternalException(UNSUPPORTED_SQL_OPERATION_KIND_ERR, "Unsupported 
operation ["
+                    + "sqlNodeKind=" + node.getKind() + "; "
+                    + "querySql=\"" + node + "\"]");
+        }
+
+        if (!allowedTypes.contains(queryType)) {
+            String message = format("Invalid SQL statement type in the batch. 
Expected {} but got {}.", allowedTypes, queryType);
+
+            throw new QueryValidationException(message);
+        }
+
+        if (parseResult.dynamicParamsCount() != params.length) {
+            String message = format(
+                    "Unexpected number of query parameters. Provided {} but 
there is only {} dynamic parameter(s).",
+                    params.length, parseResult.dynamicParamsCount()
+            );
+
+            throw new SqlException(QUERY_INVALID_ERR, message);
+        }
+    }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryType.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryType.java
index 9fde2c55ed..92e718a7d0 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryType.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryType.java
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.internal.sql.engine;
 
-import org.apache.ignite.internal.sql.engine.prepare.QueryPlan;
+import java.util.Set;
 
 /**
  * Possible query types.
@@ -35,24 +35,6 @@ public enum SqlQueryType {
     /** Explain. */
     EXPLAIN;
 
-    /**
-     * Map query plan type to sql type.
-     *
-     * @param type QueryPlan.Type.
-     * @return Associated SqlQueryType.
-     */
-    public static SqlQueryType mapPlanTypeToSqlType(QueryPlan.Type type) {
-        switch (type) {
-            case QUERY:
-                return QUERY;
-            case DML:
-                return DML;
-            case DDL:
-                return DDL;
-            case EXPLAIN:
-                return EXPLAIN;
-            default:
-                throw new UnsupportedOperationException("Unexpected query plan 
type: " + type.name());
-        }
-    }
+    /** A set of all query types. **/
+    public static final Set<SqlQueryType> ALL = Set.of(values());
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
index fd4ffaa2c1..555da1183a 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
@@ -46,6 +46,7 @@ import 
org.apache.ignite.configuration.ConfigurationChangeException;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.sql.engine.AsyncCursor;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.exec.ddl.DdlCommandHandler;
 import org.apache.ignite.internal.sql.engine.exec.rel.AbstractNode;
 import org.apache.ignite.internal.sql.engine.exec.rel.AsyncRootNode;
@@ -256,7 +257,10 @@ public class ExecutionServiceImpl<RowT> implements 
ExecutionService, TopologyEve
     public AsyncCursor<List<Object>> executePlan(
             InternalTransaction tx, QueryPlan plan, BaseQueryContext ctx
     ) {
-        switch (plan.type()) {
+        SqlQueryType queryType = plan.type();
+        assert queryType != null : "Root plan can not be a fragment";
+
+        switch (queryType) {
             case DML:
                 // TODO a barrier between previous operation and this one
             case QUERY:
@@ -267,7 +271,7 @@ public class ExecutionServiceImpl<RowT> implements 
ExecutionService, TopologyEve
                 return executeDdl((DdlPlan) plan);
 
             default:
-                throw new AssertionError("Unexpected plan type: " + plan);
+                throw new AssertionError("Unexpected query type: " + plan);
         }
     }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/DdlPlan.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/DdlPlan.java
index 759a9766ff..c594cc0e46 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/DdlPlan.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/DdlPlan.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.sql.engine.prepare;
 import java.util.List;
 import org.apache.ignite.internal.sql.api.ColumnMetadataImpl;
 import org.apache.ignite.internal.sql.api.ResultSetMetadataImpl;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.DdlCommand;
 import org.apache.ignite.sql.ColumnMetadata;
 import org.apache.ignite.sql.ColumnType;
@@ -46,8 +47,8 @@ public class DdlPlan implements QueryPlan {
 
     /** {@inheritDoc} */
     @Override
-    public Type type() {
-        return Type.DDL;
+    public SqlQueryType type() {
+        return SqlQueryType.DDL;
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainPlan.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainPlan.java
index 6f4d118c46..78089a7ef0 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainPlan.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainPlan.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.sql.engine.prepare;
 import java.util.List;
 import org.apache.ignite.internal.sql.api.ColumnMetadataImpl;
 import org.apache.ignite.internal.sql.api.ResultSetMetadataImpl;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.sql.ColumnMetadata;
 import org.apache.ignite.sql.ColumnType;
 import org.apache.ignite.sql.ResultSetMetadata;
@@ -44,8 +45,8 @@ public class  ExplainPlan implements QueryPlan {
     }
 
     /** {@inheritDoc} */
-    @Override public Type type() {
-        return Type.EXPLAIN;
+    @Override public SqlQueryType type() {
+        return SqlQueryType.EXPLAIN;
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/FragmentPlan.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/FragmentPlan.java
index 08b7961bd8..658b0874b8 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/FragmentPlan.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/FragmentPlan.java
@@ -18,9 +18,11 @@
 package org.apache.ignite.internal.sql.engine.prepare;
 
 import org.apache.calcite.plan.RelOptCluster;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.sql.ResultSetMetadata;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * FragmentPlan.
@@ -45,8 +47,8 @@ public class FragmentPlan implements QueryPlan {
 
     /** {@inheritDoc} */
     @Override
-    public Type type() {
-        return Type.FRAGMENT;
+    public @Nullable SqlQueryType type() {
+        return null;
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgnitePlanner.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgnitePlanner.java
index 4e041770a9..0f19a5c096 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgnitePlanner.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgnitePlanner.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.sql.engine.prepare;
 
 import static org.apache.ignite.internal.sql.engine.util.Commons.shortRuleName;
 import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_INVALID_ERR;
+import static org.apache.ignite.lang.IgniteStringFormatter.format;
 
 import java.io.PrintWriter;
 import java.io.Reader;
@@ -61,7 +62,6 @@ import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.sql.SqlDataTypeSpec;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.parser.SqlParseException;
 import org.apache.calcite.sql.parser.SqlParser;
@@ -76,10 +76,12 @@ import org.apache.calcite.util.Pair;
 import org.apache.ignite.internal.sql.engine.metadata.IgniteMetadata;
 import org.apache.ignite.internal.sql.engine.metadata.RelMetadataQueryEx;
 import org.apache.ignite.internal.sql.engine.rex.IgniteRexBuilder;
+import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
+import org.apache.ignite.internal.sql.engine.sql.StatementParseResult;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
-import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.internal.util.FastTimestamps;
 import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.sql.SqlException;
 
 /**
  * Query planer.
@@ -165,9 +167,20 @@ public class IgnitePlanner implements Planner, 
RelOptTable.ViewExpander {
     /** {@inheritDoc} */
     @Override
     public SqlNode parse(Reader reader) throws SqlParseException {
-        SqlNodeList sqlNodes = Commons.parse(reader, parserCfg);
+        StatementParseResult parseResult = IgniteSqlParser.parse(reader, 
StatementParseResult.MODE);
+        Object[] parameters = ctx.parameters();
 
-        return sqlNodes.size() == 1 ? sqlNodes.get(0) : sqlNodes;
+        // Parse method is only used in tests.
+        if (parameters.length != parseResult.dynamicParamsCount()) {
+            String message = format(
+                    "Unexpected number of query parameters. Provided {} but 
there is only {} dynamic parameter(s).",
+                    parameters.length, parseResult.dynamicParamsCount()
+            );
+
+            throw new SqlException(QUERY_INVALID_ERR, message);
+        }
+
+        return parseResult.statement();
     }
 
     /** {@inheritDoc} */
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 62874427a1..e084e73113 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
@@ -102,8 +102,6 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
     /** Dynamic parameters. */
     private final Object[] parameters;
 
-    private int dynamicParameterCount;
-
     /**
      * Creates a validator.
      *
@@ -123,34 +121,18 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
     /** {@inheritDoc} */
     @Override
     public SqlNode validate(SqlNode topNode) {
-        this.dynamicParameterCount = 0;
-        try {
-            SqlNode topNodeToValidate;
-            // Calcite fails to validate a query when its top node is EXPLAIN 
PLAN FOR
-            // java.lang.NullPointerException: namespace for <query>
-            // at 
org.apache.calcite.sql.validate.SqlValidatorImpl.getNamespaceOrThrow(SqlValidatorImpl.java:1280)
-            boolean explain = topNode instanceof SqlExplain;
-            if (explain) {
-                SqlExplain explainNode = (SqlExplain) topNode;
-                topNodeToValidate = explainNode.getExplicandum();
-            } else {
-                topNodeToValidate = topNode;
-            }
+        // Calcite fails to validate a query when its top node is EXPLAIN PLAN 
FOR
+        // java.lang.NullPointerException: namespace for <query>
+        // at 
org.apache.calcite.sql.validate.SqlValidatorImpl.getNamespaceOrThrow(SqlValidatorImpl.java:1280)
+        if (topNode instanceof SqlExplain) {
+            SqlExplain explainNode = (SqlExplain) topNode;
+            SqlNode topNodeToValidate = explainNode.getExplicandum();
 
             SqlNode validatedNode = super.validate(topNodeToValidate);
-            if (parameters.length != dynamicParameterCount) {
-                throw newValidationError(topNode, 
IgniteResource.INSTANCE.unexpectedParameter(parameters.length, 
dynamicParameterCount));
-            }
-
-            if (explain) {
-                SqlExplain explainNode = (SqlExplain) topNode;
-                explainNode.setOperand(0, validatedNode);
-                return explainNode;
-            } else {
-                return validatedNode;
-            }
-        } finally {
-            this.dynamicParameterCount = 0;
+            explainNode.setOperand(0, validatedNode);
+            return explainNode;
+        } else {
+            return super.validate(topNode);
         }
     }
 
@@ -272,18 +254,10 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
         } else if (n instanceof SqlDynamicParam) {
             SqlDynamicParam dynamicParam = (SqlDynamicParam) n;
             int idx = dynamicParam.getIndex();
-            // validateDynamicParam is not called by a validator we must call 
it ourselves.
-            validateDynamicParam(dynamicParam);
-
-            // We must check the number of dynamic parameters unless
-            // https://issues.apache.org/jira/browse/IGNITE-18653
-            // is resolved.
-            if (idx < parameters.length) {
-                Object param = parameters[idx];
-                if (parameters[idx] instanceof Integer) {
-                    if ((Integer) param < 0) {
-                        throw newValidationError(n, 
IgniteResource.INSTANCE.correctIntegerLimit(nodeName));
-                    }
+            Object param = parameters[idx];
+            if (param instanceof Integer) {
+                if ((Integer) param < 0) {
+                    throw newValidationError(n, 
IgniteResource.INSTANCE.correctIntegerLimit(nodeName));
                 }
             }
         }
@@ -567,21 +541,12 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
         return Commons.implicitPkEnabled() && 
Commons.IMPLICIT_PK_COL_NAME.equals(alias);
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public void validateDynamicParam(SqlDynamicParam dynamicParam) {
-        this.dynamicParameterCount = Integer.max(dynamicParameterCount, 
dynamicParam.getIndex() + 1);
-    }
-
     /** {@inheritDoc} */
     @Override
     protected void inferUnknownTypes(RelDataType inferredType, 
SqlValidatorScope scope, SqlNode node) {
         if (node instanceof SqlDynamicParam && 
inferredType.equals(unknownType)) {
             SqlDynamicParam dynamicParam = (SqlDynamicParam) node;
 
-            // We explicitly call validateDynamicParam because the validator 
does not call it.
-            validateDynamicParam(dynamicParam);
-
             RelDataType type = inferDynamicParamType(dynamicParam);
             setValidatedNodeType(node, type);
         } else if (node instanceof SqlCall) {
@@ -633,26 +598,16 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
     }
 
     private RelDataType inferDynamicParamType(SqlDynamicParam dynamicParam) {
-        // We must check the number of dynamic parameters unless
-        // https://issues.apache.org/jira/browse/IGNITE-18653
-        // is resolved.
-
         RelDataType parameterType;
 
-        if (dynamicParam.getIndex() < parameters.length) {
-            Object param = parameters[dynamicParam.getIndex()];
-            // IgniteCustomType: first we must check whether dynamic parameter 
is a custom data type.
-            // If so call createCustomType with appropriate arguments.
-            if (param instanceof UUID) {
-                parameterType =  typeFactory().createCustomType(UuidType.NAME);
-            } else if (param != null) {
-                parameterType = 
typeFactory().toSql(typeFactory().createType(param.getClass()));
-            } else {
-                parameterType = typeFactory().createSqlType(SqlTypeName.NULL);
-            }
+        Object param = parameters[dynamicParam.getIndex()];
+        // IgniteCustomType: first we must check whether dynamic parameter is 
a custom data type.
+        // If so call createCustomType with appropriate arguments.
+        if (param instanceof UUID) {
+            parameterType =  typeFactory().createCustomType(UuidType.NAME);
+        } else if (param != null) {
+            parameterType = 
typeFactory().toSql(typeFactory().createType(param.getClass()));
         } else {
-            // This query will be rejected since the number of dynamic 
parameters
-            // is not valid.
             parameterType = typeFactory().createSqlType(SqlTypeName.NULL);
         }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepDmlPlan.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepDmlPlan.java
index 36775f6627..1331fba1fa 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepDmlPlan.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepDmlPlan.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.sql.engine.prepare;
 import java.util.List;
 import org.apache.ignite.internal.sql.api.ColumnMetadataImpl;
 import org.apache.ignite.internal.sql.api.ResultSetMetadataImpl;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.sql.ColumnMetadata;
 import org.apache.ignite.sql.ColumnType;
 import org.apache.ignite.sql.ResultSetMetadata;
@@ -42,8 +43,8 @@ public class MultiStepDmlPlan extends AbstractMultiStepPlan {
     }
 
     /** {@inheritDoc} */
-    @Override public Type type() {
-        return Type.DML;
+    @Override public SqlQueryType type() {
+        return SqlQueryType.DML;
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepQueryPlan.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepQueryPlan.java
index 8ca13c53f9..0b23418c0a 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepQueryPlan.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepQueryPlan.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.engine.prepare;
 
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.sql.ResultSetMetadata;
 
 /**
@@ -34,8 +35,8 @@ public class MultiStepQueryPlan extends AbstractMultiStepPlan 
{
     }
 
     /** {@inheritDoc} */
-    @Override public Type type() {
-        return Type.QUERY;
+    @Override public SqlQueryType type() {
+        return SqlQueryType.QUERY;
     }
 
     /** {@inheritDoc} */
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 4611036835..55efa978e8 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
@@ -40,17 +40,18 @@ import org.apache.calcite.runtime.CalciteContextException;
 import org.apache.calcite.sql.SqlDdl;
 import org.apache.calcite.sql.SqlExplain;
 import org.apache.calcite.sql.SqlExplainLevel;
-import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.sql.api.ColumnMetadataImpl;
 import org.apache.ignite.internal.sql.api.ResultSetMetadataImpl;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import 
org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverter;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.schema.SchemaUpdateListener;
 import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
+import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.internal.sql.engine.util.TypeUtils;
 import org.apache.ignite.internal.storage.DataStorageManager;
 import org.apache.ignite.internal.thread.NamedThreadFactory;
@@ -146,28 +147,21 @@ public class PrepareServiceImpl implements 
PrepareService, SchemaUpdateListener
         try {
             assert single(sqlNode);
 
-            var planningContext = PlanningContext.builder()
+            SqlQueryType queryType = Commons.getQueryType(sqlNode);
+            assert queryType != null : "No query type for query: " + sqlNode;
+
+            PlanningContext planningContext = PlanningContext.builder()
                     .parentContext(ctx)
                     .build();
 
-            if (SqlKind.DDL.contains(sqlNode.getKind())) {
-                return prepareDdl(sqlNode, planningContext);
-            }
-
-            switch (sqlNode.getKind()) {
-                case SELECT:
-                case ORDER_BY:
-                case WITH:
-                case VALUES:
-                case UNION:
-                case EXCEPT:
-                case INTERSECT:
+            switch (queryType) {
+                case QUERY:
                     return prepareQuery(sqlNode, planningContext);
 
-                case INSERT:
-                case DELETE:
-                case UPDATE:
-                case MERGE:
+                case DDL:
+                    return prepareDdl(sqlNode, planningContext);
+
+                case DML:
                     return prepareDml(sqlNode, planningContext);
 
                 case EXPLAIN:
@@ -227,7 +221,7 @@ public class PrepareServiceImpl implements PrepareService, 
SchemaUpdateListener
 
         var key = new CacheKey(ctx.schemaName(), sqlNode.toString(), 
distributed, paramTypes);
 
-        var planFut = cache.computeIfAbsent(key, k -> 
CompletableFuture.supplyAsync(() -> {
+        CompletableFuture<QueryPlan> planFut = cache.computeIfAbsent(key, k -> 
CompletableFuture.supplyAsync(() -> {
             IgnitePlanner planner = ctx.planner();
 
             // Validate
@@ -251,7 +245,7 @@ public class PrepareServiceImpl implements PrepareService, 
SchemaUpdateListener
     private CompletableFuture<QueryPlan> prepareDml(SqlNode sqlNode, 
PlanningContext ctx) {
         var key = new CacheKey(ctx.schemaName(), sqlNode.toString());
 
-        var planFut = cache.computeIfAbsent(key, k -> 
CompletableFuture.supplyAsync(() -> {
+        CompletableFuture<QueryPlan> planFut = cache.computeIfAbsent(key, k -> 
CompletableFuture.supplyAsync(() -> {
             IgnitePlanner planner = ctx.planner();
 
             // Validate
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryPlan.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryPlan.java
index 142027057e..c26f2d75e6 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryPlan.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryPlan.java
@@ -17,22 +17,19 @@
 
 package org.apache.ignite.internal.sql.engine.prepare;
 
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.sql.ResultSetMetadata;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * QueryPlan interface.
  * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
  */
 public interface QueryPlan {
-    /** Query type. */
-    enum Type {
-        QUERY, FRAGMENT, DML, DDL, EXPLAIN
-    }
-
     /**
-     * Get query type.
+     * Get query type, or {@code null} if this is a fragment.
      */
-    Type type();
+    @Nullable SqlQueryType type();
 
     /**
      * Get fields metadata.
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParser.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParser.java
new file mode 100644
index 0000000000..6d1899cd78
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParser.java
@@ -0,0 +1,162 @@
+/*
+ * 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.sql;
+
+import static org.apache.ignite.internal.util.ExceptionUtils.withCauseAndCode;
+import static org.apache.ignite.lang.ErrorGroup.extractCauseMessage;
+import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_INVALID_ERR;
+
+import java.io.Reader;
+import org.apache.calcite.config.Lex;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.parser.SqlAbstractParserImpl;
+import org.apache.calcite.sql.parser.SqlParseException;
+import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.parser.SqlParserImplFactory;
+import org.apache.calcite.util.SourceStringReader;
+import 
org.apache.ignite.internal.generated.query.calcite.sql.IgniteSqlParserImpl;
+import org.apache.ignite.sql.SqlException;
+
+/**
+ * Provides method for parsing SQL statements in SQL dialect of Apache Ignite 
3.
+ *
+ * <p>One should use parsing methods defined in this class,
+ * instead of creating {@link SqlParser} that use {@link IgniteSqlParserImpl} 
directly.
+ */
+public final class IgniteSqlParser  {
+
+    /**
+     * Parser configuration.
+     */
+    public static final SqlParser.Config PARSER_CONFIG = SqlParser.config()
+            .withParserFactory(InternalIgniteSqlParser.FACTORY)
+            .withLex(Lex.ORACLE)
+            .withConformance(IgniteSqlConformance.INSTANCE);
+
+    private IgniteSqlParser() {
+
+    }
+
+    /**
+     * Parses the given SQL string in the specified {@link ParseMode mode},
+     * which determines the result of the parse operation.
+     *
+     * @param sql  An SQL string.
+     * @param mode  A parse mode.
+     * @return  A parse result.
+     *
+     * @see StatementParseResult#MODE
+     * @see ScriptParseResult#MODE
+     */
+    public static <T extends ParseResult> T parse(String sql, ParseMode<T> 
mode) {
+        try (SourceStringReader reader = new SourceStringReader(sql)) {
+            return parse(reader, mode);
+        }
+    }
+
+    /**
+     * Parses an SQL string from the given reader in the specified {@link 
ParseMode mode},
+     * which determines the result of the parse operation.
+     *
+     * @param reader  A read that contains an SQL string.
+     * @param mode  A parse mode.
+     * @return  A parse result.
+     *
+     * @see StatementParseResult#MODE
+     * @see ScriptParseResult#MODE
+     */
+    public static <T extends ParseResult> T parse(Reader reader, ParseMode<T> 
mode) {
+        try  {
+            InternalIgniteSqlParser.dynamicParamCount.set(null);
+
+            SqlParser parser = SqlParser.create(reader, PARSER_CONFIG);
+            SqlNodeList nodeList = parser.parseStmtList();
+
+            Integer dynamicParamsCount = 
InternalIgniteSqlParser.dynamicParamCount.get();
+            assert dynamicParamsCount != null : "dynamicParamCount has not 
been updated";
+
+            return mode.createResult(nodeList.getList(), dynamicParamsCount);
+        } catch (SqlParseException e) {
+            throw withCauseAndCode(
+                    SqlException::new,
+                    QUERY_INVALID_ERR,
+                    "Failed to parse query: " + 
extractCauseMessage(e.getMessage()),
+                    e);
+        } finally {
+            InternalIgniteSqlParser.dynamicParamCount.set(null);
+        }
+    }
+
+    private static final class InternalIgniteSqlParser extends 
IgniteSqlParserImpl {
+
+        /**
+         * A factory that create instances of {@link IgniteSqlParser}.
+         */
+        private static final SqlParserImplFactory FACTORY = new 
SqlParserImplFactory() {
+            @Override
+            public SqlAbstractParserImpl getParser(Reader reader) {
+                InternalIgniteSqlParser parser = new 
InternalIgniteSqlParser(reader);
+                if (reader instanceof SourceStringReader) {
+                    String sql = ((SourceStringReader) 
reader).getSourceString();
+                    parser.setOriginalSql(sql);
+                }
+                return parser;
+            }
+        };
+
+
+        // We store the number of dynamic parameters in a thread local since
+        // it is not possible to access an instance of IgniteSqlParser created 
by a parser factory.
+        static final ThreadLocal<Integer> dynamicParamCount = new 
ThreadLocal<>();
+
+        InternalIgniteSqlParser(Reader reader) {
+            super(reader);
+        }
+
+        /** {@inheritDoc} **/
+        @Override
+        public SqlNode parseSqlExpressionEof() throws Exception {
+            try {
+                return super.parseSqlExpressionEof();
+            } finally {
+                dynamicParamCount.set(nDynamicParams);
+            }
+        }
+
+        /** {@inheritDoc} **/
+        @Override
+        public SqlNode parseSqlStmtEof() throws Exception {
+            try {
+                return super.parseSqlStmtEof();
+            } finally {
+                dynamicParamCount.set(nDynamicParams);
+            }
+        }
+
+        /** {@inheritDoc} **/
+        @Override
+        public SqlNodeList parseSqlStmtList() throws Exception {
+            try {
+                return super.parseSqlStmtList();
+            } finally {
+                dynamicParamCount.set(nDynamicParams);
+            }
+        }
+    }
+}
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParseMode.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParseMode.java
new file mode 100644
index 0000000000..68db3395f3
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParseMode.java
@@ -0,0 +1,48 @@
+/*
+ * 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.sql;
+
+import java.util.List;
+import org.apache.calcite.sql.SqlNode;
+
+/**
+ * Parse mode determines the expected result of parse operation.
+ *
+ * <p>For example, {@link StatementParseResult#MODE statement mode} guarantees 
that a parse operation returns a single statement
+ * and when input contains multiple statements, it throws an exception.
+ *
+ * @param <T>  A class of parse result.
+ * @see StatementParseResult#MODE
+ * @see ScriptParseResult#MODE
+ */
+public abstract class ParseMode<T extends ParseResult> {
+
+    // Do not allow subclasses outside this package.
+    ParseMode() {
+
+    }
+
+    /**
+     * Constructs an instance of ParseResult from the given list of statements.
+     *
+     * @param list A list of statements.
+     * @param dynamicParamsCount The number of dynamic parameters in the given 
input.
+     * @return a result.
+     */
+    abstract T createResult(List<SqlNode> list, int dynamicParamsCount);
+}
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryValidator.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParseResult.java
similarity index 50%
rename from 
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryValidator.java
rename to 
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParseResult.java
index b0ba3cf6a4..b36a984f96 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryValidator.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParseResult.java
@@ -15,19 +15,35 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.sql.engine;
+package org.apache.ignite.internal.sql.engine.sql;
 
-import org.apache.ignite.internal.sql.engine.exec.QueryValidationException;
-import org.apache.ignite.internal.sql.engine.prepare.QueryPlan;
+import java.util.List;
+import org.apache.calcite.sql.SqlNode;
 
 /**
- * The query validator interface. Allows validating query plan.
- * */
-public interface QueryValidator {
+ * Result of parsing SQL string.
+ */
+public abstract class ParseResult {
+
+    private final int dynamicParamsCount;
+
     /**
-     * Validate the prepared query plan.
+     * Constructor.
      *
-     * @throws QueryValidationException in the case of a validation error.
+     * @param dynamicParamsCount the number of dynamic parameters.
      */
-    void validatePlan(QueryPlan plan) throws QueryValidationException;
+    ParseResult(int dynamicParamsCount) {
+        if (dynamicParamsCount < 0) {
+            throw new IllegalArgumentException("Number of dynamic parameters 
must be positive but got " + dynamicParamsCount);
+        }
+        this.dynamicParamsCount = dynamicParamsCount;
+    }
+
+    /** The number of dynamic parameters in this result. */
+    public int dynamicParamsCount() {
+        return dynamicParamsCount;
+    }
+
+    /** Returns a list of parsed statements. */
+    public abstract List<SqlNode> statements();
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ScriptParseResult.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ScriptParseResult.java
new file mode 100644
index 0000000000..82c77d24fc
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ScriptParseResult.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.sql;
+
+import java.util.List;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * Result of parsing SQL string that multiple statements.
+ */
+public final class ScriptParseResult extends ParseResult {
+
+    /**
+     * Parse operation is expected to return one or multiple statements.
+     */
+    public static final ParseMode<ScriptParseResult> MODE = new ParseMode<>() {
+        @Override
+        ScriptParseResult createResult(List<SqlNode> list, int 
dynamicParamsCount) {
+            return new ScriptParseResult(list, dynamicParamsCount);
+        }
+    };
+
+    private final List<SqlNode> statements;
+
+    /**
+     * Constructor.
+     *
+     * @param statements A list of parsed statements.
+     * @param dynamicParamsCount The number of dynamic parameters.
+     */
+    public ScriptParseResult(List<SqlNode> statements, int dynamicParamsCount) 
{
+        super(dynamicParamsCount);
+        this.statements = statements;
+    }
+
+    /** Returns a list of parsed statements. */
+    @Override
+    public List<SqlNode> statements() {
+        return statements;
+    }
+
+    /** {@inheritDoc} **/
+    @Override
+    public String toString() {
+        return S.toString(ScriptParseResult.class, this);
+    }
+}
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/StatementParseResult.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/StatementParseResult.java
new file mode 100644
index 0000000000..9787a5dec3
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/StatementParseResult.java
@@ -0,0 +1,78 @@
+/*
+ * 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.sql;
+
+import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_INVALID_ERR;
+
+import java.util.List;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.ignite.internal.tostring.S;
+import org.apache.ignite.sql.SqlException;
+
+/**
+ * Result of parsing SQL string that contains exactly one statement.
+ */
+public final class StatementParseResult extends ParseResult {
+
+    /**
+     * Expected result of a parse operation is a single statement. When there 
is more than one statement, parse operation fails with
+     * {@link SqlException}.
+     */
+    public static final ParseMode<StatementParseResult> MODE = new 
ParseMode<>() {
+        @Override
+        StatementParseResult createResult(List<SqlNode> list, int 
dynamicParamsCount) {
+            if (list.size() > 1) {
+                throw new SqlException(QUERY_INVALID_ERR, "Multiple statements 
are not allowed.");
+            }
+
+            return new StatementParseResult(list.get(0), dynamicParamsCount);
+        }
+    };
+
+    private final List<SqlNode> list;
+
+    /**
+     * Constructor.
+     *
+     * @param sqlNode A parsed statement.
+     * @param dynamicParamsCount The number of dynamic parameters.
+     */
+    public StatementParseResult(SqlNode sqlNode, int dynamicParamsCount) {
+        super(dynamicParamsCount);
+        assert !(sqlNode instanceof SqlNodeList) : "Can not create a statement 
result from a node list: " + sqlNode;
+        this.list = List.of(sqlNode);
+    }
+
+    /** Returns a parsed statement. */
+    public SqlNode statement() {
+        return list.get(0);
+    }
+
+    /** {@inheritDoc} **/
+    @Override
+    public List<SqlNode> statements() {
+        return list;
+    }
+
+    /** {@inheritDoc} **/
+    @Override
+    public String toString() {
+        return S.toString(StatementParseResult.class, this, "statement", 
statement());
+    }
+}
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
index acf22f06b7..0ad36b3b67 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
@@ -19,15 +19,11 @@ package org.apache.ignite.internal.sql.engine.util;
 
 import static 
org.apache.ignite.internal.sql.engine.util.BaseQueryContext.CLUSTER;
 import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
-import static org.apache.ignite.internal.util.ExceptionUtils.withCauseAndCode;
-import static org.apache.ignite.lang.ErrorGroup.extractCauseMessage;
 import static 
org.apache.ignite.lang.ErrorGroups.Sql.EXPRESSION_COMPILATION_ERR;
-import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_INVALID_ERR;
 
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
-import java.io.Reader;
 import java.io.StringReader;
 import java.math.BigDecimal;
 import java.math.BigInteger;
@@ -53,7 +49,6 @@ import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import org.apache.calcite.DataContexts;
 import org.apache.calcite.config.CalciteSystemProperty;
-import org.apache.calcite.config.Lex;
 import org.apache.calcite.config.NullCollation;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.Context;
@@ -67,22 +62,19 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.hint.HintStrategyTable;
 import org.apache.calcite.rex.RexBuilder;
-import org.apache.calcite.sql.SqlNodeList;
-import org.apache.calcite.sql.parser.SqlParseException;
-import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql2rel.SqlToRelConverter;
 import org.apache.calcite.tools.FrameworkConfig;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
-import org.apache.calcite.util.SourceStringReader;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.mapping.Mapping;
 import org.apache.calcite.util.mapping.MappingType;
 import org.apache.calcite.util.mapping.Mappings;
 import org.apache.calcite.util.mapping.Mappings.TargetMapping;
-import 
org.apache.ignite.internal.generated.query.calcite.sql.IgniteSqlParserImpl;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.schema.BitmaskNativeType;
 import org.apache.ignite.internal.schema.DecimalNativeType;
@@ -90,6 +82,7 @@ import org.apache.ignite.internal.schema.NativeType;
 import org.apache.ignite.internal.schema.NumberNativeType;
 import org.apache.ignite.internal.schema.TemporalNativeType;
 import org.apache.ignite.internal.schema.VarlenNativeType;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.exec.RowHandler;
 import org.apache.ignite.internal.sql.engine.exec.exp.ExpressionFactoryImpl;
 import org.apache.ignite.internal.sql.engine.exec.exp.RexExecutorImpl;
@@ -98,6 +91,7 @@ import 
org.apache.ignite.internal.sql.engine.prepare.IgniteConvertletTable;
 import org.apache.ignite.internal.sql.engine.prepare.IgniteTypeCoercion;
 import org.apache.ignite.internal.sql.engine.prepare.PlanningContext;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlConformance;
+import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
 import org.apache.ignite.internal.sql.engine.sql.fun.IgniteSqlOperatorTable;
 import org.apache.ignite.internal.sql.engine.trait.DistributionTraitDef;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
@@ -129,11 +123,6 @@ public final class Commons {
      */
     public static final int SORTED_IDX_PART_PREFETCH_SIZE = 100;
 
-    public static final SqlParser.Config PARSER_CONFIG = SqlParser.config()
-            .withParserFactory(IgniteSqlParserImpl.FACTORY)
-            .withLex(Lex.ORACLE)
-            .withConformance(IgniteSqlConformance.INSTANCE);
-
     @SuppressWarnings("rawtypes")
     public static final List<RelTraitDef> DISTRIBUTED_TRAITS_SET = List.of(
             ConventionTraitDef.INSTANCE,
@@ -159,7 +148,7 @@ public final class Commons {
                     )
             )
             .convertletTable(IgniteConvertletTable.INSTANCE)
-            .parserConfig(PARSER_CONFIG)
+            .parserConfig(IgniteSqlParser.PARSER_CONFIG)
             .sqlValidatorConfig(SqlValidator.Config.DEFAULT
                     .withIdentifierExpansion(true)
                     .withDefaultNullCollation(NullCollation.HIGH)
@@ -738,39 +727,6 @@ public final class Commons {
         };
     }
 
-    /**
-     * Parses a SQL statement.
-     *
-     * @param qry Query string.
-     * @param parserCfg Parser config.
-     * @return Parsed query.
-     */
-    public static SqlNodeList parse(String qry, SqlParser.Config parserCfg) {
-        try {
-            return parse(new SourceStringReader(qry), parserCfg);
-        } catch (SqlParseException e) {
-            throw withCauseAndCode(
-                    SqlException::new,
-                    QUERY_INVALID_ERR,
-                    "Failed to parse query: " + 
extractCauseMessage(e.getMessage()),
-                    e);
-        }
-    }
-
-    /**
-     * Parses a SQL statement.
-     *
-     * @param reader Source string reader.
-     * @param parserCfg Parser config.
-     * @return Parsed query.
-     * @throws org.apache.calcite.sql.parser.SqlParseException on parse error.
-     */
-    public static SqlNodeList parse(Reader reader, SqlParser.Config parserCfg) 
throws SqlParseException {
-        SqlParser parser = SqlParser.create(reader, parserCfg);
-
-        return parser.parseStmtList();
-    }
-
     public static RelOptCluster cluster() {
         return CLUSTER;
     }
@@ -805,4 +761,43 @@ public final class Commons {
 
         return ruleDescription.substring(0, pos);
     }
+
+    /**
+     * Returns a {@link SqlQueryType} for the given {@link SqlNode}.
+     * 
+     * <p>If the given node is neither {@code QUERY}, nor {@code DDL}, nor 
{@code DML}, this method returns {@code null}.
+     *
+     * @param sqlNode An SQL node.
+     * @return A query type.
+     */
+    @Nullable
+    public static SqlQueryType getQueryType(SqlNode sqlNode) {
+        SqlKind sqlKind = sqlNode.getKind();
+        if (SqlKind.DDL.contains(sqlKind)) {
+            return SqlQueryType.DDL;
+        }
+
+        switch (sqlKind) {
+            case SELECT:
+            case ORDER_BY:
+            case WITH:
+            case VALUES:
+            case UNION:
+            case EXCEPT:
+            case INTERSECT:
+                return SqlQueryType.QUERY;
+
+            case INSERT:
+            case DELETE:
+            case UPDATE:
+            case MERGE:
+                return SqlQueryType.DML;
+
+            case EXPLAIN:
+                return SqlQueryType.EXPLAIN;
+
+            default:
+                return null;
+        }
+    }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
index b9265a20ac..18bbbb14c7 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
@@ -38,9 +38,6 @@ public interface IgniteResource {
     @Resources.BaseMessage("Illegal value of {0}. The value must be positive 
and less than Integer.MAX_VALUE (" + Integer.MAX_VALUE + ").")
     Resources.ExInst<SqlValidatorException> correctIntegerLimit(String a0);
 
-    @Resources.BaseMessage("Unexpected number of query parameters. Provided 
{0} but there is only {1} dynamic parameter(s).")
-    Resources.ExInst<SqlValidatorException> unexpectedParameter(int provided, 
int expected);
-
     @Resources.BaseMessage("Invalid decimal literal")
     Resources.ExInst<SqlValidatorException> decimalLiteralInvalid();
 }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchParseBenchmark.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchParseBenchmark.java
index ac099953f2..7392ed9074 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchParseBenchmark.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchParseBenchmark.java
@@ -18,7 +18,8 @@
 package org.apache.ignite.internal.sql.engine.benchmarks;
 
 import java.util.concurrent.TimeUnit;
-import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
+import org.apache.ignite.internal.sql.engine.sql.StatementParseResult;
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.Fork;
@@ -68,7 +69,8 @@ public class TpchParseBenchmark {
      */
     @Benchmark
     public void parseQuery(Blackhole bh) {
-        bh.consume(Commons.parse(queryString, Commons.PARSER_CONFIG));
+        StatementParseResult parseResult = IgniteSqlParser.parse(queryString, 
StatementParseResult.MODE);
+        bh.consume(parseResult.statement());
     }
 
     /**
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchPrepareBenchmark.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchPrepareBenchmark.java
index c22cb1726f..f988ad024a 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchPrepareBenchmark.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchPrepareBenchmark.java
@@ -22,7 +22,8 @@ import org.apache.calcite.sql.SqlNode;
 import org.apache.ignite.internal.sql.engine.framework.TestBuilders;
 import org.apache.ignite.internal.sql.engine.framework.TestCluster;
 import org.apache.ignite.internal.sql.engine.framework.TestNode;
-import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
+import org.apache.ignite.internal.sql.engine.sql.StatementParseResult;
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.Fork;
@@ -77,7 +78,9 @@ public class TpchPrepareBenchmark {
         testCluster.start();
         gatewayNode = testCluster.node("N1");
 
-        queryAst = Commons.parse(TpchQueries.getQuery(queryId), 
Commons.PARSER_CONFIG).get(0);
+        String query = TpchQueries.getQuery(queryId);
+        StatementParseResult parseResult = IgniteSqlParser.parse(query, 
StatementParseResult.MODE);
+        queryAst = parseResult.statement();
     }
 
     /** Stops the cluster. */
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
index 5f944a52ff..3939f6ed36 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
@@ -22,8 +22,6 @@ import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.await;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
 import static org.apache.ignite.lang.ErrorGroups.Sql.OPERATION_INTERRUPTED_ERR;
 import static org.apache.ignite.lang.IgniteStringFormatter.format;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.hasSize;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@@ -48,7 +46,6 @@ import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
 import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.schema.SchemaPlus;
-import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
@@ -81,10 +78,11 @@ import 
org.apache.ignite.internal.sql.engine.schema.DefaultValueStrategy;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManager;
 import org.apache.ignite.internal.sql.engine.schema.TableDescriptorImpl;
+import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
+import org.apache.ignite.internal.sql.engine.sql.StatementParseResult;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
-import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.internal.sql.engine.util.HashFunctionFactory;
 import org.apache.ignite.internal.sql.engine.util.HashFunctionFactoryImpl;
 import org.apache.ignite.internal.testframework.IgniteTestUtils.RunnableX;
@@ -452,11 +450,11 @@ public class ExecutionServiceImplTest {
     }
 
     private QueryPlan prepare(String query, BaseQueryContext ctx) {
-        SqlNodeList nodes = Commons.parse(query, 
FRAMEWORK_CONFIG.getParserConfig());
+        StatementParseResult parseResult = IgniteSqlParser.parse(query, 
StatementParseResult.MODE);
 
-        assertThat(nodes, hasSize(1));
+        assertEquals(ctx.parameters().length, 
parseResult.dynamicParamsCount(), "Invalid number of dynamic parameters");
 
-        return await(prepareService.prepareAsync(nodes.get(0), ctx));
+        return await(prepareService.prepareAsync(parseResult.statement(), 
ctx));
     }
 
     static class TestCluster {
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
index b6230a4d26..9678100c33 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
@@ -21,8 +21,8 @@ import static 
org.apache.ignite.internal.sql.engine.util.Commons.FRAMEWORK_CONFI
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.Mockito.mock;
 
 import java.util.ArrayList;
@@ -59,8 +59,9 @@ import 
org.apache.ignite.internal.sql.engine.prepare.QueryPlan;
 import 
org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverter;
 import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
 import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManager;
+import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
+import org.apache.ignite.internal.sql.engine.sql.StatementParseResult;
 import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
-import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.internal.sql.engine.util.HashFunctionFactoryImpl;
 import org.apache.ignite.internal.util.IgniteSpinBusyLock;
 import org.apache.ignite.internal.util.IgniteUtils;
@@ -177,11 +178,12 @@ public class TestNode implements LifecycleAware {
      * @return A plan to execute.
      */
     public QueryPlan prepare(String query) {
-        SqlNodeList nodes = Commons.parse(query, 
FRAMEWORK_CONFIG.getParserConfig());
+        StatementParseResult parseResult = IgniteSqlParser.parse(query, 
StatementParseResult.MODE);
+        BaseQueryContext ctx = createContext();
 
-        assertThat(nodes, hasSize(1));
+        assertEquals(ctx.parameters().length, 
parseResult.dynamicParamsCount(), "Invalid number of dynamic parameters");
 
-        return await(prepareService.prepareAsync(nodes.get(0), 
createContext()));
+        return await(prepareService.prepareAsync(parseResult.statement(), 
ctx));
     }
 
     /**
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/AbstractDdlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/AbstractDdlParserTest.java
index 1570d46e39..9135cc01b3 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/AbstractDdlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/AbstractDdlParserTest.java
@@ -19,9 +19,6 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.function.Predicate;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.parser.SqlParseException;
-import org.apache.calcite.sql.parser.SqlParser;
-import 
org.apache.ignite.internal.generated.query.calcite.sql.IgniteSqlParserImpl;
 import org.hamcrest.CustomMatcher;
 import org.hamcrest.Matcher;
 
@@ -35,10 +32,9 @@ public abstract class AbstractDdlParserTest {
      * @param stmt Statement to parse.
      * @return An AST.
      */
-    protected SqlNode parse(String stmt) throws SqlParseException {
-        SqlParser parser = SqlParser.create(stmt, 
SqlParser.config().withParserFactory(IgniteSqlParserImpl.FACTORY));
-
-        return parser.parseStmt();
+    protected SqlNode parse(String stmt) {
+        StatementParseResult parseResult = IgniteSqlParser.parse(stmt, 
StatementParseResult.MODE);
+        return parseResult.statement();
     }
 
     /**
@@ -50,7 +46,7 @@ public abstract class AbstractDdlParserTest {
      * @return {@code true} in case the object if instance of the given class 
and matches the predicat.
      */
     protected <T> Matcher<T> ofTypeMatching(String desc, Class<T> cls, 
Predicate<T> pred) {
-        return new CustomMatcher<T>(desc) {
+        return new CustomMatcher<>(desc) {
             /** {@inheritDoc} */
             @Override
             public boolean matches(Object item) {
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
index 6362f751e6..ffa0fa2a2b 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
@@ -35,8 +35,8 @@ import java.util.Objects;
 import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlWriter;
-import org.apache.calcite.sql.parser.SqlParseException;
 import org.apache.calcite.sql.pretty.SqlPrettyWriter;
+import org.apache.ignite.sql.SqlException;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.Test;
 
@@ -53,7 +53,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      * Parse simple CREATE ZONE statement.
      */
     @Test
-    public void createZoneNoOptions() throws SqlParseException {
+    public void createZoneNoOptions() {
         // Simple name.
         IgniteSqlCreateZone createZone = parseCreateZone("create zone 
test_zone");
 
@@ -77,7 +77,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      * Parse CREATE ZONE IF NOT EXISTS statement.
      */
     @Test
-    public void createZoneIfNotExists() throws SqlParseException {
+    public void createZoneIfNotExists() {
         IgniteSqlCreateZone createZone = parseCreateZone("create zone if not 
exists test_zone");
 
         assertTrue(createZone.ifNotExists());
@@ -88,7 +88,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      * Parse CREATE ZONE WITH ... statement.
      */
     @Test
-    public void createZoneWithOptions() throws SqlParseException {
+    public void createZoneWithOptions() {
         IgniteSqlCreateZone createZone = parseCreateZone(
                 "create zone test_zone with "
                         + "replicas=2, "
@@ -126,7 +126,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
     @Test
     public void createZoneWithInvalidOptions() {
         // Unknown option.
-        assertThrows(SqlParseException.class, () -> parseCreateZone("create 
zone test_zone with foo='bar'"));
+        assertThrows(SqlException.class, () -> parseCreateZone("create zone 
test_zone with foo='bar'"));
 
         // Invalid option type.
         String query = "create zone test_zone with %s=%s";
@@ -144,7 +144,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      * Parsing DROP ZONE statement.
      */
     @Test
-    public void dropZone() throws SqlParseException {
+    public void dropZone() {
         // Simple name.
         SqlNode node = parse("drop zone test_zone");
 
@@ -165,7 +165,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      * Parsing DROP ZONE IF EXISTS statement.
      */
     @Test
-    public void dropZoneIfExists() throws SqlParseException {
+    public void dropZoneIfExists() {
         IgniteSqlDropZone dropZone = (IgniteSqlDropZone) parse("drop zone if 
exists test_zone");
 
         assertTrue(dropZone.ifExists());
@@ -177,7 +177,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      * Parsing ALTER ZONE RENAME TO statement.
      */
     @Test
-    public void alterZoneRenameTo() throws SqlParseException {
+    public void alterZoneRenameTo() {
         IgniteSqlAlterZoneRenameTo alterZone = parseAlterZoneRenameTo("alter 
zone a.test_zone rename to zone1");
         assertFalse(alterZone.ifExists());
 
@@ -189,7 +189,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      * Parsing ALTER ZONE RENAME TO statement.
      */
     @Test
-    public void alterZoneIfExistsRenameTo() throws SqlParseException {
+    public void alterZoneIfExistsRenameTo() {
         IgniteSqlAlterZoneRenameTo alterZone = parseAlterZoneRenameTo("alter 
zone if exists a.test_zone rename to zone1");
         assertTrue(alterZone.ifExists());
 
@@ -202,14 +202,14 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      */
     @Test
     public void alterZoneRenameToWithCompoundIdIsIllegal() {
-        assertThrows(SqlParseException.class, () -> parse("alter zone 
a.test_zone rename to b.zone1"));
+        assertThrows(SqlException.class, () -> parse("alter zone a.test_zone 
rename to b.zone1"));
     }
 
     /**
      * Parsing ALTER ZONE SET statement.
      */
     @Test
-    public void alterZoneSet() throws SqlParseException {
+    public void alterZoneSet() {
         IgniteSqlAlterZoneSet alterZoneSet = parseAlterZoneSet("alter zone 
a.test_zone set replicas=2");
         assertFalse(alterZoneSet.ifExists());
 
@@ -221,7 +221,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      * Parsing ALTER ZONE IF EXISTS SET statement.
      */
     @Test
-    public void alterZoneIfExistsSet() throws SqlParseException {
+    public void alterZoneIfExistsSet() {
         IgniteSqlAlterZoneSet alterZoneSet = parseAlterZoneSet("alter zone if 
exists a.test_zone set replicas=2");
         assertTrue(alterZoneSet.ifExists());
 
@@ -233,7 +233,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      * Parsing ALTER ZONE SET statement.
      */
     @Test
-    public void alterZoneSetOptions() throws SqlParseException {
+    public void alterZoneSetOptions() {
         IgniteSqlAlterZoneSet alterZoneSet = parseAlterZoneSet(
                 "alter zone a.test_zone set "
                         + "replicas=2, "
@@ -267,7 +267,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      */
     @Test
     public void alterZoneSetNoOptionsIsIllegal() {
-        assertThrows(SqlParseException.class, () -> parse("alter zone 
test_zone set"));
+        assertThrows(SqlException.class, () -> parse("alter zone test_zone 
set"));
     }
 
     /**
@@ -279,7 +279,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
 
         // invalid option
 
-        assertThrows(SqlParseException.class, () -> parse(String.format(query, 
"foo", "'bar'")));
+        assertThrows(SqlException.class, () -> parse(String.format(query, 
"foo", "'bar'")));
 
         // invalid option values
 
@@ -303,7 +303,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
      * @param stmt Create zone query.
      * @return {@link org.apache.calcite.sql.SqlCreate SqlCreate} node.
      */
-    private IgniteSqlCreateZone parseCreateZone(String stmt) throws 
SqlParseException {
+    private IgniteSqlCreateZone parseCreateZone(String stmt) {
         SqlNode node = parse(stmt);
 
         return assertInstanceOf(IgniteSqlCreateZone.class, node);
@@ -312,7 +312,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
     /**
      * Parse ALTER ZONE SET statement.
      */
-    private IgniteSqlAlterZoneSet parseAlterZoneSet(String stmt) throws 
SqlParseException {
+    private IgniteSqlAlterZoneSet parseAlterZoneSet(String stmt) {
         SqlNode node = parse(stmt);
 
         return assertInstanceOf(IgniteSqlAlterZoneSet.class, node);
@@ -321,7 +321,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
     /**
      * Parse ALTER ZONE RENAME TO statement.
      */
-    private IgniteSqlAlterZoneRenameTo parseAlterZoneRenameTo(String stmt) 
throws SqlParseException {
+    private IgniteSqlAlterZoneRenameTo parseAlterZoneRenameTo(String stmt) {
         SqlNode node = parse(stmt);
 
         return assertInstanceOf(IgniteSqlAlterZoneRenameTo.class, node);
@@ -338,7 +338,7 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
     }
 
     private void assertSqlParseError(String stmt, String name) {
-        assertThrows(SqlParseException.class, () -> parse(stmt), name);
+        assertThrows(SqlException.class, () -> parse(stmt), name);
     }
 
     private void assertThatZoneOptionPresent(List<SqlNode> optionList, 
IgniteSqlZoneOptionEnum name, Object expVal) {
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDecimalLiteralTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDecimalLiteralTest.java
index 1fda6fbab9..c5eb42b08a 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDecimalLiteralTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDecimalLiteralTest.java
@@ -28,7 +28,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.math.BigDecimal;
 import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.pretty.SqlPrettyWriter;
@@ -36,7 +35,6 @@ import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.util.Litmus;
 import org.apache.ignite.internal.sql.engine.planner.AbstractPlannerTest;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
-import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.sql.SqlException;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -150,12 +148,13 @@ public class IgniteSqlDecimalLiteralTest extends 
AbstractPlannerTest {
      */
     @Test
     public void testDecimalAsAlias() {
-        SqlNodeList nodeList = parseQuery("SELECT DECIMAL \"10\"");
+        SqlNode node = parseQuery("SELECT DECIMAL \"10\"");
 
-        assertEquals("SELECT `DECIMAL` AS `10`", nodeList.get(0).toString());
+        assertEquals("SELECT `DECIMAL` AS `10`", node.toString());
     }
 
-    private static SqlNodeList parseQuery(String qry) {
-        return Commons.parse(qry, Commons.PARSER_CONFIG);
+    private static SqlNode parseQuery(String qry) {
+        StatementParseResult parseResult = IgniteSqlParser.parse(qry, 
StatementParseResult.MODE);
+        return parseResult.statement();
     }
 }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParserTest.java
new file mode 100644
index 0000000000..af5d00810c
--- /dev/null
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParserTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.sql;
+
+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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.List;
+import org.apache.ignite.sql.SqlException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for sql parser.
+ */
+public class IgniteSqlParserTest {
+
+    @Test
+    public void testStatementMode() {
+        StatementParseResult result = IgniteSqlParser.parse("SELECT 1 + ?", 
StatementParseResult.MODE);
+
+        assertEquals(1, result.dynamicParamsCount());
+        assertNotNull(result.statement());
+        assertEquals(List.of(result.statement()), result.statements());
+    }
+
+    @Test
+    public void testScriptMode() {
+        ScriptParseResult result = IgniteSqlParser.parse("SELECT 1 + ?; SELECT 
1; SELECT 2 + ?", ScriptParseResult.MODE);
+
+        assertEquals(2, result.dynamicParamsCount());
+        assertEquals(3, result.statements().size());
+    }
+
+    /**
+     * {@link StatementParseResult#MODE} does not allow input that contains 
multiple statements.
+     */
+    @Test
+    public void testStatementModeRejectMultipleStatements() {
+        SqlException t = assertThrows(SqlException.class,
+                () -> IgniteSqlParser.parse("SELECT 1; SELECT 2", 
StatementParseResult.MODE));
+        assertThat(t.getMessage(), containsString("Multiple statements are not 
allowed"));
+
+    }
+}
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
index fcfc62c586..bcfbfc61ea 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
@@ -40,8 +40,8 @@ import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.ddl.SqlColumnDeclaration;
 import org.apache.calcite.sql.ddl.SqlKeyConstraint;
-import org.apache.calcite.sql.parser.SqlParseException;
 import org.apache.calcite.sql.pretty.SqlPrettyWriter;
+import org.apache.ignite.sql.SqlException;
 import org.hamcrest.CustomMatcher;
 import org.hamcrest.Matcher;
 import org.junit.jupiter.api.Test;
@@ -54,7 +54,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest {
      * Very simple case where only table name and a few columns are presented.
      */
     @Test
-    public void createTableSimpleCase() throws SqlParseException {
+    public void createTableSimpleCase() {
         String query = "create table my_table(id int, val varchar)";
 
         SqlNode node = parse(query);
@@ -73,7 +73,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest {
      * Parsing of CREATE TABLE with function identifier as a default 
expression.
      */
     @Test
-    public void createTableAutogenFuncDefault() throws SqlParseException {
+    public void createTableAutogenFuncDefault() {
         String query = "create table my_table(id varchar default 
gen_random_uuid primary key, val varchar)";
 
         SqlNode node = parse(query);
@@ -102,7 +102,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
      * Parsing of CREATE TABLE statement with quoted identifiers.
      */
     @Test
-    public void createTableQuotedIdentifiers() throws SqlParseException {
+    public void createTableQuotedIdentifiers() {
         String query = "create table \"My_Table\"(\"Id\" int, \"Val\" 
varchar)";
 
         SqlNode node = parse(query);
@@ -121,7 +121,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
      * Parsing of CREATE TABLE statement with IF NOT EXISTS.
      */
     @Test
-    public void createTableIfNotExists() throws SqlParseException {
+    public void createTableIfNotExists() {
         String query = "create table if not exists my_table(id int, val 
varchar)";
 
         SqlNode node = parse(query);
@@ -140,7 +140,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
      * Parsing of CREATE TABLE with specified PK constraint where constraint 
is a shortcut within a column definition.
      */
     @Test
-    public void createTableWithPkCase1() throws SqlParseException {
+    public void createTableWithPkCase1() {
         String query = "create table my_table(id int primary key, val 
varchar)";
 
         SqlNode node = parse(query);
@@ -163,7 +163,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
      * Parsing of CREATE TABLE with specified PK constraint where constraint 
is set explicitly and has no name.
      */
     @Test
-    public void createTableWithPkCase2() throws SqlParseException {
+    public void createTableWithPkCase2() {
         String query = "create table my_table(id int, val varchar, primary 
key(id))";
 
         SqlNode node = parse(query);
@@ -186,7 +186,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
      * Parsing of CREATE TABLE with specified PK constraint where constraint 
is set explicitly and has a name.
      */
     @Test
-    public void createTableWithPkCase3() throws SqlParseException {
+    public void createTableWithPkCase3() {
         String query = "create table my_table(id int, val varchar, constraint 
pk_key primary key(id))";
 
         SqlNode node = parse(query);
@@ -209,7 +209,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
      * Parsing of CREATE TABLE with specified PK constraint where constraint 
consists of several columns.
      */
     @Test
-    public void createTableWithPkCase4() throws SqlParseException {
+    public void createTableWithPkCase4() {
         String query = "create table my_table(id1 int, id2 int, val varchar, 
primary key(id1, id2))";
 
         SqlNode node = parse(query);
@@ -234,7 +234,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
      * Parsing of CREATE TABLE with specified colocation columns.
      */
     @Test
-    public void createTableWithColocationBy() throws SqlParseException {
+    public void createTableWithColocationBy() {
         IgniteSqlCreateTable createTable;
 
         createTable = parseCreateTable(
@@ -287,7 +287,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void createTableWithEngine() throws SqlParseException {
+    public void createTableWithEngine() {
         SqlNode node = parse("create table my_table(id int, val varchar) 
engine test_engine_name");
 
         assertThat(node, instanceOf(IgniteSqlCreateTable.class));
@@ -299,7 +299,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void createTableWithoutEngine() throws SqlParseException {
+    public void createTableWithoutEngine() {
         SqlNode node = parse("create table my_table(id int, val varchar)");
 
         assertThat(node, instanceOf(IgniteSqlCreateTable.class));
@@ -311,7 +311,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void createTableWithOptions() throws SqlParseException {
+    public void createTableWithOptions() {
         String query = "create table my_table(id int) with"
                 + " replicas=2,"
                 + " partitions=3,"
@@ -335,7 +335,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void createIndexSimpleCase() throws SqlParseException {
+    public void createIndexSimpleCase() {
         var query = "create index my_index on my_table (col)";
 
         SqlNode node = parse(query);
@@ -353,7 +353,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void createIndexImplicitTypeExplicitDirection() throws 
SqlParseException {
+    public void createIndexImplicitTypeExplicitDirection() {
         var query = "create index my_index on my_table (col1 asc, col2 desc)";
 
         SqlNode node = parse(query);
@@ -377,7 +377,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void createIndexExplicitTypeMixedDirection() throws 
SqlParseException {
+    public void createIndexExplicitTypeMixedDirection() {
         var query = "create index my_index on my_table using tree (col1, col2 
asc, col3 desc)";
 
         SqlNode node = parse(query);
@@ -403,7 +403,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void createHashIndex() throws SqlParseException {
+    public void createHashIndex() {
         var query = "create index my_index on my_table using hash (col)";
 
         SqlNode node = parse(query);
@@ -424,12 +424,12 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
     public void sortDirectionMustNotBeSpecifiedForHashIndex() {
         var query = "create index my_index on my_table using hash (col1, col2 
asc)";
 
-        var ex = assertThrows(SqlParseException.class, () -> parse(query));
+        var ex = assertThrows(SqlException.class, () -> parse(query));
         assertThat(ex.getMessage(), containsString("Encountered \" \"ASC\""));
     }
 
     @Test
-    public void createIndexIfNotExists() throws SqlParseException {
+    public void createIndexIfNotExists() {
         var query = "create index if not exists my_index on my_table (col)";
 
         SqlNode node = parse(query);
@@ -444,7 +444,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void createIndexTableInParticularSchema() throws SqlParseException {
+    public void createIndexTableInParticularSchema() {
         var query = "create index my_index on my_schema.my_table (col)";
 
         SqlNode node = parse(query);
@@ -458,7 +458,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void createIndexExplicitNullDirection() throws SqlParseException {
+    public void createIndexExplicitNullDirection() {
         var query = "create index my_index on my_table (col1 nulls first, col2 
nulls last, col3 desc nulls first)";
 
         SqlNode node = parse(query);
@@ -494,7 +494,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void dropIndexSimpleCase() throws SqlParseException {
+    public void dropIndexSimpleCase() {
         var query = "drop index my_index";
 
         SqlNode node = parse(query);
@@ -508,7 +508,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void dropIndexSchemaSpecified() throws SqlParseException {
+    public void dropIndexSchemaSpecified() {
         var query = "drop index my_schema.my_index";
 
         SqlNode node = parse(query);
@@ -522,7 +522,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
     }
 
     @Test
-    public void dropIndexIfExists() throws SqlParseException {
+    public void dropIndexIfExists() {
         var query = "drop index if exists my_index";
 
         SqlNode node = parse(query);
@@ -535,7 +535,7 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
         assertThat(dropIndex.indexName().names, is(List.of("MY_INDEX")));
     }
 
-    private IgniteSqlCreateTable parseCreateTable(String stmt) throws 
SqlParseException {
+    private IgniteSqlCreateTable parseCreateTable(String stmt) {
         SqlNode node = parse(stmt);
 
         assertThat(node, instanceOf(IgniteSqlCreateTable.class));

Reply via email to