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

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


The following commit(s) were added to refs/heads/main by this push:
     new 0202698b703 IGNITE-27323 Add suggestion about DDL queries batching 
(#7246)
0202698b703 is described below

commit 0202698b703224d4504aa45836ca6b3c9691052d
Author: Pavel Pereslegin <[email protected]>
AuthorDate: Tue Dec 30 13:14:37 2025 +0300

    IGNITE-27323 Add suggestion about DDL queries batching (#7246)
---
 .../repl/executor/ItIgnitePicocliCommandsTest.java |   3 +-
 .../apache/ignite/client/handler/TestServer.java   |   3 +-
 .../ignite/client/handler/ClientHandlerModule.java |  13 +-
 .../handler/ClientInboundMessageHandler.java       |  17 ++-
 .../client/handler/DdlBatchingSuggester.java       |  72 ++++++++++
 .../requests/sql/ClientSqlExecuteRequest.java      |   9 +-
 .../client/handler/DdlBatchingSuggesterTest.java   |  62 ++++++++
 .../ignite/client/TestClientHandlerModule.java     |   3 +-
 .../java/org/apache/ignite/client/TestServer.java  |   3 +-
 ...SequentialDdlExecutionConfigurationSchema.java} |  29 ++--
 ...stionsClusterExtensionConfigurationSchema.java} |  26 ++--
 .../SuggestionsConfigurationSchema.java}           |  29 ++--
 ...SuggestionsDistributedConfigurationModule.java} |  31 ++--
 .../client/ItClientDataConsistencyTest.java        |   6 +
 .../app/client/ItThinClientAuthenticationTest.java |  10 +-
 .../client/ItThinClientDdlQueriesTrackerTest.java  | 156 +++++++++++++++++++++
 .../org/apache/ignite/internal/app/IgniteImpl.java |   8 +-
 .../configuration/ignite-snapshot.bin              | Bin 5679 -> 5653 bytes
 18 files changed, 391 insertions(+), 89 deletions(-)

diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/core/repl/executor/ItIgnitePicocliCommandsTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/core/repl/executor/ItIgnitePicocliCommandsTest.java
index 3ce9526dfbd..e8dec2709a4 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/core/repl/executor/ItIgnitePicocliCommandsTest.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/core/repl/executor/ItIgnitePicocliCommandsTest.java
@@ -75,7 +75,8 @@ public class ItIgnitePicocliCommandsTest extends 
CliIntegrationTest {
             "ignite.security",
             "ignite.sql",
             "ignite.transaction",
-            "ignite.system"
+            "ignite.system",
+            "ignite.suggestions"
     };
 
     private static final String[] LOCAL_CONFIGURATION_KEYS = {
diff --git 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
index fd335df4b77..40982108dba 100644
--- 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
+++ 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
@@ -142,7 +142,8 @@ public class TestServer {
                 clientConnectorConfiguration,
                 new TestLowWatermark(),
                 new SystemPropertiesNodeProperties(),
-                Runnable::run
+                Runnable::run,
+                () -> true
         );
 
         module.startAsync(componentContext).join();
diff --git 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
index e77817bd3f0..6ed8229cf1c 100644
--- 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
+++ 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
@@ -152,6 +152,8 @@ public class ClientHandlerModule implements 
IgniteComponent, PlatformComputeTran
 
     private final ClientConnectorConfiguration clientConnectorConfiguration;
 
+    private final Supplier<Boolean> ddlBatchingSuggestionEnabled;
+
     private final Executor partitionOperationsExecutor;
 
     private final ConcurrentHashMap<String, 
CompletableFuture<PlatformComputeConnection>> computeExecutors = new 
ConcurrentHashMap<>();
@@ -176,6 +178,7 @@ public class ClientHandlerModule implements 
IgniteComponent, PlatformComputeTran
      * @param clientConnectorConfiguration Configuration of the connector.
      * @param lowWatermark Low watermark.
      * @param partitionOperationsExecutor Executor for a partition operation.
+     * @param ddlBatchingSuggestionEnabled Boolean supplier indicates whether 
the suggestion related DDL batching is enabled.
      */
     public ClientHandlerModule(
             QueryProcessor queryProcessor,
@@ -195,7 +198,8 @@ public class ClientHandlerModule implements 
IgniteComponent, PlatformComputeTran
             ClientConnectorConfiguration clientConnectorConfiguration,
             LowWatermark lowWatermark,
             NodeProperties nodeProperties,
-            Executor partitionOperationsExecutor
+            Executor partitionOperationsExecutor,
+            Supplier<Boolean> ddlBatchingSuggestionEnabled
     ) {
         assert igniteTables != null;
         assert queryProcessor != null;
@@ -212,6 +216,7 @@ public class ClientHandlerModule implements 
IgniteComponent, PlatformComputeTran
         assert catalogService != null;
         assert placementDriver != null;
         assert clientConnectorConfiguration != null;
+        assert ddlBatchingSuggestionEnabled != null;
         assert lowWatermark != null;
         assert nodeProperties != null;
         assert partitionOperationsExecutor != null;
@@ -232,6 +237,7 @@ public class ClientHandlerModule implements 
IgniteComponent, PlatformComputeTran
         this.primaryReplicaTracker = new 
ClientPrimaryReplicaTracker(placementDriver, catalogService, clockService, 
schemaSyncService,
                 lowWatermark, nodeProperties);
         this.clientConnectorConfiguration = clientConnectorConfiguration;
+        this.ddlBatchingSuggestionEnabled = ddlBatchingSuggestionEnabled;
         this.partitionOperationsExecutor = partitionOperationsExecutor;
     }
 
@@ -455,7 +461,10 @@ public class ClientHandlerModule implements 
IgniteComponent, PlatformComputeTran
                 SUPPORTED_FEATURES,
                 Map.of(),
                 computeExecutors::remove,
-                handshakeEventLoopSwitcher
+                handshakeEventLoopSwitcher,
+                ddlBatchingSuggestionEnabled.get()
+                        ? new DdlBatchingSuggester()
+                        : ignore -> {}
         );
     }
 
diff --git 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
index f2b2d7e243e..19e3f0c464c 100644
--- 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
+++ 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
@@ -163,6 +163,7 @@ import 
org.apache.ignite.internal.security.authentication.event.AuthenticationEv
 import 
org.apache.ignite.internal.security.authentication.event.AuthenticationProviderEventParameters;
 import 
org.apache.ignite.internal.security.authentication.event.UserEventParameters;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.table.IgniteTablesInternal;
 import org.apache.ignite.internal.table.distributed.schema.SchemaVersions;
 import org.apache.ignite.internal.table.distributed.schema.SchemaVersionsImpl;
@@ -210,6 +211,9 @@ public class ClientInboundMessageHandler
     /** Connection resources. */
     private final ClientResourceRegistry resources = new 
ClientResourceRegistry();
 
+    /** Tracks the number of sequential DDL queries executed and prints 
suggestion to use batching. */
+    private final Consumer<SqlQueryType> queryTypeListener;
+
     /** Configuration. */
     private final ClientConnectorView configuration;
 
@@ -292,6 +296,7 @@ public class ClientInboundMessageHandler
      * @param partitionOperationsExecutor Partition operations executor.
      * @param features Features.
      * @param extensions Extensions.
+     * @param queryTypeListener Tracks the number of sequential DDL queries 
executed and prints suggestion to use batching.
      */
     public ClientInboundMessageHandler(
             IgniteTablesInternal igniteTables,
@@ -312,7 +317,8 @@ public class ClientInboundMessageHandler
             BitSet features,
             Map<HandshakeExtension, Object> extensions,
             Function<String, CompletableFuture<PlatformComputeConnection>> 
computeConnectionFunc,
-            HandshakeEventLoopSwitcher handshakeEventLoopSwitcher
+            HandshakeEventLoopSwitcher handshakeEventLoopSwitcher,
+            Consumer<SqlQueryType> queryTypeListener
     ) {
         assert igniteTables != null;
         assert txManager != null;
@@ -367,6 +373,8 @@ public class ClientInboundMessageHandler
         this.extensions = extensions;
 
         this.computeConnectionFunc = computeConnectionFunc;
+
+        this.queryTypeListener = queryTypeListener;
     }
 
     @Override
