You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2025/09/02 11:59:32 UTC

(ignite-3) branch ignite-26156 created (now 618af386480)

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

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


      at 618af386480 wip

This branch includes the following new commits:

     new 3cd3f995358 wip
     new 7101de5c2ee Fix SqlCommand
     new 618af386480 wip

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(ignite-3) 03/03: wip

Posted by am...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 618af38648065c55dc1b6b0520d0a07a9bf703eb
Author: amashenkov <am...@apache.org>
AuthorDate: Tue Sep 2 14:48:48 2025 +0300

    wip
---
 .../sql/planner/ItSqlPlannerCommandTest.java       |  18 ++-
 .../cli/call/sql/InvalidatePlannerCacheCall.java   |  16 +--
 modules/client-handler/build.gradle                |   1 +
 .../ignite/internal/rest/api/sql/SqlQueryApi.java  |  20 ++++
 .../internal/rest/sql/SqlQueryController.java      |  14 +++
 modules/sql-engine-api/build.gradle                |   1 +
 .../internal/sql/engine/api/IgniteSqlInternal.java |  18 +++
 .../ignite/internal/sql/api/IgniteSqlImpl.java     |   7 +-
 .../ignite/internal/sql/api/IgniteSqlInternal.java |  10 --
 .../ignite/internal/sql/engine/QueryProcessor.java |  11 +-
 .../internal/sql/engine/SqlQueryProcessor.java     |   5 +-
 .../sql/engine/prepare/PrepareService.java         |  11 +-
 .../sql/engine/prepare/PrepareServiceImpl.java     | 126 +++++++++++++++------
 13 files changed, 189 insertions(+), 69 deletions(-)

diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerCommandTest.java
index 0f51473f34a..5fa41a89a18 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerCommandTest.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.internal.cli.commands.sql.planner;
 
 import static org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
-import static org.junit.jupiter.api.Assertions.assertAll;
 
 import org.apache.ignite.internal.cli.commands.sql.CliSqlCommandTestBase;
 import org.apache.ignite.internal.cli.commands.sql.SqlCommand;
