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