@@ -989,7 +997,7 @@ public class ClientInboundMessageHandler
                         partitionOperationsExecutor, in, requestId, 
cancelHandles, queryProcessor, resources, metrics, tsTracker,
                         clientContext.hasFeature(SQL_PARTITION_AWARENESS), 
clientContext.hasFeature(SQL_DIRECT_TX_MAPPING), txManager,
                         igniteTables, clockService, 
notificationSender(requestId), resolveCurrentUsername(),
-                        clientContext.hasFeature(SQL_MULTISTATEMENT_SUPPORT)
+                        clientContext.hasFeature(SQL_MULTISTATEMENT_SUPPORT), 
queryTypeListener
                 );
 
             case ClientOp.SQL_CURSOR_NEXT_RESULT_SET:
@@ -1291,6 +1299,11 @@ public class ClientInboundMessageHandler
         return cancelHandles.size();
     }
 
+    @TestOnly
+    public Consumer<SqlQueryType> queryTypeListener() {
+        return queryTypeListener;
+    }
+
     private CompletableFuture<ClientMessageUnpacker> 
sendServerToClientRequest(int serverOp, Consumer<ClientMessagePacker> writer) {
         // Server and client request ids do not clash, but we use negative to 
simplify the debugging.
         var requestId = serverToClientRequestId.decrementAndGet();
diff --git 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/DdlBatchingSuggester.java
 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/DdlBatchingSuggester.java
new file mode 100644
index 00000000000..61c714908a4
--- /dev/null
+++ 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/DdlBatchingSuggester.java
@@ -0,0 +1,72 @@
+/*
+ * 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.client.handler;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import org.apache.ignite.internal.logger.IgniteLogger;
+import org.apache.ignite.internal.logger.Loggers;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Tracks the number of sequential DDL queries executed and prints
+ * the recommendation to use batch processing of DDL queries.
+ */
+public class DdlBatchingSuggester implements Consumer<SqlQueryType> {
+    /** The number of DDL commands, after which it is necessary to print the 
recommendation for the user. */
+    static final int THRESHOLD = 10;
+
+    /** Logger. */
+    private static final IgniteLogger LOG = 
Loggers.forClass(DdlBatchingSuggester.class);
+
+    /** Message printer. */
+    private final Consumer<String> printer;
+
+    /** Number of DDL queries processed in a row. */
+    private final AtomicInteger counter = new AtomicInteger();
+
+    DdlBatchingSuggester() {
+        this.printer = LOG::warn;
+    }
+
+    @TestOnly
+    DdlBatchingSuggester(Consumer<String> printer) {
+        this.printer = printer;
+    }
+
+    @Override
+    public void accept(SqlQueryType type) {
+        if (type != SqlQueryType.DDL) {
+            counter.set(0);
+
+            return;
+        }
+
+        if (counter.incrementAndGet() == THRESHOLD) {
+            printer.accept("Multiple DDL statements were executed 
individually. For improved performance, "
+                    + "consider grouping DDL statements into a single SQL 
script. To disable this suggestion, "
+                    + "set the cluster property 
'ignite.suggestions.sequentialDdlExecution.enabled' to 'false'.");
+        }
+    }
+
+    @TestOnly
+    public int trackedQueriesCount() {
+        return counter.get();
+    }
+}
diff --git 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java
 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java
index 42390807490..baab0d9a4a0 100644
--- 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java
+++ 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java
@@ -28,6 +28,7 @@ import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import org.apache.ignite.client.handler.ClientHandlerMetricSource;
 import org.apache.ignite.client.handler.ClientResourceRegistry;
@@ -42,6 +43,7 @@ import org.apache.ignite.internal.sql.engine.AsyncSqlCursor;
 import org.apache.ignite.internal.sql.engine.InternalSqlRow;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
 import org.apache.ignite.internal.sql.engine.SqlProperties;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.tx.InternalTransaction;
 import org.apache.ignite.internal.tx.TxManager;
 import org.apache.ignite.internal.util.ArrayUtils;
@@ -96,7 +98,8 @@ public class ClientSqlExecuteRequest {
             ClockService clockService,
             NotificationSender notificationSender,
             @Nullable String username,
-            boolean sqlMultistatementsSupported
+            boolean sqlMultistatementsSupported,
+            Consumer<SqlQueryType> queryTypeListener
     ) {
         CancelHandle cancelHandle = CancelHandle.create();
         cancelHandles.put(requestId, cancelHandle);
@@ -135,6 +138,7 @@ public class ClientSqlExecuteRequest {
                 props.pageSize(),
                 props.toSqlProps().userName(username),
                 () -> cancelHandles.remove(requestId),
+                queryTypeListener,
                 arguments
         ).thenCompose(asyncResultSet ->
                         ClientSqlCommon.writeResultSetAsync(resources, 
asyncResultSet, metrics, props.pageSize(),
@@ -165,6 +169,7 @@ public class ClientSqlExecuteRequest {
             int pageSize,
             SqlProperties props,
             Runnable onComplete,
+            Consumer<SqlQueryType> queryTypeListener,
             @Nullable Object... arguments
     ) {
         try {
@@ -177,6 +182,8 @@ public class ClientSqlExecuteRequest {
                         arguments
                     )
                     .thenCompose(cur -> {
+                                queryTypeListener.accept(cur.queryType());
+
                                 doWhenAllCursorsComplete(cur, onComplete);
 
                                 return cur.requestNextAsync(pageSize)
diff --git 
a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/DdlBatchingSuggesterTest.java
 
b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/DdlBatchingSuggesterTest.java
new file mode 100644
index 00000000000..909f11d5ed5
--- /dev/null
+++ 
b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/DdlBatchingSuggesterTest.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.client.handler;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for class {@link DdlBatchingSuggester}.
+ */
+public class DdlBatchingSuggesterTest {
+    @Test
+    void suggestionIsPrintedOnlyIfQueriesWereExecutedInRow() {
+        AtomicInteger counter = new AtomicInteger();
+        DdlBatchingSuggester suggester = new DdlBatchingSuggester(ignored -> 
counter.incrementAndGet());
+
+        for (int i = 0; i < DdlBatchingSuggester.THRESHOLD - 1; i++) {
+            suggester.accept(SqlQueryType.DDL);
+        }
+
+        suggester.accept(SqlQueryType.QUERY);
+
+        assertThat(counter.get(), is(0));
+
+        for (int i = 0; i < DdlBatchingSuggester.THRESHOLD; i++) {
+            suggester.accept(SqlQueryType.DDL);
+        }
+
+        assertThat(counter.get(), is(1));
+    }
+
+    @Test
+    void suggestionIsPrintedOnlyOnce() {
+        AtomicInteger counter = new AtomicInteger();
+        DdlBatchingSuggester suggester = new DdlBatchingSuggester(ignored -> 
counter.incrementAndGet());
+
+        for (int i = 0; i < 200; i++) {
+            suggester.accept(SqlQueryType.DDL);
+        }
+
+        assertThat(counter.get(), is(1));
+    }
+}
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/TestClientHandlerModule.java
 
b/modules/client/src/test/java/org/apache/ignite/client/TestClientHandlerModule.java
index 39cea63d7ca..ce53ef8ecdc 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/TestClientHandlerModule.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/TestClientHandlerModule.java
@@ -276,7 +276,8 @@ public class TestClientHandlerModule implements 
IgniteComponent {
                                         features,
                                         randomExtensions(),
                                         unused -> null,
-                                        
bootstrapFactory.handshakeEventLoopSwitcher()
+                                        
bootstrapFactory.handshakeEventLoopSwitcher(),
+                                        ignore -> {}
                                 )
                         );
                     }
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/TestServer.java 
b/modules/client/src/test/java/org/apache/ignite/client/TestServer.java
index 4121420d3ea..875c92ac355 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/TestServer.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/TestServer.java
@@ -286,7 +286,8 @@ public class TestServer implements AutoCloseable {
                         clientConnectorConfiguration,
                         new TestLowWatermark(),
                         new SystemPropertiesNodeProperties(),
-                        Runnable::run
+                        Runnable::run,
+                        () -> true
                 );
 
         module.startAsync(componentContext).join();
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
 
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SequentialDdlExecutionConfigurationSchema.java
similarity index 55%
copy from 
modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
copy to 
modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SequentialDdlExecutionConfigurationSchema.java
index 3f97c44c2e1..fcc2701565e 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
+++ 
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SequentialDdlExecutionConfigurationSchema.java
@@ -15,26 +15,15 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.client;
+package org.apache.ignite.internal.configuration;
 
-import org.apache.ignite.Ignite;
-import org.apache.ignite.client.IgniteClient;
-import org.apache.ignite.internal.table.ItDataConsistencyTest;
-import org.junit.jupiter.api.BeforeEach;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Value;
 
-/**
- * Test data consistency in mixed read-write load initiated from a client.
- */
-public class ItClientDataConsistencyTest extends ItDataConsistencyTest {
-    private IgniteClient client;
-
-    @BeforeEach
-    public void startClient() {
-        client = IgniteClient.builder().addresses("127.0.0.1:10800").build();
-    }
-
-    @Override
-    protected Ignite assignNodeForIteration(int workerId) {
-        return client;
-    }
+/** Configuration related to DDL batching suggestion. */
+@Config
+public class SequentialDdlExecutionConfigurationSchema {
+    /** Enable/disable suggestion about DDL batching. */
+    @Value(hasDefault = true)
+    public final boolean enabled = true;
 }
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
 
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SuggestionsClusterExtensionConfigurationSchema.java
similarity index 55%
copy from 
modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
copy to 
modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SuggestionsClusterExtensionConfigurationSchema.java
index 3f97c44c2e1..fe056091697 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
+++ 
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SuggestionsClusterExtensionConfigurationSchema.java
@@ -15,26 +15,16 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.client;
+package org.apache.ignite.internal.configuration;
 
-import org.apache.ignite.Ignite;
-import org.apache.ignite.client.IgniteClient;
-import org.apache.ignite.internal.table.ItDataConsistencyTest;
-import org.junit.jupiter.api.BeforeEach;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationExtension;
 
 /**
- * Test data consistency in mixed read-write load initiated from a client.
+ * Extension for suggestions distributed configuration schema.
  */
-public class ItClientDataConsistencyTest extends ItDataConsistencyTest {
-    private IgniteClient client;
-
-    @BeforeEach
-    public void startClient() {
-        client = IgniteClient.builder().addresses("127.0.0.1:10800").build();
-    }
-
-    @Override
-    protected Ignite assignNodeForIteration(int workerId) {
-        return client;
-    }
+@ConfigurationExtension
+public class SuggestionsClusterExtensionConfigurationSchema extends 
ClusterConfigurationSchema {
+    @ConfigValue
+    public SuggestionsConfigurationSchema suggestions;
 }
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
 
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SuggestionsConfigurationSchema.java
similarity index 55%
copy from 
modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
copy to 
modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SuggestionsConfigurationSchema.java
index 3f97c44c2e1..eb2418f4d0e 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
+++ 
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SuggestionsConfigurationSchema.java
@@ -15,26 +15,15 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.client;
+package org.apache.ignite.internal.configuration;
 
-import org.apache.ignite.Ignite;
-import org.apache.ignite.client.IgniteClient;
-import org.apache.ignite.internal.table.ItDataConsistencyTest;
-import org.junit.jupiter.api.BeforeEach;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
 
-/**
- * Test data consistency in mixed read-write load initiated from a client.
- */
-public class ItClientDataConsistencyTest extends ItDataConsistencyTest {
-    private IgniteClient client;
-
-    @BeforeEach
-    public void startClient() {
-        client = IgniteClient.builder().addresses("127.0.0.1:10800").build();
-    }
-
-    @Override
-    protected Ignite assignNodeForIteration(int workerId) {
-        return client;
-    }
+/** Configuration related to system suggestions. */
+@Config
+public class SuggestionsConfigurationSchema {
+    /** DDL batching suggestion configuration. */
+    @ConfigValue
+    public SequentialDdlExecutionConfigurationSchema sequentialDdlExecution;
 }
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
 
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SuggestionsDistributedConfigurationModule.java
similarity index 53%
copy from 
modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
copy to 
modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SuggestionsDistributedConfigurationModule.java
index 3f97c44c2e1..75ad91ee8af 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
+++ 
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SuggestionsDistributedConfigurationModule.java
@@ -15,26 +15,25 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.client;
+package org.apache.ignite.internal.configuration;
 
-import org.apache.ignite.Ignite;
-import org.apache.ignite.client.IgniteClient;
-import org.apache.ignite.internal.table.ItDataConsistencyTest;
-import org.junit.jupiter.api.BeforeEach;
+import com.google.auto.service.AutoService;
+import java.util.Collection;
+import java.util.List;
+import org.apache.ignite.configuration.ConfigurationModule;
+import org.apache.ignite.configuration.annotation.ConfigurationType;
 
-/**
- * Test data consistency in mixed read-write load initiated from a client.
- */
-public class ItClientDataConsistencyTest extends ItDataConsistencyTest {
-    private IgniteClient client;
-
-    @BeforeEach
-    public void startClient() {
-        client = IgniteClient.builder().addresses("127.0.0.1:10800").build();
+/** {@link ConfigurationModule} for suggestions cluster wide configuration. */
+@AutoService(ConfigurationModule.class)
+public class SuggestionsDistributedConfigurationModule implements 
ConfigurationModule {
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationType type() {
+        return ConfigurationType.DISTRIBUTED;
     }
 
     @Override
-    protected Ignite assignNodeForIteration(int workerId) {
-        return client;
+    public Collection<Class<?>> schemaExtensions() {
+        return List.of(SuggestionsClusterExtensionConfigurationSchema.class);
     }
 }
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
index 3f97c44c2e1..253c9fcf4d7 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/client/ItClientDataConsistencyTest.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.client;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.client.IgniteClient;
 import org.apache.ignite.internal.table.ItDataConsistencyTest;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 
 /**
@@ -33,6 +34,11 @@ public class ItClientDataConsistencyTest extends 
ItDataConsistencyTest {
         client = IgniteClient.builder().addresses("127.0.0.1:10800").build();
     }
 
+    @AfterEach
+    void closeClient() {
+        client.close();
+    }
+
     @Override
     protected Ignite assignNodeForIteration(int workerId) {
         return client;
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientAuthenticationTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientAuthenticationTest.java
index c131575d4cc..fe0b339069c 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientAuthenticationTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientAuthenticationTest.java
@@ -208,16 +208,16 @@ public class ItThinClientAuthenticationTest extends 
ItAbstractThinClientTest {
     public void testCurrentUser() {
         server().sql().execute(null, "CREATE TABLE t1 (id INT PRIMARY KEY, val 
VARCHAR)").close();
 
-        IgniteClient client2WithAuth = IgniteClient.builder()
+        try (IgniteClient client2WithAuth = IgniteClient.builder()
                 .authenticator(BasicAuthenticator.builder()
                         .username(USERNAME_2)
                         .password(PASSWORD_2)
                         .build())
                 .addresses(getClientAddresses().toArray(new String[0]))
-                .build();
-
-        validateCurrentUser(clientWithAuth, USERNAME_1);
-        validateCurrentUser(client2WithAuth, USERNAME_2);
+                .build()) {
+            validateCurrentUser(clientWithAuth, USERNAME_1);
+            validateCurrentUser(client2WithAuth, USERNAME_2);
+        }
     }
 
     private static void validateCurrentUser(IgniteClient client, String 
expectedUsername) {
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientDdlQueriesTrackerTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientDdlQueriesTrackerTest.java
new file mode 100644
index 00000000000..128500298da
--- /dev/null
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientDdlQueriesTrackerTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.runner.app.client;
+
+import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.client.handler.ClientInboundMessageHandler;
+import org.apache.ignite.client.handler.DdlBatchingSuggester;
+import 
org.apache.ignite.internal.configuration.SuggestionsClusterExtensionConfiguration;
+import org.apache.ignite.internal.lang.IgniteStringFormatter;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.sql.ResultSet;
+import org.apache.ignite.sql.SqlRow;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * End-to-end tests to validate the behavior of the DDL queries suggestion 
handler.
+ */
+public class ItThinClientDdlQueriesTrackerTest extends 
ItAbstractThinClientTest {
+    private final List<AutoCloseable> closeables = new ArrayList<>();
+
+    @Override
+    protected int nodes() {
+        return 1;
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        server(0).sql().executeScript("DROP TABLE IF EXISTS t");
+
+        IgniteUtils.closeAll(closeables);
+    }
+
+    @Test
+    void ddlIsTrackedByConnection() {
+        server(0).sql().executeScript("CREATE TABLE t(id INT PRIMARY KEY)");
+
+        IgniteClient client1 = startClient();
+        client1.sql().executeScript("SELECT 1");
+        ClientInboundMessageHandler handler1 = 
unwrapIgniteImpl(server(0)).clientInboundMessageHandler();
+
+        // The handler "test reference" is updated after a new connection is 
established.
+        IgniteClient client2 = startClient();
+        client2.sql().executeScript("SELECT 1");
+        ClientInboundMessageHandler handler2 = 
unwrapIgniteImpl(server(0)).clientInboundMessageHandler();
+
+        assertThat(handler1, not(sameInstance(handler2)));
+
+        addColumn(client1, 0);
+        addColumn(client1, 1);
+
+        assertThat(ddlQueriesInRow(handler1), is(2));
+        assertThat(ddlQueriesInRow(handler2), is(0));
+
+        addColumn(client2, 2);
+
+        assertThat(ddlQueriesInRow(handler1), is(2));
+        assertThat(ddlQueriesInRow(handler2), is(1));
+    }
+
+    @Test
+    void disableSuggestion() {
+        server(0).sql().executeScript("CREATE TABLE t(id INT PRIMARY KEY)");
+
+        SuggestionsClusterExtensionConfiguration config = 
unwrapIgniteImpl(server(0)).clusterConfiguration()
+                
.getConfiguration(SuggestionsClusterExtensionConfiguration.KEY);
+
+        { // Suggestion is enabled by default
+            IgniteClient client = startClient();
+            addColumn(client, 0);
+            ClientInboundMessageHandler handler = 
unwrapIgniteImpl(server(0)).clientInboundMessageHandler();
+            assertThat(ddlQueriesInRow(handler), is(1));
+        }
+
+        { // Disable suggestion
+            await(config.suggestions().sequentialDdlExecution().change(c -> 
c.changeEnabled(false)));
+
+            // The handler "test reference" is updated after a new connection 
is established.
+            IgniteClient client = startClient();
+            addColumn(client, 1);
+            addColumn(client, 2);
+            ClientInboundMessageHandler handler = 
unwrapIgniteImpl(server(0)).clientInboundMessageHandler();
+            assertThat(ddlQueriesInRow(handler), is(-1));
+        }
+
+        { // Enable suggestion
+            await(config.suggestions().sequentialDdlExecution().change(c -> 
c.changeEnabled(true)));
+
+            IgniteClient client = startClient();
+
+            addColumn(client, 3);
+            addColumn(client, 4);
+            addColumn(client, 5);
+
+            ClientInboundMessageHandler handler = 
unwrapIgniteImpl(server(0)).clientInboundMessageHandler();
+            assertThat(ddlQueriesInRow(handler), is(3));
+        }
+    }
+
+    private IgniteClient startClient() {
+        IgniteClient client = IgniteClient.builder()
+                .addresses(getClientAddresses().toArray(new String[0]))
+                .backgroundReconnectInterval(0)
+                .retryPolicy(null)
+                .build();
+
+        closeables.add(client);
+
+        return client;
+    }
+
+    private static void addColumn(IgniteClient client, int columnNumber) {
+        String ddlQuery = IgniteStringFormatter.format("ALTER TABLE t ADD 
COLUMN col{} int;", columnNumber);
+
+        try (ResultSet<SqlRow> rs = client.sql().execute(null, ddlQuery)) {
+            assertThat(rs.hasRowSet(), is(false));
+            assertThat(rs.wasApplied(), is(true));
+        }
+    }
+
+    private static int ddlQueriesInRow(ClientInboundMessageHandler handler) {
+        Consumer<SqlQueryType> queryTypeListener = handler.queryTypeListener();
+
+        if (queryTypeListener instanceof DdlBatchingSuggester) {
+            return ((DdlBatchingSuggester) 
queryTypeListener).trackedQueriesCount();
+        }
+
+        return -1;
+    }
+}
diff --git 
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java 
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index ff584e06e7a..91460355457 100644
--- 
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ 
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -110,6 +110,8 @@ import 
org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.JdbcPortProviderImpl;
 import org.apache.ignite.internal.configuration.RaftGroupOptionsConfigHelper;
 import org.apache.ignite.internal.configuration.ServiceLoaderModulesProvider;
+import 
org.apache.ignite.internal.configuration.SuggestionsClusterExtensionConfiguration;
+import org.apache.ignite.internal.configuration.SuggestionsConfiguration;
 import org.apache.ignite.internal.configuration.SystemDistributedConfiguration;
 import 
org.apache.ignite.internal.configuration.SystemDistributedExtensionConfiguration;
 import org.apache.ignite.internal.configuration.SystemLocalConfiguration;
@@ -1293,6 +1295,9 @@ public class IgniteImpl implements Ignite {
         ClientConnectorConfiguration clientConnectorConfiguration = 
nodeConfigRegistry
                 
.getConfiguration(ClientConnectorExtensionConfiguration.KEY).clientConnector();
 
+        SuggestionsConfiguration suggestionsConfiguration = 
clusterConfigRegistry
+                
.getConfiguration(SuggestionsClusterExtensionConfiguration.KEY).suggestions();
+
         clientHandlerModule = new ClientHandlerModule(
                 qryEngine,
                 distributedTblMgr,
@@ -1315,7 +1320,8 @@ public class IgniteImpl implements Ignite {
                 clientConnectorConfiguration,
                 lowWatermark,
                 nodeProperties,
-                threadPoolsManager.partitionOperationsExecutor()
+                threadPoolsManager.partitionOperationsExecutor(),
+                () -> 
suggestionsConfiguration.sequentialDdlExecution().enabled().value()
         );
 
         computeExecutor.setPlatformComputeTransport(clientHandlerModule);
diff --git 
a/modules/runner/src/test/resources/compatibility/configuration/ignite-snapshot.bin
 
b/modules/runner/src/test/resources/compatibility/configuration/ignite-snapshot.bin
index f34374f0f1c..1dd7bd77ca2 100644
Binary files 
a/modules/runner/src/test/resources/compatibility/configuration/ignite-snapshot.bin
 and 
b/modules/runner/src/test/resources/compatibility/configuration/ignite-snapshot.bin
 differ


Reply via email to