@@ -32,10 +31,17 @@ class ItSqlPlannerCommandTest extends CliSqlCommandTestBase {
     void clearCache() {
         execute("sql", "plan", "clear-cache", CLUSTER_URL_OPTION, NODE_URL);
 
-        assertAll(
-                () -> assertExitCodeIs(1),
-                this::assertOutputIsEmpty,
-                this::assertErrOutputIsEmpty
-        );
+        assertExitCodeIs(0);
+        assertErrOutputIsEmpty();
+        assertOutputContains("Successfully cleared SQL query plan cache.");
+    }
+
+    @Test
+    void clearCacheFiltered() {
+        execute("sql", "plan", "clear-cache", CLUSTER_URL_OPTION, NODE_URL, "--tables", "PUBLIC.\"test\"");
+
+        assertExitCodeIs(0);
+        assertErrOutputIsEmpty();
+        assertOutputContains("Successfully cleared SQL query plan cache.");
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidatePlannerCacheCall.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidatePlannerCacheCall.java
index 19ee55db064..0885127c40d 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidatePlannerCacheCall.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidatePlannerCacheCall.java
@@ -18,9 +18,7 @@
 package org.apache.ignite.internal.cli.call.sql;
 
 import jakarta.inject.Singleton;
-import org.apache.ignite.internal.cli.call.configuration.JsonString;
 import org.apache.ignite.internal.cli.core.call.Call;
-import org.apache.ignite.internal.cli.core.call.CallOutput;
 import org.apache.ignite.internal.cli.core.call.DefaultCallOutput;
 import org.apache.ignite.internal.cli.core.exception.IgniteCliApiException;
 import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
@@ -31,7 +29,7 @@ import org.apache.ignite.rest.client.invoker.ApiException;
  * Shows node configuration from ignite cluster.
  */
 @Singleton
-public class InvalidatePlannerCacheCall implements Call<InvalidateCacheCallInput, JsonString> {
+public class InvalidatePlannerCacheCall implements Call<InvalidateCacheCallInput, String> {
     private final ApiClientFactory clientFactory;
 
     public InvalidatePlannerCacheCall(ApiClientFactory clientFactory) {
@@ -40,21 +38,17 @@ public class InvalidatePlannerCacheCall implements Call<InvalidateCacheCallInput
 
     /** {@inheritDoc} */
     @Override
-    public CallOutput<JsonString> execute(InvalidateCacheCallInput input) {
+    public DefaultCallOutput<String> execute(InvalidateCacheCallInput input) {
         SqlApi client = createApiClient(input);
 
         try {
-            return DefaultCallOutput.success(invalidateSqlPlannerCache(client, input));
-        } catch (ApiException | IllegalArgumentException e) {
+            client.clearCache(input.getTables());
+            return DefaultCallOutput.success("Successfully cleared SQL query plan cache.");
+        } catch (ApiException e) {
             return DefaultCallOutput.failure(new IgniteCliApiException(e, input.clusterUrl()));
         }
     }
 
-    private JsonString invalidateSqlPlannerCache(SqlApi api, InvalidateCacheCallInput input) throws ApiException {
-        // TODO: IGNITE-25872 serialize table names and implement api call.
-        return JsonString.fromString("");
-    }
-
     private SqlApi createApiClient(InvalidateCacheCallInput input) {
         return new SqlApi(clientFactory.getClient(input.clusterUrl()));
     }
diff --git a/modules/client-handler/build.gradle b/modules/client-handler/build.gradle
index d6b6bfbe84d..aed396cf689 100644
--- a/modules/client-handler/build.gradle
+++ b/modules/client-handler/build.gradle
@@ -30,6 +30,7 @@ dependencies {
     implementation project(':ignite-configuration-root')
     implementation project(':ignite-api')
     implementation project(':ignite-table')
+    implementation project(':ignite-sql-engine-api')
     implementation project(':ignite-sql-engine')
     implementation project(':ignite-network')
     implementation project(':ignite-core')
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/sql/SqlQueryApi.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/sql/SqlQueryApi.java
index 9425219c522..d62784ad6d8 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/sql/SqlQueryApi.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/sql/SqlQueryApi.java
@@ -23,6 +23,7 @@ import static org.apache.ignite.internal.rest.constants.MediaType.APPLICATION_JS
 import io.micronaut.http.annotation.Controller;
 import io.micronaut.http.annotation.Delete;
 import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.QueryValue;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.ArraySchema;
 import io.swagger.v3.oas.annotations.media.Content;
@@ -30,6 +31,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import org.apache.ignite.internal.rest.api.Problem;
@@ -104,4 +107,21 @@ public interface SqlQueryApi {
     CompletableFuture<Void> killQuery(
             @Schema(name = "queryId", description = "The unique identifier of the sql query.", requiredMode = REQUIRED) UUID queryId
     );
+
+    /**
+     * Invalidates SQL query planner cache.
+     *
+     * @return The result of the operation.
+     */
+    @Operation(
+            summary = "Invalidates SQL planner cache.",
+            description = "Invalidates SQL planner cache records on node that related to provided table names.")
+    @ApiResponse(responseCode = "200", description = "Successfully cleared SQL query plan cache.")
+    @Get("plan/clear-cache")
+    CompletableFuture<Void> clearCache(
+            @QueryValue
+            @Schema(description = "SQL query plans, which are related to given tables, will be evicted from cache. Case-sensitive, "
+                    + "cache will be reset if empty.")
+            Optional<Set<String>> tableNames
+    );
 }
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/sql/SqlQueryController.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/sql/SqlQueryController.java
index 14a1bb42c1a..50f179b2b18 100644
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/sql/SqlQueryController.java
+++ b/modules/rest/src/main/java/org/apache/ignite/internal/rest/sql/SqlQueryController.java
@@ -25,6 +25,8 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Optional;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import org.apache.ignite.internal.logger.IgniteLogger;
@@ -34,8 +36,10 @@ import org.apache.ignite.internal.rest.api.sql.SqlQueryApi;
 import org.apache.ignite.internal.rest.api.sql.SqlQueryInfo;
 import org.apache.ignite.internal.rest.sql.exception.SqlQueryKillException;
 import org.apache.ignite.internal.rest.sql.exception.SqlQueryNotFoundException;
+import org.apache.ignite.internal.sql.engine.api.IgniteSqlInternal;
 import org.apache.ignite.internal.sql.engine.api.kill.CancellableOperationType;
 import org.apache.ignite.internal.sql.engine.api.kill.KillHandlerRegistry;
+import org.apache.ignite.internal.wrapper.Wrappers;
 import org.apache.ignite.sql.IgniteSql;
 import org.apache.ignite.sql.SqlRow;
 import org.apache.ignite.sql.Statement;
@@ -87,6 +91,16 @@ public class SqlQueryController implements SqlQueryApi, ResourceHolder {
         }
     }
 
+    @Override
+    public CompletableFuture<Void> clearCache(Optional<Set<String>> tableNames) {
+        try {
+            return Wrappers.unwrap(igniteSql, IgniteSqlInternal.class).invalidatePlannerCache(tableNames.orElse(Set.of()));
+        } catch (Exception e) {
+            LOG.error("Failed to invalidate SQL planner cache.", e);
+            return failedFuture(e);
+        }
+    }
+
     private static Void handleOperationResult(UUID queryId, @Nullable Boolean result) {
         if (result != null && !result) {
             throw new SqlQueryNotFoundException(queryId.toString());
diff --git a/modules/sql-engine-api/build.gradle b/modules/sql-engine-api/build.gradle
index bc8420b1d7f..7e87523e2a4 100644
--- a/modules/sql-engine-api/build.gradle
+++ b/modules/sql-engine-api/build.gradle
@@ -21,6 +21,7 @@ apply from: "$rootDir/buildscripts/java-junit5.gradle"
 apply from: "$rootDir/buildscripts/java-test-fixtures.gradle"
 
 dependencies {
+    implementation project(':ignite-api')
     implementation project(':ignite-core')
 
     implementation libs.jetbrains.annotations
diff --git a/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/api/IgniteSqlInternal.java b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/api/IgniteSqlInternal.java
new file mode 100644
index 00000000000..d0f2ba150b2
--- /dev/null
+++ b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/api/IgniteSqlInternal.java
@@ -0,0 +1,18 @@
+package org.apache.ignite.internal.sql.engine.api;
+
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.sql.IgniteSql;
+
+/**
+ * Internal SQL facade.
+ */
+public interface IgniteSqlInternal extends IgniteSql {
+
+    /**
+     * Invalidates planner cache.
+     *
+     * @return Operation completion future.
+     */
+    CompletableFuture<Void> invalidatePlannerCache(Set<String> strings);
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlImpl.java
index 2e4abcb7aa8..9f126d0cbd5 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlImpl.java
@@ -28,6 +28,7 @@ import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
@@ -55,6 +56,7 @@ 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.sql.engine.api.IgniteSqlInternal;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.internal.tx.InternalTransaction;
 import org.apache.ignite.internal.util.ArrayUtils;
@@ -66,7 +68,6 @@ import org.apache.ignite.lang.CancellationToken;
 import org.apache.ignite.lang.TraceableException;
 import org.apache.ignite.lang.util.IgniteNameUtils;
 import org.apache.ignite.sql.BatchedArguments;
-import org.apache.ignite.sql.IgniteSql;
 import org.apache.ignite.sql.ResultSet;
 import org.apache.ignite.sql.SqlBatchException;
 import org.apache.ignite.sql.SqlException;
@@ -650,8 +651,8 @@ public class IgniteSqlImpl implements IgniteSqlInternal, IgniteComponent {
     }
 
     @Override
-    public void invalidatePlannerCache() {
-        queryProcessor.invalidatePlannerCache();
+    public CompletableFuture<Void> invalidatePlannerCache(Set<String> tableNames) {
+        return queryProcessor.invalidatePlannerCache(tableNames);
     }
 
     private static void validateDmlResult(AsyncCursor.BatchedResult<InternalSqlRow> page) {
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlInternal.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlInternal.java
deleted file mode 100644
index a560c28728a..00000000000
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlInternal.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.apache.ignite.internal.sql.api;
-
-import org.apache.ignite.sql.IgniteSql;
-
-/**
- * Internal SQL facade.
- */
-public interface IgniteSqlInternal extends IgniteSql {
-    void invalidatePlannerCache();
-}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
index 4d0fc33d5b1..011fd386298 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
@@ -17,11 +17,13 @@
 
 package org.apache.ignite.internal.sql.engine;
 
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import org.apache.ignite.internal.hlc.HybridTimestampTracker;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.sql.engine.prepare.QueryMetadata;
 import org.apache.ignite.internal.tx.InternalTransaction;
+import org.apache.ignite.internal.util.CompletableFutures;
 import org.apache.ignite.lang.CancellationToken;
 import org.apache.ignite.lang.IgniteException;
 import org.jetbrains.annotations.Nullable;
@@ -71,7 +73,12 @@ public interface QueryProcessor extends IgniteComponent {
             Object... params
     );
 
-    default void invalidatePlannerCache() {
-
+    /**
+     * Invalidates planner cache.
+     *
+     * @return Operation completion future.
+     */
+    default CompletableFuture<Void> invalidatePlannerCache(Set<String> tableNames) {
+        return CompletableFutures.nullCompletedFuture();
     }
 }
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 cd2f7eaa992..5617b9a5ca9 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
@@ -32,6 +32,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledExecutorService;
@@ -605,8 +606,8 @@ public class SqlQueryProcessor implements QueryProcessor, SystemViewProvider {
     }
 
     @Override
-    public void invalidatePlannerCache() {
-        prepareSvc.invalidateCache();
+    public CompletableFuture<Void> invalidatePlannerCache(Set<String> tableNames) {
+        return prepareSvc.invalidateCache(tableNames);
     }
 
     /** Completes the provided future when the callback is called. */
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
index 172da6c2a9f..55dfaf71dfd 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
@@ -17,10 +17,12 @@
 
 package org.apache.ignite.internal.sql.engine.prepare;
 
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import org.apache.ignite.internal.sql.engine.SqlOperationContext;
 import org.apache.ignite.internal.sql.engine.exec.LifecycleAware;
 import org.apache.ignite.internal.sql.engine.sql.ParsedResult;
+import org.apache.ignite.internal.util.CompletableFutures;
 
 /**
  * Preparation service that accepts an AST of the query and returns a prepared query plan.
@@ -37,5 +39,12 @@ public interface PrepareService extends LifecycleAware {
      */
     CompletableFuture<QueryPlan> prepareAsync(ParsedResult parsedResult, SqlOperationContext ctx);
 
-    default void invalidateCache() {};
+    /**
+     * Invalidates planner cache.
+     *
+     * @return Operation completion future.
+     */
+    default CompletableFuture<Void> invalidateCache(Set<String> tableNames) {
+        return CompletableFutures.nullCompletedFuture();
+    };
 }
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 964524581ba..4de2f875d37 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
@@ -30,6 +30,7 @@ import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
@@ -37,6 +38,7 @@ import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Predicate;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.schema.SchemaPlus;
@@ -71,6 +73,8 @@ import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueGet;
 import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueModify;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.rel.IgniteSelectCount;
+import org.apache.ignite.internal.sql.engine.rel.IgniteTableFunctionScan;
+import org.apache.ignite.internal.sql.engine.rel.IgniteTableModify;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchemas;
 import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManager;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlExplain;
@@ -248,7 +252,7 @@ public class PrepareServiceImpl implements PrepareService {
         boolean explicitTx = operationContext.txContext() != null && operationContext.txContext().explicitTx() != null;
 
         long timestamp = operationContext.operationTime().longValue();
-        int catalogVersion = schemaManager.catalogVersion(timestamp); 
+        int catalogVersion = schemaManager.catalogVersion(timestamp);
 
         CacheKey key = createCacheKey(parsedResult, catalogVersion, schemaName, operationContext.parameters());
 
@@ -305,10 +309,28 @@ public class PrepareServiceImpl implements PrepareService {
         );
     }
 
-    /** Invalidates planner cache. */
+    /** {@inheritDoc} */
     @Override
-    public void invalidateCache() {
-        cache.clear();
+    public CompletableFuture<Void> invalidateCache(Set<String> tableNames) {
+        return CompletableFuture.supplyAsync(() -> {
+            if (tableNames.isEmpty()) {
+                cache.clear();
+            } else {
+                cache.removeIfValue(p -> p.isDone() && planMatches(p.join(), tableNames::contains));
+            }
+
+            return null;
+        }, planningPool);
+    }
+
+    private boolean planMatches(QueryPlan plan, Predicate<String> containsTable) {
+        assert plan instanceof ExplainablePlan;
+
+        MatchingShuttle shuttle = new MatchingShuttle(containsTable);
+
+        ((ExplainablePlan) plan).getRel().accept(shuttle);
+
+        return shuttle.matches();
     }
 
     private CompletableFuture<QueryPlan> prepareAsync0(
@@ -461,25 +483,25 @@ public class PrepareServiceImpl implements PrepareService {
                     IgniteKeyValueGet kvGet = (IgniteKeyValueGet) optimizedRel;
 
                     return new KeyValueGetPlan(
-                            nextPlanId(), 
-                            catalogVersion, 
-                            kvGet, 
+                            nextPlanId(),
+                            catalogVersion,
+                            kvGet,
                             resultSetMetadata,
                             parameterMetadata,
-                            relWithMetadata.paMetadata, 
+                            relWithMetadata.paMetadata,
                             relWithMetadata.ppMetadata
                     );
                 }
 
                 var plan = new MultiStepPlan(
-                        nextPlanId(), 
-                        SqlQueryType.QUERY, 
-                        optimizedRel, 
-                        resultSetMetadata, 
-                        parameterMetadata, 
-                        catalogVersion, 
-                        relWithMetadata.numSources, 
-                        fastPlan, 
+                        nextPlanId(),
+                        SqlQueryType.QUERY,
+                        optimizedRel,
+                        resultSetMetadata,
+                        parameterMetadata,
+                        catalogVersion,
+                        relWithMetadata.numSources,
+                        fastPlan,
                         relWithMetadata.paMetadata,
                         relWithMetadata.ppMetadata
                 );
@@ -545,10 +567,10 @@ public class PrepareServiceImpl implements PrepareService {
         if (optimizedRel instanceof IgniteKeyValueModify) {
             plan = new KeyValueModifyPlan(
                     nextPlanId(),
-                    ctx.catalogVersion(), 
+                    ctx.catalogVersion(),
                     (IgniteKeyValueModify) optimizedRel,
                     DML_METADATA,
-                    parameterMetadata, 
+                    parameterMetadata,
                     relWithMetadata.paMetadata,
                     relWithMetadata.ppMetadata
             );
@@ -558,9 +580,9 @@ public class PrepareServiceImpl implements PrepareService {
                     SqlQueryType.DML,
                     optimizedRel, DML_METADATA,
                     parameterMetadata,
-                    ctx.catalogVersion(), 
-                    relWithMetadata.numSources, 
-                    null, 
+                    ctx.catalogVersion(),
+                    relWithMetadata.numSources,
+                    null,
                     relWithMetadata.paMetadata,
                     relWithMetadata.ppMetadata
             );
@@ -617,25 +639,25 @@ public class PrepareServiceImpl implements PrepareService {
                     IgniteKeyValueModify kvModify = (IgniteKeyValueModify) optimizedRel;
 
                     plan = new KeyValueModifyPlan(
-                            nextPlanId(), 
+                            nextPlanId(),
                             catalogVersion,
                             kvModify,
                             DML_METADATA,
-                            parameterMetadata, 
-                            relWithMetadata.paMetadata, 
+                            parameterMetadata,
+                            relWithMetadata.paMetadata,
                             relWithMetadata.ppMetadata
                     );
                 } else {
                     plan = new MultiStepPlan(
-                            nextPlanId(), 
+                            nextPlanId(),
                             SqlQueryType.DML,
                             optimizedRel,
-                            DML_METADATA, 
-                            parameterMetadata, 
+                            DML_METADATA,
+                            parameterMetadata,
                             catalogVersion,
-                            relWithMetadata.numSources, 
-                            null, 
-                            relWithMetadata.paMetadata, 
+                            relWithMetadata.numSources,
+                            null,
+                            relWithMetadata.paMetadata,
                             relWithMetadata.ppMetadata
                     );
                 }
@@ -773,9 +795,9 @@ public class PrepareServiceImpl implements PrepareService {
     }
 
     private RelWithMetadata doOptimize(
-            PlanningContext ctx, 
-            SqlNode validatedNode, 
-            IgnitePlanner planner, 
+            PlanningContext ctx,
+            SqlNode validatedNode,
+            IgnitePlanner planner,
             @Nullable Runnable onTimeoutAction
     ) {
         // Convert to Relational operators graph
@@ -912,7 +934,7 @@ public class PrepareServiceImpl implements PrepareService {
         RelWithMetadata(
                 IgniteRel rel,
                 int numSources,
-                @Nullable PartitionAwarenessMetadata paMetadata, 
+                @Nullable PartitionAwarenessMetadata paMetadata,
                 @Nullable PartitionPruningMetadata ppMetadata
         ) {
             this.rel = rel;
@@ -921,4 +943,40 @@ public class PrepareServiceImpl implements PrepareService {
             this.ppMetadata = ppMetadata;
         }
     }
+
+    private static class MatchingShuttle extends IgniteRelShuttle {
+        private final Predicate<String> containsTable;
+        boolean matches;
+
+        private MatchingShuttle(Predicate<String> containsTable) {
+            this.containsTable = containsTable;
+            matches = false;
+        }
+
+        /**
+         * Visits all children of a parent.
+         */
+        @Override
+        protected IgniteRel processNode(IgniteRel rel) {
+            if (!matches && rel.getTable() != null) {
+                matches = containsTable.test(rel.getTable().getQualifiedName().toString());
+            }
+
+            if (matches) {
+                return rel;
+            }
+
+            List<IgniteRel> inputs = Commons.cast(rel.getInputs());
+
+            for (int i = 0; i < inputs.size() && !matches; i++) {
+                visit(inputs.get(i));
+            }
+
+            return rel;
+        }
+
+        boolean matches() {
+            return matches;
+        }
+    }
 }


(ignite-3) 02/03: Fix SqlCommand

Posted by am...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 7101de5c2eeda4abbcfdcbadd5975ec3b8e8ac34
Author: amashenkov <am...@apache.org>
AuthorDate: Tue Sep 2 11:07:02 2025 +0300

    Fix SqlCommand
---
 .../internal/cli/commands/sql/SqlCommand.java      | 22 ++++++++++++++--------
 1 file changed, 14 insertions(+), 8 deletions(-)

diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
index 3cb6f3d1b6f..c393e85ec2f 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
@@ -56,14 +56,19 @@ import picocli.CommandLine.Parameters;
         description = "Executes SQL query"
 )
 public class SqlCommand extends BaseCommand implements Callable<Integer> {
-    @Option(names = JDBC_URL_OPTION, required = true, descriptionKey = JDBC_URL_KEY, description = JDBC_URL_OPTION_DESC)
-    private String jdbc;
+    @ArgGroup(validate = false, exclusive = false)
+    private JdbcOption jdbcOption;
 
-    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
-    private boolean plain;
+    private static class JdbcOption {
+        @Option(names = JDBC_URL_OPTION, required = true, descriptionKey = JDBC_URL_KEY, description = JDBC_URL_OPTION_DESC)
+        private String jdbc;
 
-    @ArgGroup(multiplicity = "1")
-    private ExecOptions execOptions;
+        @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
+        private boolean plain;
+
+        @ArgGroup(multiplicity = "1")
+        private ExecOptions execOptions;
+    }
 
     private static class ExecOptions {
         @Parameters(index = "0", description = "SQL query to execute")
@@ -86,12 +91,13 @@ public class SqlCommand extends BaseCommand implements Callable<Integer> {
      */
     @Override
     public Integer call() {
-        try (SqlManager sqlManager = new SqlManager(jdbc)) {
+        try (SqlManager sqlManager = new SqlManager(jdbcOption.jdbc)) {
+            ExecOptions execOptions = jdbcOption.execOptions;
             String executeCommand = execOptions.file != null ? extract(execOptions.file) : execOptions.command;
             return runPipeline(CallExecutionPipeline.builder(new SqlQueryCall(sqlManager))
                     .inputProvider(() -> new StringCallInput(executeCommand))
                     .exceptionHandler(SqlExceptionHandler.INSTANCE)
-                    .decorator(new SqlQueryResultDecorator(plain))
+                    .decorator(new SqlQueryResultDecorator(jdbcOption.plain))
             );
         } catch (SQLException e) {
             ExceptionWriter exceptionWriter = ExceptionWriter.fromPrintWriter(spec.commandLine().getErr());


(ignite-3) 01/03: wip

Posted by am...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 3cd3f995358e006a91ab02af72d53c4e128fef6e
Author: amashenkov <am...@apache.org>
AuthorDate: Mon Sep 1 11:56:58 2025 +0300

    wip
---
 .../sql/planner/ItSqlPlannerCommandTest.java}      | 34 ++++++------
 .../cli/call/sql/InvalidateCacheCallInput.java     | 64 ++++++++++++++++++++++
 .../cli/call/sql/InvalidatePlannerCacheCall.java   | 61 +++++++++++++++++++++
 .../internal/cli/commands/sql/SqlCommand.java      |  8 ++-
 .../sql/planner/InvalidateCacheCommand.java        | 52 ++++++++++++++++++
 .../cli/commands/sql/planner/PlanCommand.java}     | 27 ++++-----
 .../ignite/internal/sql/api/IgniteSqlImpl.java     |  7 ++-
 .../ignite/internal/sql/api/IgniteSqlInternal.java | 10 ++++
 .../ignite/internal/sql/engine/QueryProcessor.java |  4 ++
 .../internal/sql/engine/SqlQueryProcessor.java     |  5 ++
 .../sql/engine/prepare/PrepareService.java         |  2 +
 .../sql/engine/prepare/PrepareServiceImpl.java     |  6 ++
 12 files changed, 245 insertions(+), 35 deletions(-)

diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerCommandTest.java
similarity index 50%
copy from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerCommandTest.java
index 0cd83ea8e90..0f51473f34a 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerCommandTest.java
@@ -15,25 +15,27 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.sql.engine.prepare;
+package org.apache.ignite.internal.cli.commands.sql.planner;
 
-import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.internal.sql.engine.SqlOperationContext;
-import org.apache.ignite.internal.sql.engine.exec.LifecycleAware;
-import org.apache.ignite.internal.sql.engine.sql.ParsedResult;
+import static org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import org.apache.ignite.internal.cli.commands.sql.CliSqlCommandTestBase;
+import org.apache.ignite.internal.cli.commands.sql.SqlCommand;
+import org.junit.jupiter.api.Test;
 
 /**
- * Preparation service that accepts an AST of the query and returns a prepared query plan.
+ * Tests for {@link SqlCommand}.
  */
-public interface PrepareService extends LifecycleAware {
+class ItSqlPlannerCommandTest extends CliSqlCommandTestBase {
+    @Test
+    void clearCache() {
+        execute("sql", "plan", "clear-cache", CLUSTER_URL_OPTION, NODE_URL);
 
-    /**
-     * Prepare query plan.
-     *
-     * @param parsedResult Represent of parsed query as AST, which need to be planned.
-     * @param ctx Query context.
-     *
-     * @return Future that contains prepared query plan when completes.
-     */
-    CompletableFuture<QueryPlan> prepareAsync(ParsedResult parsedResult, SqlOperationContext ctx);
+        assertAll(
+                () -> assertExitCodeIs(1),
+                this::assertOutputIsEmpty,
+                this::assertErrOutputIsEmpty
+        );
+    }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidateCacheCallInput.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidateCacheCallInput.java
new file mode 100644
index 00000000000..0c7acefec57
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidateCacheCallInput.java
@@ -0,0 +1,64 @@
+/*
+ * 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.cli.call.sql;
+
+import java.util.List;
+import org.apache.ignite.internal.cli.core.call.CallInput;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Input for {@link InvalidatePlannerCacheCall}.
+ */
+public class InvalidateCacheCallInput implements CallInput {
+    /**
+     * Cluster url.
+     */
+    private final String clusterUrl;
+
+    /**
+     * Tables filter.
+     */
+    @Nullable
+    private final List<String> tables;
+
+    @Nullable
+    private final List<String> targetNodes;
+
+    public InvalidateCacheCallInput(String clusterUrl, @Nullable List<String> tables, @Nullable List<String> targetNodes) {
+        this.clusterUrl = clusterUrl;
+        this.tables = tables;
+        this.targetNodes = targetNodes;
+    }
+
+    /** Cluster url. */
+    public String clusterUrl() {
+        return clusterUrl;
+    }
+
+    /** Returns names specifying nodes to restart partitions. Empty/null means "all nodes". */
+    @Nullable
+    public List<String> targetNodes() {
+        return targetNodes;
+    }
+
+    /** Return tables that SQL plans contains any of these tables should be invalidated. */
+    @Nullable
+    public List<String> getTables() {
+        return tables;
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidatePlannerCacheCall.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidatePlannerCacheCall.java
new file mode 100644
index 00000000000..19ee55db064
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidatePlannerCacheCall.java
@@ -0,0 +1,61 @@
+/*
+ * 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.cli.call.sql;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cli.call.configuration.JsonString;
+import org.apache.ignite.internal.cli.core.call.Call;
+import org.apache.ignite.internal.cli.core.call.CallOutput;
+import org.apache.ignite.internal.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.internal.cli.core.exception.IgniteCliApiException;
+import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
+import org.apache.ignite.rest.client.api.SqlApi;
+import org.apache.ignite.rest.client.invoker.ApiException;
+
+/**
+ * Shows node configuration from ignite cluster.
+ */
+@Singleton
+public class InvalidatePlannerCacheCall implements Call<InvalidateCacheCallInput, JsonString> {
+    private final ApiClientFactory clientFactory;
+
+    public InvalidatePlannerCacheCall(ApiClientFactory clientFactory) {
+        this.clientFactory = clientFactory;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public CallOutput<JsonString> execute(InvalidateCacheCallInput input) {
+        SqlApi client = createApiClient(input);
+
+        try {
+            return DefaultCallOutput.success(invalidateSqlPlannerCache(client, input));
+        } catch (ApiException | IllegalArgumentException e) {
+            return DefaultCallOutput.failure(new IgniteCliApiException(e, input.clusterUrl()));
+        }
+    }
+
+    private JsonString invalidateSqlPlannerCache(SqlApi api, InvalidateCacheCallInput input) throws ApiException {
+        // TODO: IGNITE-25872 serialize table names and implement api call.
+        return JsonString.fromString("");
+    }
+
+    private SqlApi createApiClient(InvalidateCacheCallInput input) {
+        return new SqlApi(clientFactory.getClient(input.clusterUrl()));
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
index 0c23597b4db..3cb6f3d1b6f 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
@@ -33,6 +33,7 @@ import java.sql.SQLException;
 import java.util.concurrent.Callable;
 import org.apache.ignite.internal.cli.call.sql.SqlQueryCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.sql.planner.PlanCommand;
 import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.internal.cli.core.call.StringCallInput;
 import org.apache.ignite.internal.cli.core.exception.ExceptionWriter;
@@ -48,7 +49,12 @@ import picocli.CommandLine.Parameters;
 /**
  * Command for sql execution.
  */
-@Command(name = "sql", description = "Executes SQL query")
+@Command(name = "sql",
+        subcommands = {
+                PlanCommand.class
+        },
+        description = "Executes SQL query"
+)
 public class SqlCommand extends BaseCommand implements Callable<Integer> {
     @Option(names = JDBC_URL_OPTION, required = true, descriptionKey = JDBC_URL_KEY, description = JDBC_URL_OPTION_DESC)
     private String jdbc;
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/InvalidateCacheCommand.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/InvalidateCacheCommand.java
new file mode 100644
index 00000000000..731ad0e3c52
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/InvalidateCacheCommand.java
@@ -0,0 +1,52 @@
+/*
+ * 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.cli.commands.sql.planner;
+
+import jakarta.inject.Inject;
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.apache.ignite.internal.cli.call.sql.InvalidateCacheCallInput;
+import org.apache.ignite.internal.cli.call.sql.InvalidatePlannerCacheCall;
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.cluster.ClusterUrlProfileMixin;
+import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
+
+/**
+ * Sql planner cache invalidation command.
+ */
+@Command(name = "clear-cache", description = "Invalidates SQL planner cache")
+public class InvalidateCacheCommand extends BaseCommand implements Callable<Integer> {
+    /** Cluster endpoint URL option. */
+    @Mixin
+    private ClusterUrlProfileMixin clusterUrl;
+
+    @Inject
+    private InvalidatePlannerCacheCall call;
+
+    /** {@inheritDoc} */
+    @Override
+    public Integer call() {
+        return runPipeline(CallExecutionPipeline.builder(call)
+                .inputProvider(() -> new InvalidateCacheCallInput(clusterUrl.getClusterUrl(), List.of(), List.of()))
+                .exceptionHandler(ClusterNotInitializedExceptionHandler.createHandler("Failed to invalidate SQL planner cache"))
+        );
+    }
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/PlanCommand.java
similarity index 50%
copy from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
copy to modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/PlanCommand.java
index 0cd83ea8e90..fe3a237885d 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/PlanCommand.java
@@ -15,25 +15,18 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.sql.engine.prepare;
+package org.apache.ignite.internal.cli.commands.sql.planner;
 
-import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.internal.sql.engine.SqlOperationContext;
-import org.apache.ignite.internal.sql.engine.exec.LifecycleAware;
-import org.apache.ignite.internal.sql.engine.sql.ParsedResult;
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import picocli.CommandLine.Command;
 
 /**
- * Preparation service that accepts an AST of the query and returns a prepared query plan.
+ * Sql planner command.
  */
-public interface PrepareService extends LifecycleAware {
-
-    /**
-     * Prepare query plan.
-     *
-     * @param parsedResult Represent of parsed query as AST, which need to be planned.
-     * @param ctx Query context.
-     *
-     * @return Future that contains prepared query plan when completes.
-     */
-    CompletableFuture<QueryPlan> prepareAsync(ParsedResult parsedResult, SqlOperationContext ctx);
+@Command(name = "plan",
+        subcommands = {
+                InvalidateCacheCommand.class
+        },
+        description = "SQL planner operations")
+public class PlanCommand extends BaseCommand {
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlImpl.java
index bb2705e43b3..2e4abcb7aa8 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlImpl.java
@@ -83,7 +83,7 @@ import org.jetbrains.annotations.TestOnly;
  * Embedded implementation of the Ignite SQL query facade.
  */
 @SuppressWarnings("rawtypes")
-public class IgniteSqlImpl implements IgniteSql, IgniteComponent {
+public class IgniteSqlImpl implements IgniteSqlInternal, IgniteComponent {
     private static final IgniteLogger LOG = Loggers.forClass(IgniteSqlImpl.class);
 
     private static final int AWAIT_CURSOR_CLOSE_ON_STOP_IN_SECONDS = 10;
@@ -649,6 +649,11 @@ public class IgniteSqlImpl implements IgniteSql, IgniteComponent {
         });
     }
 
+    @Override
+    public void invalidatePlannerCache() {
+        queryProcessor.invalidatePlannerCache();
+    }
+
     private static void validateDmlResult(AsyncCursor.BatchedResult<InternalSqlRow> page) {
         if (page == null
                 || page.items() == null
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlInternal.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlInternal.java
new file mode 100644
index 00000000000..a560c28728a
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/IgniteSqlInternal.java
@@ -0,0 +1,10 @@
+package org.apache.ignite.internal.sql.api;
+
+import org.apache.ignite.sql.IgniteSql;
+
+/**
+ * Internal SQL facade.
+ */
+public interface IgniteSqlInternal extends IgniteSql {
+    void invalidatePlannerCache();
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
index 29e497a3dfa..4d0fc33d5b1 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
@@ -70,4 +70,8 @@ public interface QueryProcessor extends IgniteComponent {
             String qry,
             Object... params
     );
+
+    default void invalidatePlannerCache() {
+
+    }
 }
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 f2e11594072..cd2f7eaa992 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
@@ -604,6 +604,11 @@ public class SqlQueryProcessor implements QueryProcessor, SystemViewProvider {
         return List.of(queriesViewProvider.get());
     }
 
+    @Override
+    public void invalidatePlannerCache() {
+        prepareSvc.invalidateCache();
+    }
+
     /** Completes the provided future when the callback is called. */
     public static class PrefetchCallback implements QueryPrefetchCallback {
         private final CompletableFuture<Void> prefetchFuture = new CompletableFuture<>();
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
index 0cd83ea8e90..172da6c2a9f 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
@@ -36,4 +36,6 @@ public interface PrepareService extends LifecycleAware {
      * @return Future that contains prepared query plan when completes.
      */
     CompletableFuture<QueryPlan> prepareAsync(ParsedResult parsedResult, SqlOperationContext ctx);
+
+    default void invalidateCache() {};
 }
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 16a7cfcce28..964524581ba 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
@@ -305,6 +305,12 @@ public class PrepareServiceImpl implements PrepareService {
         );
     }
 
+    /** Invalidates planner cache. */
+    @Override
+    public void invalidateCache() {
+        cache.clear();
+    }
+
     private CompletableFuture<QueryPlan> prepareAsync0(
             ParsedResult parsedResult,
             PlanningContext planningContext