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

amashenkov pushed a commit to branch ignite-26446
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 0d0da878c377bede42fd7601f215f71fb0ea1bb9
Author: amashenkov <[email protected]>
AuthorDate: Wed Oct 22 15:38:07 2025 +0300

    wip
---
 .../sql/engine/exec/fsm/DdlBatchingHelper.java     | 79 ++++++++++++++++++++++
 .../sql/engine/exec/fsm/MultiStatementHandler.java |  9 ++-
 .../sql/engine/exec/fsm/QueryExecutor.java         |  4 ++
 .../sql/engine/sql/IgniteSqlCreateSchema.java      |  2 +-
 .../sql/engine/sql/IgniteSqlDropSchema.java        |  2 +-
 .../internal/sql/engine/sql/ParserServiceImpl.java |  4 +-
 .../internal/sql/engine/exec/DdlBatchingTest.java  | 52 +++++++++++---
 7 files changed, 138 insertions(+), 14 deletions(-)

diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/DdlBatchingHelper.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/DdlBatchingHelper.java
new file mode 100644
index 00000000000..6e9cf5cddb4
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/DdlBatchingHelper.java
@@ -0,0 +1,79 @@
+/*
+ * 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.exec.fsm;
+
+import java.util.List;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlNode;
+
+/**
+ *  Provide helper methods for batched DDL commands.
+ */
+class DdlBatchingHelper {
+    /**
+     * Return {@code true} if a DDL command is compatible with batched DDL 
commands and can be added to the batch.
+     *
+     * @param batch DDL commands represented by AST trees.
+     * @param statement A DDL statement represented by AST tree
+     * @return {@code true} if statement can be added to the batch, {@code 
false} otherwise.
+     */
+    static boolean canBeBatched(List<SqlNode> batch, SqlNode statement) {
+        return batch.stream().allMatch(s -> isCompatible(s, statement));
+    }
+
+    /** Returns {@code true} if commands (represented by AST trees) can be 
executed together, {@code false} otherwise. */
+    private static boolean isCompatible(SqlNode node1, SqlNode node2) {
+        DdlCommandType kind1 = getCommandType(node1);
+        DdlCommandType kind2 = getCommandType(node2);
+
+        return kind1 == kind2 && kind1 != DdlCommandType.OTHER;
+    }
+
+    /** Returns command kind. */
+    private static DdlCommandType getCommandType(SqlNode node) {
+        SqlKind kind = node.getKind();
+
+        switch (kind) {
+            case CREATE_SCHEMA:
+            case CREATE_TABLE:
+            case CREATE_SEQUENCE:
+            case CREATE_INDEX:
+                return DdlCommandType.CREATE;
+
+            case DROP_SCHEMA:
+            case DROP_TABLE:
+            case DROP_INDEX:
+            case DROP_SEQUENCE:
+                return DdlCommandType.DROP;
+
+            case OTHER_DDL:
+            default:
+                return DdlCommandType.OTHER;
+        }
+    }
+
+    /** Describes DDL command kind. */
+    private enum DdlCommandType {
+        /** Create command. */
+        CREATE,
+        /** Drop command. */
+        DROP,
+        /** Any other DDL command (ALTER command, zone commands, and others) */
+        OTHER
+    }
+}
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/MultiStatementHandler.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/MultiStatementHandler.java
index 31f8a2a5925..6f107dcc658 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/MultiStatementHandler.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/MultiStatementHandler.java
@@ -30,6 +30,7 @@ import java.util.Queue;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import org.apache.calcite.sql.SqlNode;
 import org.apache.ignite.internal.sql.ResultSetMetadataImpl;
 import org.apache.ignite.internal.sql.engine.AsyncSqlCursor;
 import org.apache.ignite.internal.sql.engine.AsyncSqlCursorImpl;
@@ -43,6 +44,7 @@ import org.apache.ignite.internal.sql.engine.sql.ParsedResult;
 import org.apache.ignite.internal.sql.engine.tx.QueryTransactionContext;
 import org.apache.ignite.internal.sql.engine.tx.ScriptTransactionContext;
 import org.apache.ignite.internal.sql.engine.util.IteratorToDataCursorAdapter;
+import org.apache.ignite.internal.util.CollectionUtils;
 import org.apache.ignite.sql.ResultSetMetadata;
 import org.apache.ignite.sql.SqlException;
 import org.jetbrains.annotations.Nullable;
@@ -177,6 +179,11 @@ class MultiStatementHandler {
                         break;
                     }
 
+                    List<SqlNode> batch = CollectionUtils.view(ddlBatch, s -> 
s.parsedQuery().parsedTree());
+                    if (!DdlBatchingHelper.canBeBatched(batch, 
statement.parsedResult.parsedTree())) {
+                        break;
+                    }
+
                     scriptStatement = statement;
 
                     statements.poll();
