This is an automated email from the ASF dual-hosted git repository.
amashenkov 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 53b6524d85b IGNITE-26156 Drop cached SQL plans through CLI and REST
(#6533)
53b6524d85b is described below
commit 53b6524d85b48478f5316b40b7bc6848e6a06557
Author: Andrew V. Mashenkov <[email protected]>
AuthorDate: Mon Sep 15 14:15:25 2025 +0300
IGNITE-26156 Drop cached SQL plans through CLI and REST (#6533)
---
.../sql/planner/ItSqlPlannerCommandTest.java | 54 +++++
.../sql/planner/ItSqlPlannerReplCommandTest.java} | 19 +-
.../cli/call/sql/InvalidateCacheCallInput.java | 65 ++++++
.../cli/call/sql/InvalidatePlannerCacheCall.java | 55 +++++
.../internal/cli/commands/sql/SqlCommand.java | 98 +++-----
.../sql/{SqlCommand.java => SqlExecCommand.java} | 6 +-
...SqlReplCommand.java => SqlExecReplCommand.java} | 2 +-
.../internal/cli/commands/sql/SqlReplCommand.java | 250 +++------------------
.../sql/planner/InvalidateCacheCommand.java | 56 +++++
.../sql/planner/InvalidateCacheReplCommand.java | 62 +++++
.../commands/sql/planner/SqlPlannerCommand.java} | 21 +-
.../sql/planner/SqlPlannerReplCommand.java} | 21 +-
.../internal/cli/commands/sql/SqlCommandTest.java | 5 +-
modules/client-handler/build.gradle | 3 +-
modules/client/build.gradle | 2 +-
.../org/apache/ignite/internal/sql/SqlCommon.java | 3 +
modules/partition-replicator/build.gradle | 1 +
.../ignite/internal/rest/api/sql/SqlQueryApi.java | 20 ++
.../internal/rest/sql/SqlQueryController.java | 14 ++
modules/sql-engine-api/build.gradle | 3 +
.../ignite/internal/sql/engine/AsyncSqlCursor.java | 0
.../ignite/internal/sql/engine/InternalSqlRow.java | 0
.../ignite/internal/sql/engine/QueryProcessor.java | 11 +
.../ignite/internal/sql/engine/SqlProperties.java | 9 +-
.../ignite/internal/sql/engine/SqlQueryType.java | 0
.../internal/sql/engine/exec/AsyncDataCursor.java | 0
.../sql/engine/prepare/ParameterMetadata.java | 0
.../internal/sql/engine/prepare/ParameterType.java | 16 --
.../internal/sql/engine/prepare/QueryMetadata.java | 0
.../prepare/partitionawareness/DirectTxMode.java | 0
.../PartitionAwarenessMetadata.java | 2 -
modules/sql-engine/build.gradle | 1 +
.../ignite/internal/sql/api/IgniteSqlImpl.java | 12 +-
.../sql/api/PublicApiThreadingIgniteSql.java | 3 +-
.../internal/sql/engine/SqlQueryProcessor.java | 12 +-
.../sql/engine/prepare/PrepareService.java | 12 +
.../sql/engine/prepare/PrepareServiceImpl.java | 133 ++++++++---
.../ignite/internal/sql/engine/util/TypeUtils.java | 16 ++
.../sql/engine/exec/ExecutionServiceImplTest.java | 3 +-
.../sql/engine/exec/RuntimeSortedIndexTest.java | 4 +-
.../sql/engine/exec/rel/AbstractExecutionTest.java | 4 +-
.../sql/engine/framework/TestBuilders.java | 3 +-
.../internal/sql/engine/framework/TestNode.java | 3 +-
.../sql/engine/prepare/PrepareServiceImplTest.java | 137 +++++++++--
.../internal/sql/engine/util/QueryCheckerImpl.java | 3 +-
45 files changed, 739 insertions(+), 405 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
new file mode 100644
index 00000000000..1ca0c8a7be2
--- /dev/null
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerCommandTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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 static
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
+
+import org.apache.ignite.internal.cli.commands.sql.CliSqlCommandTestBase;
+import org.apache.ignite.internal.util.ArrayUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link SqlPlannerCommand}.
+ */
+class ItSqlPlannerCommandTest extends CliSqlCommandTestBase {
+ private static final String[] INVALIDATE_CACHE_COMMAND =
{"invalidate-cache"};
+
+ @Override
+ protected Class<?> getCommandClass() {
+ return SqlPlannerCommand.class;
+ }
+
+ @Test
+ void clearCache() {
+ execute(ArrayUtils.concat(INVALIDATE_CACHE_COMMAND,
CLUSTER_URL_OPTION, NODE_URL));
+
+ assertErrOutputIsEmpty();
+ assertExitCodeIs(0);
+ assertOutputContains("Successfully cleared SQL query plan cache.");
+ }
+
+ @Test
+ void clearCacheFiltered() {
+ execute(ArrayUtils.concat(INVALIDATE_CACHE_COMMAND,
CLUSTER_URL_OPTION, NODE_URL, "--tables", "PUBLIC.\"test\""));
+
+ assertErrOutputIsEmpty();
+ assertExitCodeIs(0);
+ assertOutputContains("Successfully cleared SQL query plan cache.");
+ }
+}
diff --git a/modules/sql-engine-api/build.gradle
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerReplCommandTest.java
similarity index 68%
copy from modules/sql-engine-api/build.gradle
copy to
modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerReplCommandTest.java
index bc8420b1d7f..d14a18a280d 100644
--- a/modules/sql-engine-api/build.gradle
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/planner/ItSqlPlannerReplCommandTest.java
@@ -15,15 +15,14 @@
* limitations under the License.
*/
-apply from: "$rootDir/buildscripts/java-core.gradle"
-apply from: "$rootDir/buildscripts/publishing.gradle"
-apply from: "$rootDir/buildscripts/java-junit5.gradle"
-apply from: "$rootDir/buildscripts/java-test-fixtures.gradle"
+package org.apache.ignite.internal.cli.commands.sql.planner;
-dependencies {
- implementation project(':ignite-core')
-
- implementation libs.jetbrains.annotations
+/**
+ * Tests for {@link SqlPlannerReplCommand}.
+ */
+public class ItSqlPlannerReplCommandTest extends ItSqlPlannerCommandTest {
+ @Override
+ protected Class<?> getCommandClass() {
+ return SqlPlannerReplCommand.class;
+ }
}
-
-description = 'ignite-sql-engine-api'
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..b7397ea7d9e
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidateCacheCallInput.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+ /** Returns {@link InvalidateCacheCallInput} with specified arguments. */
+ public static InvalidateCacheCallInput of(String clusterUrl, @Nullable
List<String> tables, @Nullable List<String> targetNodes) {
+ return new InvalidateCacheCallInput(clusterUrl, tables, targetNodes);
+ }
+
+ private 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..0885127c40d
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/sql/InvalidatePlannerCacheCall.java
@@ -0,0 +1,55 @@
+/*
+ * 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.core.call.Call;
+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, String> {
+ private final ApiClientFactory clientFactory;
+
+ public InvalidatePlannerCacheCall(ApiClientFactory clientFactory) {
+ this.clientFactory = clientFactory;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public DefaultCallOutput<String> execute(InvalidateCacheCallInput input) {
+ SqlApi client = createApiClient(input);
+
+ try {
+ 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 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..9ab52fdd34b 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
@@ -17,79 +17,47 @@
package org.apache.ignite.internal.cli.commands.sql;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_KEY;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_OPTION;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_OPTION_DESC;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION_DESC;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-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.core.call.CallExecutionPipeline;
-import org.apache.ignite.internal.cli.core.call.StringCallInput;
-import org.apache.ignite.internal.cli.core.exception.ExceptionWriter;
-import org.apache.ignite.internal.cli.core.exception.IgniteCliException;
-import
org.apache.ignite.internal.cli.core.exception.handler.SqlExceptionHandler;
-import org.apache.ignite.internal.cli.decorators.SqlQueryResultDecorator;
-import org.apache.ignite.internal.cli.sql.SqlManager;
-import picocli.CommandLine.ArgGroup;
+import org.apache.ignite.internal.cli.commands.sql.planner.SqlPlannerCommand;
+import org.apache.ignite.internal.util.ArrayUtils;
+import picocli.CommandLine;
import picocli.CommandLine.Command;
-import picocli.CommandLine.Option;
-import picocli.CommandLine.Parameters;
+import picocli.CommandLine.IFactory;
+import picocli.CommandLine.Unmatched;
/**
- * Command for sql execution.
+ * Command for sql operations.
+ *
+ * <p>The class describes subcommands and redirect calls to default subcommand
{@link SqlExecCommand} if no subcommand was specified.
+ *
+ * @see SqlExecCommand
*/
-@Command(name = "sql", description = "Executes SQL query")
+@Command(name = "sql",
+ subcommands = {
+ SqlPlannerCommand.class
+ },
+ description = "SQL query engine operations."
+)
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;
-
- @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")
- private String command;
-
- @Option(names = SCRIPT_FILE_OPTION, description =
SCRIPT_FILE_OPTION_DESC)
- private File file;
- }
-
- private static String extract(File file) {
- try {
- return String.join("\n", Files.readAllLines(file.toPath(),
StandardCharsets.UTF_8));
- } catch (IOException e) {
- throw new IgniteCliException("File [" + file.getAbsolutePath() +
"] not found");
- }
- }
+ @Unmatched
+ private String[] args;
- /**
- * {@inheritDoc}
- */
@Override
- public Integer call() {
- try (SqlManager sqlManager = new SqlManager(jdbc)) {
- 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))
- );
- } catch (SQLException e) {
- ExceptionWriter exceptionWriter =
ExceptionWriter.fromPrintWriter(spec.commandLine().getErr());
- return SqlExceptionHandler.INSTANCE.handle(exceptionWriter, e);
- }
+ public Integer call() throws Exception {
+ // Picocli lack flexibility parameter validation for subcommands +
parser can't distinct positional parameter and subcommand in
+ // some cases. That leads to unexpected behavior.
+ //
+ // With RunLast strategy (see IExecutionStrategy) is used and all
parent parameters have Scope.LOCAL,
+ // we don't expect parent command parameters be validated when running
a subcommand (this just make no sense).
+ // To overcome this issues, we implement command in separate class
(see SqlExecCommand) and redirect call to it.
+ IFactory factory = spec.commandLine().getFactory();
+ CommandLine commandLine = new
CommandLine(factory.create(SqlExecCommand.class), factory)
+ .setErr(spec.commandLine().getErr())
+ .setOut(spec.commandLine().getOut())
+ .setDefaultValueProvider(spec.defaultValueProvider())
+
.setExecutionExceptionHandler(spec.commandLine().getExecutionExceptionHandler());
+
+ return commandLine.execute(args == null ?
ArrayUtils.STRING_EMPTY_ARRAY : args);
}
}
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/SqlExecCommand.java
similarity index 97%
copy from
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
copy to
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlExecCommand.java
index 0c23597b4db..9f0144497bf 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/SqlExecCommand.java
@@ -49,7 +49,7 @@ import picocli.CommandLine.Parameters;
* Command for sql execution.
*/
@Command(name = "sql", description = "Executes SQL query")
-public class SqlCommand extends BaseCommand implements Callable<Integer> {
+public class SqlExecCommand extends BaseCommand implements Callable<Integer> {
@Option(names = JDBC_URL_OPTION, required = true, descriptionKey =
JDBC_URL_KEY, description = JDBC_URL_OPTION_DESC)
private String jdbc;
@@ -75,9 +75,7 @@ public class SqlCommand extends BaseCommand implements
Callable<Integer> {
}
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
@Override
public Integer call() {
try (SqlManager sqlManager = new SqlManager(jdbc)) {
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlExecReplCommand.java
similarity index 99%
copy from
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
copy to
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlExecReplCommand.java
index d15ee44cd86..244d2eb1c9d 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlExecReplCommand.java
@@ -80,7 +80,7 @@ import picocli.CommandLine.Parameters;
* Command for sql execution in REPL mode.
*/
@Command(name = "sql", description = "Executes SQL query")
-public class SqlReplCommand extends BaseCommand implements Runnable {
+public class SqlExecReplCommand extends BaseCommand implements Runnable {
@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/SqlReplCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
index d15ee44cd86..539a475894a 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
@@ -17,227 +17,47 @@
package org.apache.ignite.internal.cli.commands.sql;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_KEY;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_OPTION;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_OPTION_DESC;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION_DESC;
-import static org.apache.ignite.internal.cli.core.style.AnsiStringSupport.ansi;
-import static org.apache.ignite.internal.cli.core.style.AnsiStringSupport.fg;
-
-import jakarta.inject.Inject;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.sql.SQLException;
-import java.util.regex.Pattern;
-import org.apache.ignite.internal.cli.call.sql.SqlQueryCall;
+import java.util.concurrent.Callable;
import org.apache.ignite.internal.cli.commands.BaseCommand;
-import
org.apache.ignite.internal.cli.commands.sql.help.IgniteSqlCommandCompleter;
-import
org.apache.ignite.internal.cli.commands.treesitter.highlighter.SqlAttributedStringHighlighter;
-import org.apache.ignite.internal.cli.config.CliConfigKeys;
-import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
-import org.apache.ignite.internal.cli.core.CallExecutionPipelineProvider;
-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.ExceptionHandlers;
-import org.apache.ignite.internal.cli.core.exception.ExceptionWriter;
-import org.apache.ignite.internal.cli.core.exception.IgniteCliApiException;
-import org.apache.ignite.internal.cli.core.exception.IgniteCliException;
-import
org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
-import
org.apache.ignite.internal.cli.core.exception.handler.SqlExceptionHandler;
-import org.apache.ignite.internal.cli.core.repl.Repl;
-import org.apache.ignite.internal.cli.core.repl.Session;
-import
org.apache.ignite.internal.cli.core.repl.executor.RegistryCommandExecutor;
-import org.apache.ignite.internal.cli.core.repl.executor.ReplExecutorProvider;
-import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
-import org.apache.ignite.internal.cli.core.style.AnsiStringSupport.Color;
-import org.apache.ignite.internal.cli.decorators.SqlQueryResultDecorator;
-import org.apache.ignite.internal.cli.sql.SqlManager;
-import org.apache.ignite.internal.cli.sql.SqlSchemaProvider;
-import org.apache.ignite.internal.util.StringUtils;
-import org.apache.ignite.rest.client.api.ClusterManagementApi;
-import org.apache.ignite.rest.client.invoker.ApiException;
-import org.jline.reader.EOFError;
-import org.jline.reader.Highlighter;
-import org.jline.reader.LineReader;
-import org.jline.reader.ParsedLine;
-import org.jline.reader.Parser;
-import org.jline.reader.SyntaxError;
-import org.jline.reader.impl.DefaultHighlighter;
-import org.jline.reader.impl.DefaultParser;
-import org.jline.reader.impl.completer.AggregateCompleter;
-import org.jline.utils.AttributedString;
-import picocli.CommandLine.ArgGroup;
+import
org.apache.ignite.internal.cli.commands.sql.planner.SqlPlannerReplCommand;
+import org.apache.ignite.internal.util.ArrayUtils;
+import picocli.CommandLine;
import picocli.CommandLine.Command;
-import picocli.CommandLine.Option;
-import picocli.CommandLine.Parameters;
+import picocli.CommandLine.IFactory;
+import picocli.CommandLine.Unmatched;
/**
- * Command for sql execution in REPL mode.
+ * Command for sql operations in REPL mode.
+ *
+ * <p>The class describes subcommands and redirect calls to default subcommand
{@link SqlExecReplCommand} if no subcommand was specified.
+ *
+ * @see SqlExecReplCommand
*/
-@Command(name = "sql", description = "Executes SQL query")
-public class SqlReplCommand extends BaseCommand implements Runnable {
- @Option(names = JDBC_URL_OPTION, required = true, descriptionKey =
JDBC_URL_KEY, description = JDBC_URL_OPTION_DESC)
- private String jdbc;
-
- @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
- private boolean plain;
-
- @ArgGroup
- private ExecOptions execOptions;
-
- private static class ExecOptions {
- @Parameters(index = "0", description = "SQL query to execute",
defaultValue = Option.NULL_VALUE)
- private String command;
-
- @Option(names = SCRIPT_FILE_OPTION, description =
SCRIPT_FILE_OPTION_DESC, defaultValue = Option.NULL_VALUE)
- private File file;
- }
-
- @Inject
- private ReplExecutorProvider replExecutorProvider;
-
- @Inject
- private ConfigManagerProvider configManagerProvider;
+@Command(name = "sql",
+ subcommands = {
+ SqlPlannerReplCommand.class,
+ },
+ description = "SQL query engine operations."
+)
+public class SqlReplCommand extends BaseCommand implements Callable<Integer> {
+ @Unmatched
+ private String[] args;
- @Inject
- private Session session;
-
- @Inject
- private ApiClientFactory clientFactory;
-
- private static String extract(File file) {
- try {
- return String.join("\n", Files.readAllLines(file.toPath(),
StandardCharsets.UTF_8));
- } catch (IOException e) {
- throw new IgniteCliException("File [" + file.getAbsolutePath() +
"] not found");
- }
- }
-
- /**
- * {@inheritDoc}
- */
@Override
- public void run() {
- try (SqlManager sqlManager = new SqlManager(jdbc)) {
- // When passing white space to this command, picocli will treat it
as a positional argument
- if (execOptions == null ||
(StringUtils.nullOrBlank(execOptions.command) && execOptions.file == null)) {
- SqlSchemaProvider schemaProvider = new
SqlSchemaProvider(sqlManager::getMetadata);
- schemaProvider.initStateAsync();
-
- SqlCompleter sqlCompleter = new SqlCompleter(schemaProvider);
- IgniteSqlCommandCompleter sqlCommandCompleter = new
IgniteSqlCommandCompleter();
-
- replExecutorProvider.get().execute(Repl.builder()
- .withPromptProvider(() ->
ansi(fg(Color.GREEN).mark("sql-cli> ")))
- .withCompleter(new
AggregateCompleter(sqlCommandCompleter, sqlCompleter))
- .withCommandClass(SqlReplTopLevelCliCommand.class)
-
.withCallExecutionPipelineProvider(provider(sqlManager))
- .withHistoryFileName("sqlhistory")
- .withAutosuggestionsWidgets()
- .withHighlighter(highlightingEnabled() ? new
HighlighterImpl() : new DefaultHighlighter())
- .withParser(multilineSupported() ? new
MultilineParser() : new DefaultParser())
- .build());
- } else {
- String executeCommand = execOptions.file != null ?
extract(execOptions.file) : execOptions.command;
- createSqlExecPipeline(sqlManager,
executeCommand).runPipeline();
- }
- } catch (SQLException e) {
- String url = session.info() == null ? null :
session.info().nodeUrl();
-
- ExceptionWriter exceptionWriter =
ExceptionWriter.fromPrintWriter(spec.commandLine().getErr());
- try {
- if (url != null) {
- new
ClusterManagementApi(clientFactory.getClient(url)).clusterState();
- }
-
- SqlExceptionHandler.INSTANCE.handle(exceptionWriter, e);
- } catch (ApiException apiE) {
- new ClusterNotInitializedExceptionHandler("Failed to start sql
repl mode", "cluster init")
- .handle(exceptionWriter, new
IgniteCliApiException(apiE, url));
- }
- }
- }
-
- private boolean multilineSupported() {
- return
Boolean.parseBoolean(configManagerProvider.get().getCurrentProperty(CliConfigKeys.SQL_MULTILINE.value()));
- }
-
- private boolean highlightingEnabled() {
- return
Boolean.parseBoolean(configManagerProvider.get().getCurrentProperty(CliConfigKeys.SYNTAX_HIGHLIGHTING.value()));
- }
-
- /**
- * Multiline parser, expects ";" at the end of the line.
- */
- private static final class MultilineParser implements Parser {
-
- private static final Parser DEFAULT_PARSER = new DefaultParser();
-
- @Override
- public ParsedLine parse(String line, int cursor, Parser.ParseContext
context) throws SyntaxError {
- if ((ParseContext.UNSPECIFIED == context ||
ParseContext.ACCEPT_LINE == context)
- && !line.trim().endsWith(";")) {
- throw new EOFError(-1, cursor, "Missing semicolon (;)");
- }
-
- return DEFAULT_PARSER.parse(line, cursor, context);
- }
- }
-
- private static class HighlighterImpl implements Highlighter {
-
- @Override
- public AttributedString highlight(LineReader lineReader, String s) {
- return SqlAttributedStringHighlighter.highlight(s);
- }
-
- @Override
- public void setErrorPattern(Pattern pattern) {
- }
-
- @Override
- public void setErrorIndex(int i) {
- }
- }
-
- private CallExecutionPipelineProvider provider(SqlManager sqlManager) {
- return (executor, exceptionHandlers, line) ->
executor.hasCommand(dropSemicolon(line))
- ? createInternalCommandPipeline(executor, exceptionHandlers,
line)
- : createSqlExecPipeline(sqlManager, line);
- }
-
- private CallExecutionPipeline<?, ?> createSqlExecPipeline(SqlManager
sqlManager, String line) {
- return CallExecutionPipeline.builder(new SqlQueryCall(sqlManager))
- .inputProvider(() -> new StringCallInput(line))
- .output(spec.commandLine().getOut())
- .errOutput(spec.commandLine().getErr())
- .decorator(new SqlQueryResultDecorator(plain))
- .verbose(verbose)
- .exceptionHandler(SqlExceptionHandler.INSTANCE)
- .build();
- }
-
- private CallExecutionPipeline<?, ?>
createInternalCommandPipeline(RegistryCommandExecutor call,
- ExceptionHandlers exceptionHandlers,
- String line) {
- return CallExecutionPipeline.builder(call)
- .inputProvider(() -> new StringCallInput(dropSemicolon(line)))
- .output(spec.commandLine().getOut())
- .errOutput(spec.commandLine().getErr())
- .exceptionHandlers(exceptionHandlers)
- .verbose(verbose)
- .build();
- }
-
- private static String dropSemicolon(String line) {
- if (line.trim().endsWith(";")) {
- line = line.substring(0, line.length() - 1);
- }
- return line;
+ public Integer call() throws Exception {
+ // Picocli lack flexibility parameter validation for subcommands +
parser can't distinct positional parameter and subcommand in
+ // some cases. That leads to unexpected behavior.
+ //
+ // With RunLast strategy (see IExecutionStrategy) is used and all
parent parameters have Scope.LOCAL,
+ // we don't expect parent command parameters be validated when running
a subcommand (this just make no sense).
+ // To overcome this issues, we implement command in separate class
(see SqlExecReplCommand) and redirect call to it.
+ IFactory factory = spec.commandLine().getFactory();
+ CommandLine commandLine = new
CommandLine(factory.create(SqlExecReplCommand.class), factory)
+ .setErr(spec.commandLine().getErr())
+ .setOut(spec.commandLine().getOut())
+ .setDefaultValueProvider(spec.defaultValueProvider())
+
.setExecutionExceptionHandler(spec.commandLine().getExecutionExceptionHandler());
+
+ return commandLine.execute(args == null ?
ArrayUtils.STRING_EMPTY_ARRAY : args);
}
}
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..3c9d3a2fa5a
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/InvalidateCacheCommand.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+import picocli.CommandLine.Option;
+
+/**
+ * Sql planner cache invalidation command.
+ */
+@Command(name = "invalidate-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;
+
+ @Option(names = "--tables", description = "Tables filter", split = ",")
+ private List<String> tables;
+
+ /** {@inheritDoc} */
+ @Override
+ public Integer call() {
+ return runPipeline(CallExecutionPipeline.builder(call)
+ .inputProvider(() ->
InvalidateCacheCallInput.of(clusterUrl.getClusterUrl(), tables, List.of()))
+
.exceptionHandler(ClusterNotInitializedExceptionHandler.createHandler("Failed
to invalidate SQL planner cache"))
+ );
+ }
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/InvalidateCacheReplCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/InvalidateCacheReplCommand.java
new file mode 100644
index 00000000000..eff8db3aa8e
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/InvalidateCacheReplCommand.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.commands.sql.planner;
+
+import jakarta.inject.Inject;
+import java.util.List;
+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.ClusterUrlMixin;
+import
org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion;
+import
org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
+import org.apache.ignite.internal.cli.core.flow.builder.Flows;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
+import picocli.CommandLine.Option;
+
+/**
+ * Sql planner cache invalidation command in REPL mode.
+ */
+@Command(name = "invalidate-cache", description = "Invalidates SQL planner
cache")
+public class InvalidateCacheReplCommand extends BaseCommand implements
Runnable {
+ /** Cluster endpoint URL option. */
+ @Mixin
+ private ClusterUrlMixin clusterUrl;
+
+ @Inject
+ private ConnectToClusterQuestion question;
+
+ @Inject
+ private InvalidatePlannerCacheCall call;
+
+ @Option(names = "--tables", description = "Tables filter", split = ",")
+ private List<String> tables;
+
+ /** {@inheritDoc} */
+ @Override
+ public void run() {
+ runFlow(question.askQuestionIfNotConnected(clusterUrl.getClusterUrl())
+ .map(url -> InvalidateCacheCallInput.of(url, tables,
List.of()))
+
+ .then(Flows.fromCall(call))
+
.exceptionHandler(ClusterNotInitializedExceptionHandler.createHandler("Failed
to invalidate SQL planner cache"))
+ .print()
+ );
+ }
+}
diff --git a/modules/sql-engine-api/build.gradle
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/SqlPlannerCommand.java
similarity index 66%
copy from modules/sql-engine-api/build.gradle
copy to
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/SqlPlannerCommand.java
index bc8420b1d7f..ade0bcece2e 100644
--- a/modules/sql-engine-api/build.gradle
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/SqlPlannerCommand.java
@@ -15,15 +15,18 @@
* limitations under the License.
*/
-apply from: "$rootDir/buildscripts/java-core.gradle"
-apply from: "$rootDir/buildscripts/publishing.gradle"
-apply from: "$rootDir/buildscripts/java-junit5.gradle"
-apply from: "$rootDir/buildscripts/java-test-fixtures.gradle"
+package org.apache.ignite.internal.cli.commands.sql.planner;
-dependencies {
- implementation project(':ignite-core')
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import picocli.CommandLine.Command;
- implementation libs.jetbrains.annotations
+/**
+ * SQL planner command.
+ */
+@Command(name = "planner",
+ subcommands = {
+ InvalidateCacheCommand.class
+ },
+ description = "SQL planner operations.")
+public class SqlPlannerCommand extends BaseCommand {
}
-
-description = 'ignite-sql-engine-api'
diff --git a/modules/sql-engine-api/build.gradle
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/SqlPlannerReplCommand.java
similarity index 65%
copy from modules/sql-engine-api/build.gradle
copy to
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/SqlPlannerReplCommand.java
index bc8420b1d7f..8427365617a 100644
--- a/modules/sql-engine-api/build.gradle
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/planner/SqlPlannerReplCommand.java
@@ -15,15 +15,18 @@
* limitations under the License.
*/
-apply from: "$rootDir/buildscripts/java-core.gradle"
-apply from: "$rootDir/buildscripts/publishing.gradle"
-apply from: "$rootDir/buildscripts/java-junit5.gradle"
-apply from: "$rootDir/buildscripts/java-test-fixtures.gradle"
+package org.apache.ignite.internal.cli.commands.sql.planner;
-dependencies {
- implementation project(':ignite-core')
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import picocli.CommandLine.Command;
- implementation libs.jetbrains.annotations
+/**
+ * SQL planner command in REPL mode.
+ */
+@Command(name = "planner",
+ subcommands = {
+ InvalidateCacheReplCommand.class
+ },
+ description = "SQL planner operations.")
+public class SqlPlannerReplCommand extends BaseCommand {
}
-
-description = 'ignite-sql-engine-api'
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/sql/SqlCommandTest.java
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/sql/SqlCommandTest.java
index 30a82bfc6c0..22602dc1de3 100644
---
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/sql/SqlCommandTest.java
+++
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/sql/SqlCommandTest.java
@@ -23,7 +23,10 @@ import
org.apache.ignite.internal.cli.commands.CliCommandTestBase;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-class SqlCommandTest extends CliCommandTestBase {
+/**
+ * Tests for {@link SqlCommandTest}.
+ */
+public class SqlCommandTest extends CliCommandTestBase {
@Override
protected Class<?> getCommandClass() {
diff --git a/modules/client-handler/build.gradle
b/modules/client-handler/build.gradle
index d6b6bfbe84d..905900bb826 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')
@@ -66,7 +67,7 @@ dependencies {
integrationTestImplementation project(':ignite-api')
integrationTestImplementation project(':ignite-network')
integrationTestImplementation project(':ignite-network-api')
- integrationTestImplementation project(':ignite-sql-engine')
+ integrationTestImplementation project(':ignite-sql-engine-api')
integrationTestImplementation project(':ignite-schema')
integrationTestImplementation project(':ignite-table')
integrationTestImplementation project(':ignite-metrics')
diff --git a/modules/client/build.gradle b/modules/client/build.gradle
index e43aec28a61..0a1067fac6f 100644
--- a/modules/client/build.gradle
+++ b/modules/client/build.gradle
@@ -43,7 +43,7 @@ dependencies {
testImplementation project(':ignite-client-handler')
testImplementation project(':ignite-configuration')
testImplementation project(':ignite-configuration-root')
- testImplementation project(':ignite-sql-engine')
+ testImplementation project(':ignite-sql-engine-api')
testImplementation project(':ignite-schema')
testImplementation project(':ignite-table')
testImplementation project(':ignite-network')
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlCommon.java
b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlCommon.java
index 6466aa1ce8a..2d398d15741 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlCommon.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlCommon.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.sql;
+import java.time.ZoneId;
import org.apache.ignite.lang.util.IgniteNameUtils;
import org.apache.ignite.table.QualifiedName;
@@ -29,4 +30,6 @@ public final class SqlCommon {
/** Default page size. */
public static final int DEFAULT_PAGE_SIZE = 1024;
+ /** Default time-zone ID. */
+ public static final ZoneId DEFAULT_TIME_ZONE_ID = ZoneId.of("UTC");
}
diff --git a/modules/partition-replicator/build.gradle
b/modules/partition-replicator/build.gradle
index 68f85521ba4..24fc89780b2 100644
--- a/modules/partition-replicator/build.gradle
+++ b/modules/partition-replicator/build.gradle
@@ -100,6 +100,7 @@ dependencies {
integrationTestImplementation project(':ignite-runner')
integrationTestImplementation project(':ignite-system-disaster-recovery')
integrationTestImplementation project(':ignite-configuration-storage')
+ integrationTestImplementation project(':ignite-sql-engine-api') // TODO:
IGNITE-22522 - remove.
integrationTestImplementation project(':ignite-sql-engine') // TODO:
IGNITE-22522 - remove.
integrationTestImplementation project(':ignite-system-view-api') // TODO:
IGNITE-22522 - remove.
integrationTestImplementation project(':ignite-system-view') // TODO:
IGNITE-22522 - remove.
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..89eb702dc4c 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.QueryProcessor;
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,
QueryProcessor.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..b4da92fecc2 100644
--- a/modules/sql-engine-api/build.gradle
+++ b/modules/sql-engine-api/build.gradle
@@ -21,7 +21,10 @@ 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 project(':ignite-schema')
+ implementation project(':ignite-transactions')
implementation libs.jetbrains.annotations
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/AsyncSqlCursor.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/AsyncSqlCursor.java
similarity index 100%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/AsyncSqlCursor.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/AsyncSqlCursor.java
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/InternalSqlRow.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/InternalSqlRow.java
similarity index 100%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/InternalSqlRow.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/InternalSqlRow.java
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
similarity index 85%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
index 29e497a3dfa..7c49859bed1 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
+++
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java
@@ -17,6 +17,7 @@
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;
@@ -70,4 +71,14 @@ public interface QueryProcessor extends IgniteComponent {
String qry,
Object... params
);
+
+ /**
+ * Invalidates planner cache if {@code tableNames} is empty, otherwise
invalidates only plans, which refers to the provided tables.
+ *
+ * @param tableNames Table names.
+ * @return Operation completion future.
+ */
+ default CompletableFuture<Void> invalidatePlannerCache(Set<String>
tableNames) {
+ return CompletableFuture.failedFuture(new
UnsupportedOperationException("Planner implementation doesn't support cache."));
+ }
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlProperties.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/SqlProperties.java
similarity index 93%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlProperties.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/SqlProperties.java
index 00858271de7..f0ebe73eac1 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlProperties.java
+++
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/SqlProperties.java
@@ -17,12 +17,10 @@
package org.apache.ignite.internal.sql.engine;
-import static
org.apache.ignite.internal.sql.engine.SqlQueryProcessor.DEFAULT_TIME_ZONE_ID;
-
import java.time.ZoneId;
import java.util.Set;
-import javax.annotation.Nullable;
import org.apache.ignite.internal.sql.SqlCommon;
+import org.jetbrains.annotations.Nullable;
/**
* An object that keeps values of the properties.
@@ -32,8 +30,9 @@ public class SqlProperties {
private Set<SqlQueryType> allowedQueryTypes = SqlQueryType.ALL;
private boolean allowMultiStatement = true;
private String defaultSchema = SqlCommon.DEFAULT_SCHEMA_NAME;
- private ZoneId timeZoneId = DEFAULT_TIME_ZONE_ID;
- @Nullable private String userName;
+ private ZoneId timeZoneId = SqlCommon.DEFAULT_TIME_ZONE_ID;
+ @Nullable
+ private String userName;
public SqlProperties() {
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryType.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryType.java
similarity index 100%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryType.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryType.java
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/AsyncDataCursor.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/exec/AsyncDataCursor.java
similarity index 100%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/AsyncDataCursor.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/exec/AsyncDataCursor.java
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ParameterMetadata.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ParameterMetadata.java
similarity index 100%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ParameterMetadata.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ParameterMetadata.java
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ParameterType.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ParameterType.java
similarity index 75%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ParameterType.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ParameterType.java
index 8afff2cf535..e8aa8416e1e 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ParameterType.java
+++
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ParameterType.java
@@ -17,8 +17,6 @@
package org.apache.ignite.internal.sql.engine.prepare;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.ignite.internal.sql.engine.util.TypeUtils;
import org.apache.ignite.internal.tostring.S;
import org.apache.ignite.sql.ColumnMetadata;
import org.apache.ignite.sql.ColumnType;
@@ -79,18 +77,4 @@ public final class ParameterType {
public String toString() {
return S.toString(this);
}
-
- /** Creates parameter metadata from the given logical type. */
- static ParameterType fromRelDataType(RelDataType type) {
- ColumnType columnType = TypeUtils.columnType(type);
- assert columnType != null : "No column type for " + type;
-
- int precision = columnType.lengthAllowed() ||
columnType.precisionAllowed()
- ? type.getPrecision()
- : ColumnMetadata.UNDEFINED_PRECISION;
-
- int scale = columnType.scaleAllowed() ? type.getScale() :
ColumnMetadata.UNDEFINED_SCALE;
-
- return new ParameterType(columnType, precision, scale,
type.isNullable());
- }
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryMetadata.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryMetadata.java
similarity index 100%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryMetadata.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryMetadata.java
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/DirectTxMode.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/DirectTxMode.java
similarity index 100%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/DirectTxMode.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/DirectTxMode.java
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadata.java
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadata.java
similarity index 98%
rename from
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadata.java
rename to
modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadata.java
index bb9c6512105..bee2e9b3e92 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadata.java
+++
b/modules/sql-engine-api/src/main/java/org/apache/ignite/internal/sql/engine/prepare/partitionawareness/PartitionAwarenessMetadata.java
@@ -38,8 +38,6 @@ import org.apache.ignite.internal.tostring.S;
* indexes[i] >= 0 => use dynamicParam[indexes[i]]
* indexes[i] < 0 => use hash[-(indexes[i] + 1)]
* </pre>
- *
- * @see PartitionAwarenessMetadataExtractor
*/
public final class PartitionAwarenessMetadata {
diff --git a/modules/sql-engine/build.gradle b/modules/sql-engine/build.gradle
index 0f9d3efad2a..c7b730cedfe 100644
--- a/modules/sql-engine/build.gradle
+++ b/modules/sql-engine/build.gradle
@@ -149,6 +149,7 @@ dependencies {
testFixturesImplementation project(':ignite-core')
testFixturesImplementation project(':ignite-api')
testFixturesImplementation project(':ignite-schema')
+ testFixturesImplementation project(':ignite-sql-engine-api')
testFixturesImplementation project(':ignite-transactions')
testFixturesImplementation project(':ignite-system-view-api')
testFixturesImplementation project(':ignite-system-view')
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..958a2b301f3 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
@@ -62,6 +62,7 @@ import org.apache.ignite.internal.util.AsyncCursor;
import org.apache.ignite.internal.util.ExceptionUtils;
import org.apache.ignite.internal.util.IgniteSpinBusyLock;
import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.wrapper.Wrapper;
import org.apache.ignite.lang.CancellationToken;
import org.apache.ignite.lang.TraceableException;
import org.apache.ignite.lang.util.IgniteNameUtils;
@@ -83,7 +84,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 IgniteSql, IgniteComponent, Wrapper {
private static final IgniteLogger LOG =
Loggers.forClass(IgniteSqlImpl.class);
private static final int AWAIT_CURSOR_CLOSE_ON_STOP_IN_SECONDS = 10;
@@ -690,6 +691,15 @@ public class IgniteSqlImpl implements IgniteSql,
IgniteComponent {
return IgniteUtils.getInterruptibly(future);
}
+ @Override
+ public <T> T unwrap(Class<T> classToUnwrap) {
+ if (classToUnwrap.isAssignableFrom(QueryProcessor.class)) {
+ return classToUnwrap.cast(queryProcessor);
+ }
+
+ return classToUnwrap.cast(this);
+ }
+
private static class ScriptHandler {
private final CompletableFuture<Void> resFut;
private final List<Throwable> cursorCloseErrors =
Collections.synchronizedList(new ArrayList<>());
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/PublicApiThreadingIgniteSql.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/PublicApiThreadingIgniteSql.java
index e552380777a..323230338eb 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/PublicApiThreadingIgniteSql.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/PublicApiThreadingIgniteSql.java
@@ -25,6 +25,7 @@ import java.util.concurrent.Executor;
import java.util.function.Supplier;
import org.apache.ignite.internal.thread.PublicApiThreading;
import org.apache.ignite.internal.wrapper.Wrapper;
+import org.apache.ignite.internal.wrapper.Wrappers;
import org.apache.ignite.lang.CancellationToken;
import org.apache.ignite.sql.BatchedArguments;
import org.apache.ignite.sql.IgniteSql;
@@ -220,6 +221,6 @@ public class PublicApiThreadingIgniteSql implements
IgniteSql, Wrapper {
@Override
public <T> T unwrap(Class<T> classToUnwrap) {
- return classToUnwrap.cast(sql);
+ return Wrappers.unwrap(sql, classToUnwrap);
}
}
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 8e76a785cdd..0bdcb44fad5 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
@@ -27,11 +27,11 @@ import static
org.apache.ignite.internal.util.IgniteUtils.closeAll;
import static org.apache.ignite.lang.ErrorGroups.Common.NODE_STOPPING_ERR;
import static org.apache.ignite.lang.ErrorGroups.Sql.EXECUTION_CANCELLED_ERR;
-import java.time.ZoneId;
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;
@@ -57,6 +57,7 @@ import
org.apache.ignite.internal.placementdriver.event.PrimaryReplicaEvent;
import org.apache.ignite.internal.replicator.ReplicaService;
import org.apache.ignite.internal.schema.SchemaManager;
import org.apache.ignite.internal.schema.SchemaSyncService;
+import org.apache.ignite.internal.sql.SqlCommon;
import
org.apache.ignite.internal.sql.configuration.distributed.SqlDistributedConfiguration;
import
org.apache.ignite.internal.sql.configuration.local.SqlLocalConfiguration;
import org.apache.ignite.internal.sql.engine.api.kill.CancellableOperationType;
@@ -122,8 +123,6 @@ import org.jetbrains.annotations.TestOnly;
* Main implementation of {@link QueryProcessor}.
*/
public class SqlQueryProcessor implements QueryProcessor, SystemViewProvider {
- /** Default time-zone ID. */
- public static final ZoneId DEFAULT_TIME_ZONE_ID = ZoneId.of("UTC");
private static final int PARSED_RESULT_CACHE_SIZE = 10_000;
@@ -541,7 +540,7 @@ public class SqlQueryProcessor implements QueryProcessor,
SystemViewProvider {
.queryId(UUID.randomUUID())
// time zone is used in execution phase,
// so we may use any time zone for preparation only
- .timeZoneId(DEFAULT_TIME_ZONE_ID)
+ .timeZoneId(SqlCommon.DEFAULT_TIME_ZONE_ID)
.defaultSchemaName(schemaName)
.operationTime(timestamp)
.cancel(queryCancel)
@@ -606,6 +605,11 @@ public class SqlQueryProcessor implements QueryProcessor,
SystemViewProvider {
return List.of(queriesViewProvider.get());
}
+ @Override
+ public CompletableFuture<Void> invalidatePlannerCache(Set<String>
tableNames) {
+ return prepareSvc.invalidateCache(tableNames);
+ }
+
/** 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..b4dbab49fcf 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.
@@ -36,4 +38,14 @@ public interface PrepareService extends LifecycleAware {
* @return Future that contains prepared query plan when completes.
*/
CompletableFuture<QueryPlan> prepareAsync(ParsedResult parsedResult,
SqlOperationContext ctx);
+
+ /**
+ * Invalidates planner cache if {@code tableNames} is empty, otherwise
invalidates only plans, which refers to the provided tables.
+ *
+ * @param tableNames Table names.
+ * @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 16a7cfcce28..b7ddf7608a0 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,8 @@ 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 java.util.stream.Collectors;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.schema.SchemaPlus;
@@ -95,6 +98,8 @@ import org.apache.ignite.sql.ColumnMetadata;
import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.sql.ResultSetMetadata;
import org.apache.ignite.sql.SqlException;
+import org.apache.ignite.table.QualifiedName;
+import org.apache.ignite.table.QualifiedNameHelper;
import org.jetbrains.annotations.Nullable;
/**
@@ -248,7 +253,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,6 +310,32 @@ public class PrepareServiceImpl implements PrepareService {
);
}
+ /** {@inheritDoc} */
+ @Override
+ public CompletableFuture<Void> invalidateCache(Set<String> tableNames) {
+ return CompletableFuture.supplyAsync(() -> {
+ if (tableNames.isEmpty()) {
+ cache.clear();
+ } else {
+ Set<QualifiedName> qualifiedNames =
tableNames.stream().map(QualifiedName::parse).collect(Collectors.toSet());
+ cache.removeIfValue(p -> p.isDone() && planMatches(p.join(),
qualifiedNames::contains));
+ }
+
+ return null;
+ }, planningPool);
+ }
+
+ /** Check if the given query plan matches the given predicate. */
+ public static boolean planMatches(QueryPlan plan, Predicate<QualifiedName>
predicate) {
+ assert plan instanceof ExplainablePlan;
+
+ MatchingShuttle shuttle = new MatchingShuttle(predicate);
+
+ ((ExplainablePlan) plan).getRel().accept(shuttle);
+
+ return shuttle.matches();
+ }
+
private CompletableFuture<QueryPlan> prepareAsync0(
ParsedResult parsedResult,
PlanningContext planningContext
@@ -455,25 +486,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
);
@@ -539,10 +570,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
);
@@ -552,9 +583,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
);
@@ -611,25 +642,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
);
}
@@ -767,9 +798,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
@@ -819,7 +850,7 @@ public class PrepareServiceImpl implements PrepareService {
for (int i = 0; i < parameterRowType.getFieldCount(); i++) {
RelDataTypeField field = parameterRowType.getFieldList().get(i);
- ParameterType parameterType =
ParameterType.fromRelDataType(field.getType());
+ ParameterType parameterType =
TypeUtils.fromRelDataType(field.getType());
parameterTypes.add(parameterType);
}
@@ -906,7 +937,7 @@ public class PrepareServiceImpl implements PrepareService {
RelWithMetadata(
IgniteRel rel,
int numSources,
- @Nullable PartitionAwarenessMetadata paMetadata,
+ @Nullable PartitionAwarenessMetadata paMetadata,
@Nullable PartitionPruningMetadata ppMetadata
) {
this.rel = rel;
@@ -915,4 +946,42 @@ public class PrepareServiceImpl implements PrepareService {
this.ppMetadata = ppMetadata;
}
}
+
+ private static class MatchingShuttle extends IgniteRelShuttle {
+ private final Predicate<QualifiedName> tableNamePredicate;
+ private boolean matches;
+
+ private MatchingShuttle(Predicate<QualifiedName> tableNamePredicate) {
+ this.tableNamePredicate = tableNamePredicate;
+ matches = false;
+ }
+
+ /**
+ * Visits all children of a parent.
+ */
+ @Override
+ protected IgniteRel processNode(IgniteRel rel) {
+ if (!matches && rel.getTable() != null) {
+ List<String> tableName = rel.getTable().getQualifiedName();
+ assert tableName.size() == 2 : "Qualified table name
expected.";
+ matches =
tableNamePredicate.test(QualifiedNameHelper.fromNormalized(tableName.get(0),
tableName.get(1)));
+ }
+
+ 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;
+ }
+ }
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
index 25d4a97b0a0..368ef3d4033 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
@@ -66,6 +66,7 @@ import
org.apache.ignite.internal.sql.engine.exec.row.RowSchema;
import org.apache.ignite.internal.sql.engine.exec.row.RowSchemaTypes;
import org.apache.ignite.internal.sql.engine.exec.row.RowType;
import org.apache.ignite.internal.sql.engine.exec.row.TypeSpec;
+import org.apache.ignite.internal.sql.engine.prepare.ParameterType;
import org.apache.ignite.internal.sql.engine.type.IgniteCustomType;
import
org.apache.ignite.internal.sql.engine.type.IgniteCustomTypeCoercionRules;
import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
@@ -74,6 +75,7 @@ import org.apache.ignite.internal.type.NativeType;
import org.apache.ignite.internal.type.NativeTypes;
import org.apache.ignite.internal.type.TemporalNativeType;
import org.apache.ignite.internal.type.VarlenNativeType;
+import org.apache.ignite.sql.ColumnMetadata;
import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.sql.SqlException;
import org.jetbrains.annotations.Nullable;
@@ -152,6 +154,20 @@ public class TypeUtils {
}
}
+ /** Creates parameter metadata from the given logical type. */
+ public static ParameterType fromRelDataType(RelDataType type) {
+ ColumnType columnType = columnType(type);
+ assert columnType != null : "No column type for " + type;
+
+ int precision = columnType.lengthAllowed() ||
columnType.precisionAllowed()
+ ? type.getPrecision()
+ : ColumnMetadata.UNDEFINED_PRECISION;
+
+ int scale = columnType.scaleAllowed() ? type.getScale() :
ColumnMetadata.UNDEFINED_SCALE;
+
+ return new ParameterType(columnType, precision, scale,
type.isNullable());
+ }
+
private static class SupportedParamClassesHolder {
// TODO: https://issues.apache.org/jira/browse/IGNITE-17373
static final Set<ColumnType> UNSUPPORTED_COLUMN_TYPES_AS_PARAMETERS =
Set.of(ColumnType.PERIOD, ColumnType.DURATION);
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
index 92411d3dfe4..ce1731eb294 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
@@ -98,7 +98,6 @@ import
org.apache.ignite.internal.sql.engine.NodeLeftException;
import org.apache.ignite.internal.sql.engine.QueryCancel;
import org.apache.ignite.internal.sql.engine.QueryCancelledException;
import org.apache.ignite.internal.sql.engine.SqlOperationContext;
-import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
import
org.apache.ignite.internal.sql.engine.exec.ExecutionServiceImplTest.TestCluster.TestNode;
import org.apache.ignite.internal.sql.engine.exec.ddl.DdlCommandHandler;
import org.apache.ignite.internal.sql.engine.exec.exp.ExpressionFactoryImpl;
@@ -1213,7 +1212,7 @@ public class ExecutionServiceImplTest extends
BaseIgniteAbstractTest {
.cancel(new QueryCancel())
.operationTime(new HybridClockImpl().now())
.defaultSchemaName(SqlCommon.DEFAULT_SCHEMA_NAME)
- .timeZoneId(SqlQueryProcessor.DEFAULT_TIME_ZONE_ID)
+ .timeZoneId(SqlCommon.DEFAULT_TIME_ZONE_ID)
.txContext(ExplicitTxContext.fromTx(new
NoOpTransaction(nodeNames.get(0), false)));
}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/RuntimeSortedIndexTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/RuntimeSortedIndexTest.java
index 1fd333efa8b..1a335c42500 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/RuntimeSortedIndexTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/RuntimeSortedIndexTest.java
@@ -36,7 +36,7 @@ import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.util.ImmutableIntList;
import org.apache.calcite.util.Pair;
import org.apache.ignite.internal.network.ClusterNodeImpl;
-import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
+import org.apache.ignite.internal.sql.SqlCommon;
import org.apache.ignite.internal.sql.engine.exec.exp.ExpressionFactoryImpl;
import org.apache.ignite.internal.sql.engine.framework.ArrayRowHandler;
import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
@@ -128,7 +128,7 @@ public class RuntimeSortedIndexTest extends
IgniteAbstractTest {
ArrayRowHandler.INSTANCE,
Map.of(),
null,
- SqlQueryProcessor.DEFAULT_TIME_ZONE_ID,
+ SqlCommon.DEFAULT_TIME_ZONE_ID,
-1,
Clock.systemUTC(),
null
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractExecutionTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractExecutionTest.java
index 6e8c8c31434..ed8a167f6e2 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractExecutionTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractExecutionTest.java
@@ -53,7 +53,7 @@ import org.apache.ignite.internal.network.InternalClusterNode;
import org.apache.ignite.internal.schema.BinaryRowConverter;
import org.apache.ignite.internal.schema.BinaryTuple;
import org.apache.ignite.internal.schema.BinaryTupleSchema;
-import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
+import org.apache.ignite.internal.sql.SqlCommon;
import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
import org.apache.ignite.internal.sql.engine.exec.ExecutionId;
import org.apache.ignite.internal.sql.engine.exec.QueryTaskExecutorImpl;
@@ -155,7 +155,7 @@ public abstract class AbstractExecutionTest<T> extends
IgniteAbstractTest {
rowHandler(),
Map.of(),
TxAttributes.fromTx(new NoOpTransaction("fake-test-node",
false)),
- SqlQueryProcessor.DEFAULT_TIME_ZONE_ID,
+ SqlCommon.DEFAULT_TIME_ZONE_ID,
bufferSize,
Clock.systemUTC(),
null
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
index a44252aabfa..ab6b18ae8ff 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
@@ -98,7 +98,6 @@ import
org.apache.ignite.internal.partitiondistribution.Assignment;
import org.apache.ignite.internal.partitiondistribution.TokenizedAssignments;
import
org.apache.ignite.internal.partitiondistribution.TokenizedAssignmentsImpl;
import org.apache.ignite.internal.sql.SqlCommon;
-import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
import org.apache.ignite.internal.sql.engine.api.kill.OperationKillHandler;
import org.apache.ignite.internal.sql.engine.exec.ExecutableTable;
import org.apache.ignite.internal.sql.engine.exec.ExecutableTableRegistry;
@@ -565,7 +564,7 @@ public class TestBuilders {
private QueryTaskExecutor executor = null;
private InternalClusterNode node = null;
private Object[] dynamicParams = ArrayUtils.OBJECT_EMPTY_ARRAY;
- private ZoneId zoneId = SqlQueryProcessor.DEFAULT_TIME_ZONE_ID;
+ private ZoneId zoneId = SqlCommon.DEFAULT_TIME_ZONE_ID;
private Clock clock = Clock.systemUTC();
/** {@inheritDoc} */
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
index 978b57951db..abb91cf4029 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
@@ -55,7 +55,6 @@ import org.apache.ignite.internal.sql.engine.InternalSqlRow;
import org.apache.ignite.internal.sql.engine.QueryCancel;
import org.apache.ignite.internal.sql.engine.SqlOperationContext;
import org.apache.ignite.internal.sql.engine.SqlProperties;
-import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
import org.apache.ignite.internal.sql.engine.api.kill.OperationKillHandler;
import org.apache.ignite.internal.sql.engine.exec.ExchangeService;
import org.apache.ignite.internal.sql.engine.exec.ExchangeServiceImpl;
@@ -422,7 +421,7 @@ public class TestNode implements LifecycleAware {
.cancel(new QueryCancel())
.operationTime(clock.now())
.defaultSchemaName(SqlCommon.DEFAULT_SCHEMA_NAME)
- .timeZoneId(SqlQueryProcessor.DEFAULT_TIME_ZONE_ID)
+ .timeZoneId(SqlCommon.DEFAULT_TIME_ZONE_ID)
.txContext(ImplicitTxContext.create())
.parameters();
}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImplTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImplTest.java
index b6a7b9038fc..e21d918b9d6 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImplTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImplTest.java
@@ -22,6 +22,7 @@ import static
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThr
import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@@ -38,6 +39,7 @@ import java.time.Duration;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -311,22 +313,7 @@ public class PrepareServiceImplTest extends
BaseIgniteAbstractTest {
IgniteSchema schema = new IgniteSchema("PUBLIC", 0,
List.of(igniteTable));
Cache<Object, Object> cache =
CaffeineCacheFactory.INSTANCE.create(100);
- CacheFactory cacheFactory = new CacheFactory() {
- @Override
- public <K, V> Cache<K, V> create(int size) {
- return (Cache<K, V>) cache;
- }
-
- @Override
- public <K, V> Cache<K, V> create(int size, StatsCounter
statCounter) {
- return (Cache<K, V>) cache;
- }
-
- @Override
- public <K, V> Cache<K, V> create(int size, StatsCounter
statCounter, Duration expireAfterAccess) {
- return (Cache<K, V>) cache;
- }
- };
+ CacheFactory cacheFactory = new DummyCacheFactory(cache);
PrepareServiceImpl service = createPlannerService(schema,
cacheFactory, 100);
@@ -410,6 +397,101 @@ public class PrepareServiceImplTest extends
BaseIgniteAbstractTest {
});
}
+ @Test
+ public void invalidatePlannerCache() {
+ IgniteSchema schema = new IgniteSchema("PUBLIC", 0, List.of(
+ TestBuilders.table().name("T1").addColumn("C",
NativeTypes.INT32).distribution(IgniteDistributions.single()).build(),
+ TestBuilders.table().name("T2").addColumn("C",
NativeTypes.INT32).distribution(IgniteDistributions.single()).build()
+ ));
+
+ Cache<Object, Object> cache =
CaffeineCacheFactory.INSTANCE.create(100);
+
+ PrepareServiceImpl service = createPlannerService(schema, new
DummyCacheFactory(cache), 1000);
+
+ await(service.prepareAsync(parse("SELECT * FROM t1"),
createContext()));
+ await(service.prepareAsync(parse("SELECT * FROM t1 WHERE C > 0"),
createContext()));
+ await(service.prepareAsync(parse("SELECT * FROM t2"),
createContext()));
+
+ assertThat(cache.size(), is(3));
+
+ // Invalidate
+ await(service.invalidateCache(Set.of()));
+
+ assertThat(cache.size(), is(0));
+ }
+
+ @Test
+ public void invalidateQueryPlans() {
+ IgniteSchema schema = new IgniteSchema("PUBLIC", 0, List.of(
+ TestBuilders.table().name("T1").addColumn("C",
NativeTypes.INT32).distribution(IgniteDistributions.single()).build(),
+ TestBuilders.table().name("t2").addColumn("C",
NativeTypes.INT32).distribution(IgniteDistributions.single()).build()
+ ));
+
+ Cache<Object, Object> cache =
CaffeineCacheFactory.INSTANCE.create(100);
+
+ PrepareServiceImpl service = createPlannerService(schema, new
DummyCacheFactory(cache), 1000);
+
+ { // Simple name.
+ await(service.prepareAsync(parse("SELECT * FROM t1"),
createContext()));
+ await(service.prepareAsync(parse("SELECT * FROM t1 WHERE C > 0"),
createContext()));
+ QueryPlan queryPlan = await(service.prepareAsync(parse("SELECT *
FROM \"t2\""), createContext()));
+
+ assertThat(cache.size(), is(3));
+
+ // Case, when no plan matches.
+ await(service.invalidateCache(Set.of("t")));
+ assertThat(cache.size(), is(3));
+
+ await(service.invalidateCache(Set.of("t2")));
+ assertThat(cache.size(), is(3));
+
+ // Found and invalidate related plan.
+ await(service.invalidateCache(Set.of("t1")));
+ assertThat(cache.size(), is(1));
+
+ QueryPlan explainPlan = await(service.prepareAsync(
+ parse("explain plan for select * from \"t2\""),
+ createContext()
+ ));
+
+ ExplainPlan plan = (ExplainPlan) explainPlan;
+ assertThat(plan.plan(), sameInstance(queryPlan));
+
+ await(service.invalidateCache(Set.of("\"t2\"")));
+ assertThat(cache.size(), is(0));
+ }
+
+ { // Qualified name.
+ await(service.prepareAsync(parse("SELECT * FROM t1"),
createContext()));
+ await(service.prepareAsync(parse("SELECT * FROM t1 WHERE C > 0"),
createContext()));
+ QueryPlan queryPlan = await(service.prepareAsync(parse("SELECT *
FROM \"t2\""), createContext()));
+
+ assertThat(cache.size(), is(3));
+
+ // Case, when no plan matches.
+ await(service.invalidateCache(Set.of("PUBLIC.t2")));
+ assertThat(cache.size(), is(3));
+
+ await(service.invalidateCache(Set.of("MYSCHEMA.t1")));
+ assertThat(cache.size(), is(3));
+
+ // Found and invalidate related plan.
+ await(service.invalidateCache(Set.of("PUBLIC.t1")));
+ assertThat(cache.size(), is(1));
+
+ QueryPlan explainPlan = await(service.prepareAsync(
+ parse("explain plan for select * from \"t2\""),
+ createContext()
+ ));
+
+ ExplainPlan plan = (ExplainPlan) explainPlan;
+ assertThat(plan.plan(), sameInstance(queryPlan));
+
+ await(service.invalidateCache(Set.of("PUBLIC.\"t2\"")));
+ assertThat(cache.size(), is(0));
+ }
+ }
+
private static Stream<Arguments> parameterTypes() {
int noScale = ColumnMetadata.UNDEFINED_SCALE;
int noPrecision = ColumnMetadata.UNDEFINED_PRECISION;
@@ -491,4 +573,27 @@ public class PrepareServiceImplTest extends
BaseIgniteAbstractTest {
return service;
}
+
+ private static class DummyCacheFactory implements CacheFactory {
+ private final Cache<Object, Object> cache;
+
+ DummyCacheFactory(Cache<Object, Object> cache) {
+ this.cache = cache;
+ }
+
+ @Override
+ public <K, V> Cache<K, V> create(int size) {
+ return (Cache<K, V>) cache;
+ }
+
+ @Override
+ public <K, V> Cache<K, V> create(int size, StatsCounter statCounter) {
+ return (Cache<K, V>) cache;
+ }
+
+ @Override
+ public <K, V> Cache<K, V> create(int size, StatsCounter statCounter,
Duration expireAfterAccess) {
+ return (Cache<K, V>) cache;
+ }
+ }
}
diff --git
a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java
b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java
index c48e6a9a8b3..a78abc16249 100644
---
a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java
+++
b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java
@@ -52,7 +52,6 @@ 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.SqlQueryProcessor;
import org.apache.ignite.internal.sql.engine.SqlQueryType;
import org.apache.ignite.internal.sql.engine.hint.IgniteHint;
import org.apache.ignite.internal.sql.engine.prepare.QueryMetadata;
@@ -88,7 +87,7 @@ abstract class QueryCheckerImpl implements QueryChecker {
private Object[] params = OBJECT_EMPTY_ARRAY;
- private ZoneId timeZoneId = SqlQueryProcessor.DEFAULT_TIME_ZONE_ID;
+ private ZoneId timeZoneId = SqlCommon.DEFAULT_TIME_ZONE_ID;
private String defaultSchema = SqlCommon.DEFAULT_SCHEMA_NAME;