@@ -299,7 +306,7 @@ class MultiStatementHandler {
                 .whenComplete((ignored, ex) -> 
query.moveTo(ExecutionPhase.TERMINATED));
     }
 
-    private static class ScriptStatement {
+    static class ScriptStatement {
         private final CompletableFuture<AsyncSqlCursor<InternalSqlRow>> 
cursorFuture = new CompletableFuture<>();
         private final CompletableFuture<AsyncSqlCursor<InternalSqlRow>> 
nextStatementFuture;
         private final ParsedResult parsedResult;
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/QueryExecutor.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/QueryExecutor.java
index c7fba380251..b837ba2bad0 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/QueryExecutor.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/fsm/QueryExecutor.java
@@ -623,6 +623,10 @@ public class QueryExecutor implements LifecycleAware, 
Debuggable {
             this.parsedQuery = parsedQuery;
             this.nextCursorFuture = nextCursorFuture;
         }
+
+        public ParsedResult parsedQuery() {
+            return parsedQuery;
+        }
     }
 
     @Override
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateSchema.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateSchema.java
index 1de9c2508a4..edeccec56c6 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateSchema.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateSchema.java
@@ -40,7 +40,7 @@ public class IgniteSqlCreateSchema extends SqlCreate {
 
         /** Constructor. */
         protected Operator(boolean existFlag) {
-            super("CREATE SCHEMA", SqlKind.OTHER_DDL, existFlag);
+            super("CREATE SCHEMA", SqlKind.CREATE_SCHEMA, existFlag);
         }
 
         /** {@inheritDoc} */
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropSchema.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropSchema.java
index b94dcbf315c..5235127730e 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropSchema.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropSchema.java
@@ -41,7 +41,7 @@ public class IgniteSqlDropSchema extends SqlDrop {
 
         /** Constructor. */
         protected Operator(boolean existFlag, IgniteSqlDropSchemaBehavior 
dropBehavior) {
-            super("DROP SCHEMA", SqlKind.OTHER_DDL, existFlag);
+            super("DROP SCHEMA", SqlKind.DROP_SCHEMA, existFlag);
 
             this.dropBehavior = dropBehavior;
         }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParserServiceImpl.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParserServiceImpl.java
index 94b808ac8b1..90fe72d2771 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParserServiceImpl.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParserServiceImpl.java
@@ -86,7 +86,9 @@ public class ParserServiceImpl implements ParserService {
                     normalizedQuery,
                     result.dynamicParamsCount(),
                     () -> {
-                        if (queryType != SqlQueryType.TX_CONTROL && 
!used.compareAndSet(false, true)) {
+                        if (queryType != SqlQueryType.TX_CONTROL && queryType 
!= SqlQueryType.DDL
+                                && !used.compareAndSet(false, true)
+                        ) {
                             throw new IllegalStateException("Parsed result of 
script is not reusable.");
                         }
 
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/DdlBatchingTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/DdlBatchingTest.java
index 2d9d9fb12b2..d1f1dd8736f 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/DdlBatchingTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/DdlBatchingTest.java
@@ -204,11 +204,10 @@ public class DdlBatchingTest extends 
BaseIgniteAbstractTest {
                 "CREATE TABLE t1 (id INT PRIMARY KEY, val_1 INT, val_2 INT);"
                         + "ALTER TABLE t1 ADD COLUMN val_3 INT;"
                         + "ALTER TABLE t1 DROP COLUMN val_2;"
-                        + "CREATE INDEX t1_ind_3 ON t1 (val_3);"
+                        + "CREATE TABLE t2 (id INT PRIMARY KEY, val_1 INT, 
val_2 INT);"
         );
 
         // CREATE TABLE t1 (id INT PRIMARY KEY, val_1 INT, val_2 INT)
-        cursor = cursor.nextResult().join();
         assertDdlResult(cursor, true);
         assertThat(cursor.hasNextResult(), is(true));
         assertThat(cursor.nextResult(), willSucceedFast());
@@ -225,7 +224,7 @@ public class DdlBatchingTest extends BaseIgniteAbstractTest 
{
         assertThat(cursor.hasNextResult(), is(true));
         assertThat(cursor.nextResult(), willSucceedFast());
 
-        // CREATE INDEX t1_ind_3 ON t1 (val_3)
+        // CREATE TABLE t2 (id INT PRIMARY KEY, val_1 INT, val_2 INT)
         cursor = cursor.nextResult().join();
         assertDdlResult(cursor, true);
         assertThat(cursor.hasNextResult(), is(false));
@@ -234,7 +233,7 @@ public class DdlBatchingTest extends BaseIgniteAbstractTest 
{
         assertEquals(4, executeCallCounter.get());
 
         assertTableExists("t1");
-        assertIndexExists("t1_ind_3");
+        assertTableExists("t2");
     }
 
     @Test
@@ -243,15 +242,17 @@ public class DdlBatchingTest extends 
BaseIgniteAbstractTest {
         AsyncSqlCursor<InternalSqlRow> cursor = gatewayNode.executeQuery(
                 "CREATE TABLE t1 (id INT PRIMARY KEY, val_1 INT, val_2 INT);"
                         + "CREATE TABLE t2 (id INT PRIMARY KEY, val_1 INT, 
val_2 INT);"
+                        + "CREATE TABLE t3 (id INT PRIMARY KEY, val_1 INT, 
val_2 INT);"
+                        + "CREATE INDEX t1_ind_1 ON t1 (val_1);"
                         + "CREATE INDEX t2_ind_1 ON t2 (val_1);"
-                        + "CREATE INDEX t2_ind_2 ON t2 (val_2);"
-                        + "DROP TABLE t2;"
+                        + "CREATE INDEX t3_ind_1 ON t3 (val_1);"
                         + "DROP TABLE t1;"
+                        + "DROP TABLE t2;"
+                        + "DROP INDEX t3_ind_1;"
                         + "CREATE TABLE t1 (id INT PRIMARY KEY, val_1 INT, 
val_2 INT);"
         );
 
         // CREATE TABLE t1 (id INT PRIMARY KEY, val_1 INT, val_2 INT)
-        cursor = cursor.nextResult().join();
         assertDdlResult(cursor, true);
         assertThat(cursor.hasNextResult(), is(true));
         assertThat(cursor.nextResult(), willSucceedFast());
@@ -262,29 +263,60 @@ public class DdlBatchingTest extends 
BaseIgniteAbstractTest {
         assertThat(cursor.hasNextResult(), is(true));
         assertThat(cursor.nextResult(), willSucceedFast());
 
+        // CREATE TABLE t3 (id INT PRIMARY KEY, val_1 INT, val_2 INT)
+        cursor = cursor.nextResult().join();
+        assertDdlResult(cursor, true);
+        assertThat(cursor.hasNextResult(), is(true));
+        assertThat(cursor.nextResult(), willSucceedFast());
+
+        // CREATE INDEX t1_ind_1 ON t1 (val_1)
+        cursor = cursor.nextResult().join();
+        assertDdlResult(cursor, true);
+        assertThat(cursor.hasNextResult(), is(true));
+        assertThat(cursor.nextResult(), willSucceedFast());
+
         // CREATE INDEX t2_ind_1 ON t2 (val_1)
         cursor = cursor.nextResult().join();
         assertDdlResult(cursor, true);
         assertThat(cursor.hasNextResult(), is(true));
         assertThat(cursor.nextResult(), willSucceedFast());
 
+        // CREATE INDEX t3_ind_1 ON t3 (val_1)
+        cursor = cursor.nextResult().join();
+        assertDdlResult(cursor, true);
+        assertThat(cursor.hasNextResult(), is(true));
+        assertThat(cursor.nextResult(), willSucceedFast());
+
+        // DROP TABLE t1
+        cursor = cursor.nextResult().join();
+        assertDdlResult(cursor, true);
+        assertThat(cursor.hasNextResult(), is(true));
+        assertThat(cursor.nextResult(), willSucceedFast());
+
         // DROP TABLE t2
         cursor = cursor.nextResult().join();
         assertDdlResult(cursor, true);
         assertThat(cursor.hasNextResult(), is(true));
         assertThat(cursor.nextResult(), willSucceedFast());
 
-        // CCREATE INDEX t1_ind_3 ON t1 (val_3)
+        // DROP INDEX t3_ind_1
+        cursor = cursor.nextResult().join();
+        assertDdlResult(cursor, true);
+        assertThat(cursor.hasNextResult(), is(true));
+        assertThat(cursor.nextResult(), willSucceedFast());
+
+        // CREATE TABLE t1 (id INT PRIMARY KEY, val_1 INT, val_2 INT)
         cursor = cursor.nextResult().join();
         assertDdlResult(cursor, true);
         assertThat(cursor.hasNextResult(), is(false));
 
-        // ALTER splits the batch
         assertEquals(3, executeCallCounter.get());
         assertTableExists("t1");
+        assertTableExists("t3");
         assertTableNotExists("t2");
-        assertIndexNotExists("t2_ind_1");
+        assertIndexNotExists("t1_ind_1");
         assertIndexNotExists("t2_ind_2");
+        assertIndexNotExists("t3_ind_1");
     }
 
     @Test

Reply via email to