You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ko...@apache.org on 2023/07/05 10:25:46 UTC
[ignite-3] branch main updated: IGNITE-17765 Sql. Introduce cache for parsed statements (#2280)
This is an automated email from the ASF dual-hosted git repository.
korlov 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 5646997ae4 IGNITE-17765 Sql. Introduce cache for parsed statements (#2280)
5646997ae4 is described below
commit 5646997ae40d6fa1a936d928291d7bc2a09f5bf5
Author: korlov42 <ko...@gridgain.com>
AuthorDate: Wed Jul 5 13:25:40 2023 +0300
IGNITE-17765 Sql. Introduce cache for parsed statements (#2280)
---
.../internal/sql/engine/SqlQueryProcessor.java | 52 +++----
.../sql/engine/prepare/PrepareService.java | 4 +-
.../sql/engine/prepare/PrepareServiceImpl.java | 59 +++----
.../internal/sql/engine/sql/ParsedResult.java | 48 ++++++
.../PrepareService.java => sql/ParserService.java} | 21 +--
.../internal/sql/engine/sql/ParserServiceImpl.java | 130 ++++++++++++++++
.../ignite/internal/sql/engine/util/Cache.java | 52 +++++++
.../PrepareService.java => util/CacheFactory.java} | 20 +--
.../sql/engine/util/CaffeineCacheFactory.java | 66 ++++++++
.../engine/benchmarks/TpchPrepareBenchmark.java | 13 +-
.../sql/engine/exec/ExecutionServiceImplTest.java | 14 +-
.../internal/sql/engine/framework/TestNode.java | 29 ++--
.../sql/engine/sql/ParserServiceImplTest.java | 171 +++++++++++++++++++++
.../sql/engine/util/EmptyCacheFactory.java | 57 +++++++
.../internal/sql/engine/util/StatementChecker.java | 4 +-
15 files changed, 632 insertions(+), 108 deletions(-)
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 88e7b590d9..9b56c73757 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
@@ -23,7 +23,6 @@ import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_INVALID_ERR;
import static org.apache.ignite.lang.ErrorGroups.Sql.SESSION_EXPIRED_ERR;
import static org.apache.ignite.lang.ErrorGroups.Sql.SESSION_NOT_FOUND_ERR;
import static org.apache.ignite.lang.ErrorGroups.Sql.UNSUPPORTED_DDL_OPERATION_ERR;
-import static org.apache.ignite.lang.ErrorGroups.Sql.UNSUPPORTED_SQL_OPERATION_KIND_ERR;
import static org.apache.ignite.lang.IgniteStringFormatter.format;
import java.util.ArrayList;
@@ -41,8 +40,6 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.calcite.schema.SchemaPlus;
-import org.apache.calcite.sql.SqlKind;
-import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.util.Pair;
import org.apache.ignite.internal.catalog.CatalogManager;
@@ -81,11 +78,11 @@ import org.apache.ignite.internal.sql.engine.session.SessionId;
import org.apache.ignite.internal.sql.engine.session.SessionInfo;
import org.apache.ignite.internal.sql.engine.session.SessionManager;
import org.apache.ignite.internal.sql.engine.session.SessionProperty;
-import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
-import org.apache.ignite.internal.sql.engine.sql.ParseResult;
-import org.apache.ignite.internal.sql.engine.sql.StatementParseResult;
+import org.apache.ignite.internal.sql.engine.sql.ParsedResult;
+import org.apache.ignite.internal.sql.engine.sql.ParserService;
+import org.apache.ignite.internal.sql.engine.sql.ParserServiceImpl;
import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
-import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.engine.util.CaffeineCacheFactory;
import org.apache.ignite.internal.sql.engine.util.TypeUtils;
import org.apache.ignite.internal.storage.DataStorageManager;
import org.apache.ignite.internal.table.distributed.TableManager;
@@ -116,6 +113,8 @@ public class SqlQueryProcessor implements QueryProcessor {
/** Size of the cache for query plans. */
private static final int PLAN_CACHE_SIZE = 1024;
+ private static final int PARSED_RESULT_CACHE_SIZE = 10_000;
+
/** Size of the table access cache. */
private static final int TABLE_CACHE_SIZE = 1024;
@@ -136,6 +135,10 @@ public class SqlQueryProcessor implements QueryProcessor {
.set(SessionProperty.IDLE_TIMEOUT, DEFAULT_SESSION_IDLE_TIMEOUT)
.build();
+ private final ParserService parserService = new ParserServiceImpl(
+ PARSED_RESULT_CACHE_SIZE, CaffeineCacheFactory.INSTANCE
+ );
+
private final List<LifecycleAware> services = new ArrayList<>();
private final ClusterService clusterSrvc;
@@ -420,16 +423,12 @@ public class SqlQueryProcessor implements QueryProcessor {
AtomicReference<InternalTransaction> tx = new AtomicReference<>();
CompletableFuture<AsyncSqlCursor<List<Object>>> stage = start
- .thenApply(v -> {
- StatementParseResult parseResult = IgniteSqlParser.parse(sql, StatementParseResult.MODE);
- SqlNode sqlNode = parseResult.statement();
+ .thenCompose(ignored -> {
+ ParsedResult result = parserService.parse(sql);
- validateParsedStatement(context, outerTx, parseResult, sqlNode, params);
+ validateParsedStatement(context, outerTx, result, params);
- return sqlNode;
- })
- .thenCompose(sqlNode -> {
- boolean rwOp = dataModificationOp(sqlNode);
+ boolean rwOp = dataModificationOp(result);
boolean implicitTxRequired = outerTx == null;
@@ -453,7 +452,7 @@ public class SqlQueryProcessor implements QueryProcessor {
.plannerTimeout(PLANNER_TIMEOUT)
.build();
- return prepareSvc.prepareAsync(sqlNode, ctx)
+ return prepareSvc.prepareAsync(result, ctx)
.thenApply(plan -> {
var dataCursor = executionSrvc.executePlan(tx.get(), plan, ctx);
@@ -609,26 +608,19 @@ public class SqlQueryProcessor implements QueryProcessor {
}
/** Returns {@code true} if this is data modification operation. */
- private static boolean dataModificationOp(SqlNode sqlNode) {
- return SqlKind.DML.contains(sqlNode.getKind());
+ private static boolean dataModificationOp(ParsedResult parsedResult) {
+ return parsedResult.queryType() == SqlQueryType.DML;
}
/** Performs additional validation of a parsed statement. **/
private static void validateParsedStatement(
QueryContext context,
- InternalTransaction outerTx,
- ParseResult parseResult,
- SqlNode node,
+ @Nullable InternalTransaction outerTx,
+ ParsedResult parsedResult,
Object[] params
) {
Set<SqlQueryType> allowedTypes = context.allowedQueryTypes();
- SqlQueryType queryType = Commons.getQueryType(node);
-
- if (queryType == null) {
- throw new IgniteInternalException(UNSUPPORTED_SQL_OPERATION_KIND_ERR, "Unsupported operation ["
- + "sqlNodeKind=" + node.getKind() + "; "
- + "querySql=\"" + node + "\"]");
- }
+ SqlQueryType queryType = parsedResult.queryType();
if (!allowedTypes.contains(queryType)) {
String message = format("Invalid SQL statement type in the batch. Expected {} but got {}.", allowedTypes, queryType);
@@ -640,10 +632,10 @@ public class SqlQueryProcessor implements QueryProcessor {
throw new SqlException(UNSUPPORTED_DDL_OPERATION_ERR, "DDL doesn't support transactions.");
}
- if (parseResult.dynamicParamsCount() != params.length) {
+ if (parsedResult.dynamicParamsCount() != params.length) {
String message = format(
"Unexpected number of query parameters. Provided {} but there is only {} dynamic parameter(s).",
- params.length, parseResult.dynamicParamsCount()
+ params.length, parsedResult.dynamicParamsCount()
);
throw new SqlException(QUERY_INVALID_ERR, message);
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 7a42727412..907ef4c9a2 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
@@ -18,8 +18,8 @@
package org.apache.ignite.internal.sql.engine.prepare;
import java.util.concurrent.CompletableFuture;
-import org.apache.calcite.sql.SqlNode;
import org.apache.ignite.internal.sql.engine.exec.LifecycleAware;
+import org.apache.ignite.internal.sql.engine.sql.ParsedResult;
import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
/**
@@ -29,5 +29,5 @@ public interface PrepareService extends LifecycleAware {
/**
* Prepare query plan.
*/
- CompletableFuture<QueryPlan> prepareAsync(SqlNode sqlNode, BaseQueryContext ctx);
+ CompletableFuture<QueryPlan> prepareAsync(ParsedResult parsedResult, BaseQueryContext ctx);
}
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 23a42b3a46..b3992f0076 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
@@ -21,7 +21,6 @@ import static org.apache.ignite.internal.sql.engine.prepare.CacheKey.EMPTY_CLASS
import static org.apache.ignite.internal.sql.engine.prepare.PlannerHelper.optimize;
import static org.apache.ignite.internal.sql.engine.trait.TraitUtils.distributionPresent;
import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_VALIDATION_ERR;
-import static org.apache.ignite.lang.ErrorGroups.Sql.UNSUPPORTED_SQL_OPERATION_KIND_ERR;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.ArrayList;
@@ -46,12 +45,11 @@ import org.apache.ignite.internal.logger.IgniteLogger;
import org.apache.ignite.internal.logger.Loggers;
import org.apache.ignite.internal.sql.api.ColumnMetadataImpl;
import org.apache.ignite.internal.sql.api.ResultSetMetadataImpl;
-import org.apache.ignite.internal.sql.engine.SqlQueryType;
import org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverter;
import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
import org.apache.ignite.internal.sql.engine.schema.SchemaUpdateListener;
+import org.apache.ignite.internal.sql.engine.sql.ParsedResult;
import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
-import org.apache.ignite.internal.sql.engine.util.Commons;
import org.apache.ignite.internal.sql.engine.util.TypeUtils;
import org.apache.ignite.internal.storage.DataStorageManager;
import org.apache.ignite.internal.thread.NamedThreadFactory;
@@ -143,34 +141,27 @@ public class PrepareServiceImpl implements PrepareService, SchemaUpdateListener
/** {@inheritDoc} */
@Override
- public CompletableFuture<QueryPlan> prepareAsync(SqlNode sqlNode, BaseQueryContext ctx) {
+ public CompletableFuture<QueryPlan> prepareAsync(ParsedResult parsedResult, BaseQueryContext ctx) {
try {
- assert single(sqlNode);
-
- SqlQueryType queryType = Commons.getQueryType(sqlNode);
- assert queryType != null : "No query type for query: " + sqlNode;
-
PlanningContext planningContext = PlanningContext.builder()
.parentContext(ctx)
.build();
- switch (queryType) {
+ switch (parsedResult.queryType()) {
case QUERY:
- return prepareQuery(sqlNode, planningContext);
+ return prepareQuery(parsedResult, planningContext);
case DDL:
- return prepareDdl(sqlNode, planningContext);
+ return prepareDdl(parsedResult, planningContext);
case DML:
- return prepareDml(sqlNode, planningContext);
+ return prepareDml(parsedResult, planningContext);
case EXPLAIN:
- return prepareExplain(sqlNode, planningContext);
+ return prepareExplain(parsedResult, planningContext);
default:
- throw new IgniteInternalException(UNSUPPORTED_SQL_OPERATION_KIND_ERR, "Unsupported operation ["
- + "sqlNodeKind=" + sqlNode.getKind() + "; "
- + "querySql=\"" + planningContext.query() + "\"]");
+ throw new AssertionError("Unexpected queryType=" + parsedResult.queryType());
}
} catch (CalciteContextException e) {
throw new IgniteInternalException(QUERY_VALIDATION_ERR, "Failed to validate query. " + e.getMessage(), e);
@@ -183,19 +174,25 @@ public class PrepareServiceImpl implements PrepareService, SchemaUpdateListener
cache.clear();
}
- private CompletableFuture<QueryPlan> prepareDdl(SqlNode sqlNode, PlanningContext ctx) {
+ private CompletableFuture<QueryPlan> prepareDdl(ParsedResult parsedResult, PlanningContext ctx) {
+ SqlNode sqlNode = parsedResult.parsedTree();
+
assert sqlNode instanceof SqlDdl : sqlNode == null ? "null" : sqlNode.getClass().getName();
return CompletableFuture.completedFuture(new DdlPlan(ddlConverter.convert((SqlDdl) sqlNode, ctx)));
}
- private CompletableFuture<QueryPlan> prepareExplain(SqlNode explain, PlanningContext ctx) {
+ private CompletableFuture<QueryPlan> prepareExplain(ParsedResult parsedResult, PlanningContext ctx) {
return CompletableFuture.supplyAsync(() -> {
IgnitePlanner planner = ctx.planner();
+ SqlNode sqlNode = parsedResult.parsedTree();
+
+ assert single(sqlNode);
+
// Validate
// We extract query subtree inside the validator.
- SqlNode explainNode = planner.validate(explain);
+ SqlNode explainNode = planner.validate(sqlNode);
// Extract validated query.
SqlNode validNode = ((SqlExplain) explainNode).getExplicandum();
@@ -208,16 +205,20 @@ public class PrepareServiceImpl implements PrepareService, SchemaUpdateListener
}, planningPool);
}
- private boolean single(SqlNode sqlNode) {
+ private static boolean single(SqlNode sqlNode) {
return !(sqlNode instanceof SqlNodeList);
}
- private CompletableFuture<QueryPlan> prepareQuery(SqlNode sqlNode, PlanningContext ctx) {
- CacheKey key = createCacheKey(sqlNode, ctx);
+ private CompletableFuture<QueryPlan> prepareQuery(ParsedResult parsedResult, PlanningContext ctx) {
+ CacheKey key = createCacheKey(parsedResult, ctx);
CompletableFuture<QueryPlan> planFut = cache.computeIfAbsent(key, k -> CompletableFuture.supplyAsync(() -> {
IgnitePlanner planner = ctx.planner();
+ SqlNode sqlNode = parsedResult.parsedTree();
+
+ assert single(sqlNode);
+
// Validate
ValidationResult validated = planner.validateAndGetTypeMetadata(sqlNode);
@@ -236,12 +237,16 @@ public class PrepareServiceImpl implements PrepareService, SchemaUpdateListener
return planFut.thenApply(QueryPlan::copy);
}
- private CompletableFuture<QueryPlan> prepareDml(SqlNode sqlNode, PlanningContext ctx) {
- var key = createCacheKey(sqlNode, ctx);
+ private CompletableFuture<QueryPlan> prepareDml(ParsedResult parsedResult, PlanningContext ctx) {
+ var key = createCacheKey(parsedResult, ctx);
CompletableFuture<QueryPlan> planFut = cache.computeIfAbsent(key, k -> CompletableFuture.supplyAsync(() -> {
IgnitePlanner planner = ctx.planner();
+ SqlNode sqlNode = parsedResult.parsedTree();
+
+ assert single(sqlNode);
+
// Validate
SqlNode validatedNode = planner.validate(sqlNode);
@@ -259,14 +264,14 @@ public class PrepareServiceImpl implements PrepareService, SchemaUpdateListener
return planFut.thenApply(QueryPlan::copy);
}
- private static CacheKey createCacheKey(SqlNode sqlNode, PlanningContext ctx) {
+ private static CacheKey createCacheKey(ParsedResult parsedResult, PlanningContext ctx) {
boolean distributed = distributionPresent(ctx.config().getTraitDefs());
Class[] paramTypes = ctx.parameters().length == 0
? EMPTY_CLASS_ARRAY :
Arrays.stream(ctx.parameters()).map(p -> (p != null) ? p.getClass() : Void.class).toArray(Class[]::new);
- return new CacheKey(ctx.schemaName(), sqlNode.toString(), distributed, paramTypes);
+ return new CacheKey(ctx.schemaName(), parsedResult.normalizedQuery(), distributed, paramTypes);
}
private ResultSetMetadata resultSetMetadata(
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParsedResult.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParsedResult.java
new file mode 100644
index 0000000000..27b3a79192
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParsedResult.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.sql;
+
+import org.apache.calcite.sql.SqlNode;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
+
+/**
+ * Result of the parse.
+ *
+ * @see ParserService
+ */
+public interface ParsedResult {
+ /** Returns the type of the query. */
+ SqlQueryType queryType();
+
+ /** Returns the original query string sent to the parser service, based on which this result was created. */
+ String originalQuery();
+
+ /**
+ * Returns the query string in a normal form.
+ *
+ * <p>That is, with keywords converted to upper case, and all non-quoted identifiers converted to upper case as well
+ * and wrapped with double quotes.
+ */
+ String normalizedQuery();
+
+ /** Returns the count of the dynamic params (specified by question marks in the query text) used in the query. */
+ int dynamicParamsCount();
+
+ /** Returns the syntax tree of the query according to the grammar rules. */
+ SqlNode parsedTree();
+}
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/sql/ParserService.java
similarity index 60%
copy from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
copy to modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParserService.java
index 7a42727412..d358ebf161 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/sql/ParserService.java
@@ -15,19 +15,20 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.sql.engine.prepare;
-
-import java.util.concurrent.CompletableFuture;
-import org.apache.calcite.sql.SqlNode;
-import org.apache.ignite.internal.sql.engine.exec.LifecycleAware;
-import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
+package org.apache.ignite.internal.sql.engine.sql;
/**
- * Preparation service that accepts an AST of the query and returns a prepared query plan.
+ * A service whose sole purpose is to take a query string and convert it into a syntax tree according to the rules of grammar.
+ *
+ * @see ParsedResult
*/
-public interface PrepareService extends LifecycleAware {
+@SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
+public interface ParserService {
/**
- * Prepare query plan.
+ * Takes a query string and convert it into a syntax tree according to the rules of grammar.
+ *
+ * @param query A query to convert.
+ * @return Result of the parsing.
*/
- CompletableFuture<QueryPlan> prepareAsync(SqlNode sqlNode, BaseQueryContext ctx);
+ ParsedResult parse(String query);
}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParserServiceImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParserServiceImpl.java
new file mode 100644
index 0000000000..bf9a895430
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/ParserServiceImpl.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.sql;
+
+import java.util.function.Supplier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
+import org.apache.ignite.internal.sql.engine.util.Cache;
+import org.apache.ignite.internal.sql.engine.util.CacheFactory;
+import org.apache.ignite.internal.sql.engine.util.Commons;
+
+/**
+ * An implementation of {@link ParserService} that, apart of parsing, introduces cache of parsed results.
+ */
+public class ParserServiceImpl implements ParserService {
+
+ private final Cache<String, ParsedResult> queryToParsedResultCache;
+
+ /**
+ * Constructs the object.
+ *
+ * @param cacheFactory A factory to create cache for parsed results.
+ */
+ public ParserServiceImpl(int cacheSize, CacheFactory cacheFactory) {
+ this.queryToParsedResultCache = cacheFactory.create(cacheSize);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ParsedResult parse(String query) {
+ ParsedResult cachedResult = queryToParsedResultCache.get(query);
+
+ if (cachedResult != null) {
+ return cachedResult;
+ }
+
+ StatementParseResult parsedStatement = IgniteSqlParser.parse(query, StatementParseResult.MODE);
+
+ SqlNode parsedTree = parsedStatement.statement();
+
+ SqlQueryType queryType = Commons.getQueryType(parsedTree);
+
+ assert queryType != null : parsedTree.toString();
+
+ ParsedResult result = new ParsedResultImpl(
+ queryType,
+ query,
+ parsedTree.toString(),
+ parsedStatement.dynamicParamsCount(),
+ () -> IgniteSqlParser.parse(query, StatementParseResult.MODE).statement()
+ );
+
+ if (shouldBeCached(queryType)) {
+ queryToParsedResultCache.put(query, result);
+ }
+
+ return result;
+ }
+
+ static class ParsedResultImpl implements ParsedResult {
+ private final SqlQueryType queryType;
+ private final String originalQuery;
+ private final String normalizedQuery;
+ private final int dynamicParamCount;
+ private final Supplier<SqlNode> parsedTreeSupplier;
+
+ private ParsedResultImpl(
+ SqlQueryType queryType,
+ String originalQuery,
+ String normalizedQuery,
+ int dynamicParamCount,
+ Supplier<SqlNode> parsedTreeSupplier
+ ) {
+ this.queryType = queryType;
+ this.originalQuery = originalQuery;
+ this.normalizedQuery = normalizedQuery;
+ this.dynamicParamCount = dynamicParamCount;
+ this.parsedTreeSupplier = parsedTreeSupplier;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SqlQueryType queryType() {
+ return queryType;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String originalQuery() {
+ return originalQuery;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String normalizedQuery() {
+ return normalizedQuery;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int dynamicParamsCount() {
+ return dynamicParamCount;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SqlNode parsedTree() {
+ return parsedTreeSupplier.get();
+ }
+ }
+
+ private static boolean shouldBeCached(SqlQueryType queryType) {
+ return queryType == SqlQueryType.QUERY || queryType == SqlQueryType.DML;
+ }
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Cache.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Cache.java
new file mode 100644
index 0000000000..0d31647797
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Cache.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.sql.engine.util;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A mapping from keys to values.
+ *
+ * <p>Implementations of this interface are expected to be thread-safe, and can be safely accessed by
+ * multiple concurrent threads.
+ *
+ * @param <K> Type of the key object.
+ * @param <V> Type of the value object.
+ */
+public interface Cache<K, V> {
+ /**
+ * Returns value associated with given key, or null if there no mapping exists.
+ *
+ * @param key A key to look up value for.
+ * @return A value.
+ */
+ @Nullable V get(K key);
+
+ /**
+ * Associates the {@code value} with the {@code key} in this cache. If the cache previously
+ * contained a value associated with the {@code key}, the old value is replaced by the new
+ * {@code value}.
+ *
+ * @param key A key with which the specified value is to be associated.
+ * @param value A value to be associated with the specified key.
+ */
+ void put(K key, V value);
+
+ /** Clears the given cache. That is, remove all keys and associated values. */
+ void clear();
+}
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/util/CacheFactory.java
similarity index 60%
copy from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareService.java
copy to modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/CacheFactory.java
index 7a42727412..710c877f11 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/util/CacheFactory.java
@@ -15,19 +15,19 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.sql.engine.prepare;
-
-import java.util.concurrent.CompletableFuture;
-import org.apache.calcite.sql.SqlNode;
-import org.apache.ignite.internal.sql.engine.exec.LifecycleAware;
-import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
+package org.apache.ignite.internal.sql.engine.util;
/**
- * Preparation service that accepts an AST of the query and returns a prepared query plan.
+ * Factory that creates a cache.
*/
-public interface PrepareService extends LifecycleAware {
+public interface CacheFactory {
/**
- * Prepare query plan.
+ * Creates a cache with a desired size.
+ *
+ * @param size Desired size of the cache.
+ * @return An instance of the cache.
+ * @param <K> Type of the key object.
+ * @param <V> Type of the value object.
*/
- CompletableFuture<QueryPlan> prepareAsync(SqlNode sqlNode, BaseQueryContext ctx);
+ <K, V> Cache<K, V> create(int size);
}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/CaffeineCacheFactory.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/CaffeineCacheFactory.java
new file mode 100644
index 0000000000..093dca8a9d
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/CaffeineCacheFactory.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.util;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import java.util.concurrent.ConcurrentMap;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Factory that creates caches backed by {@link Caffeine} cache.
+ */
+public class CaffeineCacheFactory implements CacheFactory {
+ public static CacheFactory INSTANCE = new CaffeineCacheFactory();
+
+ private CaffeineCacheFactory() {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <K, V> Cache<K, V> create(int size) {
+ return new ConcurrentMapToCacheAdapter<>(
+ Caffeine.newBuilder()
+ .maximumSize(size)
+ .<K, V>build()
+ .asMap()
+ );
+ }
+
+ private static class ConcurrentMapToCacheAdapter<K, V> implements Cache<K, V> {
+ private final ConcurrentMap<K, V> map;
+
+ private ConcurrentMapToCacheAdapter(ConcurrentMap<K, V> map) {
+ this.map = map;
+ }
+
+ @Override
+ public @Nullable V get(K key) {
+ return map.get(key);
+ }
+
+ @Override
+ public void put(K key, V value) {
+ map.put(key, value);
+ }
+
+ @Override
+ public void clear() {
+ map.clear();
+ }
+ }
+}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchPrepareBenchmark.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchPrepareBenchmark.java
index f988ad024a..424f21ba7a 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchPrepareBenchmark.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/benchmarks/TpchPrepareBenchmark.java
@@ -18,12 +18,12 @@
package org.apache.ignite.internal.sql.engine.benchmarks;
import java.util.concurrent.TimeUnit;
-import org.apache.calcite.sql.SqlNode;
import org.apache.ignite.internal.sql.engine.framework.TestBuilders;
import org.apache.ignite.internal.sql.engine.framework.TestCluster;
import org.apache.ignite.internal.sql.engine.framework.TestNode;
-import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
-import org.apache.ignite.internal.sql.engine.sql.StatementParseResult;
+import org.apache.ignite.internal.sql.engine.sql.ParsedResult;
+import org.apache.ignite.internal.sql.engine.sql.ParserServiceImpl;
+import org.apache.ignite.internal.sql.engine.util.EmptyCacheFactory;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -65,7 +65,7 @@ public class TpchPrepareBenchmark {
private TestNode gatewayNode;
- private SqlNode queryAst;
+ private ParsedResult parsedResult;
/** Starts the cluster and prepares the plan of the query. */
@Setup
@@ -79,8 +79,7 @@ public class TpchPrepareBenchmark {
gatewayNode = testCluster.node("N1");
String query = TpchQueries.getQuery(queryId);
- StatementParseResult parseResult = IgniteSqlParser.parse(query, StatementParseResult.MODE);
- queryAst = parseResult.statement();
+ parsedResult = new ParserServiceImpl(0, EmptyCacheFactory.INSTANCE).parse(query);
}
/** Stops the cluster. */
@@ -96,7 +95,7 @@ public class TpchPrepareBenchmark {
*/
@Benchmark
public void prepareQuery(Blackhole bh) {
- bh.consume(gatewayNode.prepare(queryAst));
+ bh.consume(gatewayNode.prepare(parsedResult));
}
/**
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 e95472779c..353008c8cc 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
@@ -92,11 +92,13 @@ import org.apache.ignite.internal.sql.engine.schema.DefaultValueStrategy;
import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManager;
import org.apache.ignite.internal.sql.engine.schema.TableDescriptorImpl;
-import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
-import org.apache.ignite.internal.sql.engine.sql.StatementParseResult;
+import org.apache.ignite.internal.sql.engine.sql.ParsedResult;
+import org.apache.ignite.internal.sql.engine.sql.ParserService;
+import org.apache.ignite.internal.sql.engine.sql.ParserServiceImpl;
import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
+import org.apache.ignite.internal.sql.engine.util.EmptyCacheFactory;
import org.apache.ignite.internal.sql.engine.util.HashFunctionFactory;
import org.apache.ignite.internal.sql.engine.util.HashFunctionFactoryImpl;
import org.apache.ignite.internal.testframework.IgniteTestUtils.RunnableX;
@@ -142,6 +144,7 @@ public class ExecutionServiceImplTest {
private TestCluster testCluster;
private List<ExecutionServiceImpl<?>> executionServices;
private PrepareService prepareService;
+ private ParserService parserService;
private RuntimeException mappingException;
@BeforeEach
@@ -149,6 +152,7 @@ public class ExecutionServiceImplTest {
testCluster = new TestCluster();
executionServices = nodeNames.stream().map(this::create).collect(Collectors.toList());
prepareService = new PrepareServiceImpl("test", 0, null);
+ parserService = new ParserServiceImpl(0, EmptyCacheFactory.INSTANCE);
prepareService.start();
}
@@ -537,11 +541,11 @@ public class ExecutionServiceImplTest {
}
private QueryPlan prepare(String query, BaseQueryContext ctx) {
- StatementParseResult parseResult = IgniteSqlParser.parse(query, StatementParseResult.MODE);
+ ParsedResult parsedResult = parserService.parse(query);
- assertEquals(ctx.parameters().length, parseResult.dynamicParamsCount(), "Invalid number of dynamic parameters");
+ assertEquals(ctx.parameters().length, parsedResult.dynamicParamsCount(), "Invalid number of dynamic parameters");
- return await(prepareService.prepareAsync(parseResult.statement(), ctx));
+ return await(prepareService.prepareAsync(parsedResult, ctx));
}
static class TestCluster {
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 8840ff3699..0ab72ec015 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
@@ -19,9 +19,6 @@ package org.apache.ignite.internal.sql.engine.framework;
import static org.apache.ignite.internal.sql.engine.util.Commons.FRAMEWORK_CONFIG;
import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
@@ -30,8 +27,6 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.calcite.schema.SchemaPlus;
-import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.tools.Frameworks;
import org.apache.ignite.internal.sql.engine.AsyncCursor;
import org.apache.ignite.internal.sql.engine.QueryCancel;
@@ -64,9 +59,11 @@ import org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverte
import org.apache.ignite.internal.sql.engine.rel.IgniteIndexScan;
import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManager;
-import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser;
-import org.apache.ignite.internal.sql.engine.sql.StatementParseResult;
+import org.apache.ignite.internal.sql.engine.sql.ParsedResult;
+import org.apache.ignite.internal.sql.engine.sql.ParserService;
+import org.apache.ignite.internal.sql.engine.sql.ParserServiceImpl;
import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
+import org.apache.ignite.internal.sql.engine.util.EmptyCacheFactory;
import org.apache.ignite.internal.sql.engine.util.HashFunctionFactoryImpl;
import org.apache.ignite.internal.util.IgniteSpinBusyLock;
import org.apache.ignite.internal.util.IgniteUtils;
@@ -84,6 +81,7 @@ public class TestNode implements LifecycleAware {
private final SchemaPlus schema;
private final PrepareService prepareService;
private final ExecutionService executionService;
+ private final ParserService parserService;
private final List<LifecycleAware> services = new ArrayList<>();
@@ -153,6 +151,8 @@ public class TestNode implements LifecycleAware {
}
}
));
+
+ parserService = new ParserServiceImpl(0, EmptyCacheFactory.INSTANCE);
}
/** {@inheritDoc} */
@@ -196,25 +196,23 @@ public class TestNode implements LifecycleAware {
* @return A plan to execute.
*/
public QueryPlan prepare(String query) {
- StatementParseResult parseResult = IgniteSqlParser.parse(query, StatementParseResult.MODE);
+ ParsedResult parsedResult = parserService.parse(query);
BaseQueryContext ctx = createContext();
- assertEquals(ctx.parameters().length, parseResult.dynamicParamsCount(), "Invalid number of dynamic parameters");
+ assertEquals(ctx.parameters().length, parsedResult.dynamicParamsCount(), "Invalid number of dynamic parameters");
- return await(prepareService.prepareAsync(parseResult.statement(), ctx));
+ return await(prepareService.prepareAsync(parsedResult, ctx));
}
/**
* Prepares (validates, and optimizes) the given query AST
* and returns the plan to execute.
*
- * @param queryAst Parsed ASD of a query to prepare.
+ * @param parsedResult Parsed AST of a query to prepare.
* @return A plan to execute.
*/
- public QueryPlan prepare(SqlNode queryAst) {
- assertThat(queryAst, not(instanceOf(SqlNodeList.class)));
-
- return await(prepareService.prepareAsync(queryAst, createContext()));
+ public QueryPlan prepare(ParsedResult parsedResult) {
+ return await(prepareService.prepareAsync(parsedResult, createContext()));
}
private BaseQueryContext createContext() {
@@ -233,5 +231,4 @@ public class TestNode implements LifecycleAware {
return service;
}
-
}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/ParserServiceImplTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/ParserServiceImplTest.java
new file mode 100644
index 0000000000..083a383ede
--- /dev/null
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/ParserServiceImplTest.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.sql;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.apache.calcite.sql.SqlNode;
+import org.apache.ignite.internal.sql.engine.SqlQueryType;
+import org.apache.ignite.internal.sql.engine.util.Cache;
+import org.apache.ignite.internal.sql.engine.util.CacheFactory;
+import org.apache.ignite.internal.sql.engine.util.CaffeineCacheFactory;
+import org.apache.ignite.internal.sql.engine.util.EmptyCacheFactory;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+/**
+ * Tests to verify {@link ParserServiceImpl}.
+ */
+public class ParserServiceImplTest {
+ enum Statement {
+ QUERY("SELECT * FROM my_table", SqlQueryType.QUERY, true),
+ DML("INSERT INTO my_table VALUES (1, 1)", SqlQueryType.DML, true),
+ DDL("CREATE TABLE my_table (id INT PRIMARY KEY, avl INT)", SqlQueryType.DDL, false),
+ EXPLAIN_QUERY("EXPLAIN PLAN FOR SELECT * FROM my_table", SqlQueryType.EXPLAIN, false),
+ EXPLAIN_DML("EXPLAIN PLAN FOR INSERT INTO my_table VALUES (1, 1)", SqlQueryType.EXPLAIN, false);
+
+ private final String text;
+ private final SqlQueryType type;
+ private final boolean cacheable;
+
+ Statement(String text, SqlQueryType type, boolean cacheable) {
+ this.text = text;
+ this.type = type;
+ this.cacheable = cacheable;
+ }
+ }
+
+ @ParameterizedTest
+ @EnumSource(Statement.class)
+ void serviceAlwaysReturnsResultFromCacheIfPresent(Statement statement) {
+ ParsedResult expected = new DummyParsedResults();
+
+ ParsedResult actual = new ParserServiceImpl(0, new SameObjectCacheFactory(expected)).parse(statement.text);
+
+ assertSame(actual, expected);
+ }
+
+ @ParameterizedTest
+ @EnumSource(Statement.class)
+ void serviceCachesOnlyCertainStatements(Statement statement) {
+ ParserServiceImpl service = new ParserServiceImpl(
+ Statement.values().length, CaffeineCacheFactory.INSTANCE
+ );
+
+ ParsedResult firstResult = service.parse(statement.text);
+ ParsedResult secondResult = service.parse(statement.text);
+
+ if (statement.cacheable) {
+ assertSame(firstResult, secondResult);
+ } else {
+ assertNotSame(firstResult, secondResult);
+ }
+ }
+
+ @ParameterizedTest
+ @EnumSource(Statement.class)
+ void serviceReturnsResultOfExpectedType(Statement statement) {
+ ParserServiceImpl service = new ParserServiceImpl(0, EmptyCacheFactory.INSTANCE);
+
+ ParsedResult result = service.parse(statement.text);
+
+ assertThat(result.queryType(), is(statement.type));
+ }
+
+ @ParameterizedTest
+ @EnumSource(Statement.class)
+ void resultReturnedByServiceCreateNewInstanceOfTree(Statement statement) {
+ ParserServiceImpl service = new ParserServiceImpl(0, EmptyCacheFactory.INSTANCE);
+
+ ParsedResult result = service.parse(statement.text);
+
+ SqlNode firstCall = result.parsedTree();
+ SqlNode secondCall = result.parsedTree();
+
+ assertNotSame(firstCall, secondCall);
+ assertThat(firstCall.toString(), is(secondCall.toString()));
+ }
+
+ /**
+ * Parsed result that throws {@link AssertionError} on every method call.
+ *
+ * <p>Used in cases where you need to verify referential equality.
+ */
+ private static class DummyParsedResults implements ParsedResult {
+
+ @Override
+ public SqlQueryType queryType() {
+ throw new AssertionError();
+ }
+
+ @Override
+ public String originalQuery() {
+ throw new AssertionError();
+ }
+
+ @Override
+ public String normalizedQuery() {
+ throw new AssertionError();
+ }
+
+ @Override
+ public int dynamicParamsCount() {
+ throw new AssertionError();
+ }
+
+ @Override
+ public SqlNode parsedTree() {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * A factory that creates a cache that always return value passed to the constructor of factory.
+ */
+ private static class SameObjectCacheFactory implements CacheFactory {
+ private final Object object;
+
+ private SameObjectCacheFactory(Object object) {
+ this.object = object;
+ }
+
+ @Override
+ public <K, V> Cache<K, V> create(int size) {
+ return new Cache<>() {
+ @Override
+ public @Nullable V get(K key) {
+ return (V) object;
+ }
+
+ @Override
+ public void put(K key, V value) {
+ // NO-OP
+ }
+
+ @Override
+ public void clear() {
+ // NO-OP
+ }
+ };
+ }
+ }
+}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/EmptyCacheFactory.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/EmptyCacheFactory.java
new file mode 100644
index 0000000000..39cc61352e
--- /dev/null
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/EmptyCacheFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.util;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A factory creating cache that keeps no objects.
+ *
+ * <p>That is, any instance of cache returned by this factory always returns {@code null}
+ * from {@link Cache#get(Object)} method, and do nothing when {@link Cache#put(Object, Object)}
+ * is invoked.
+ */
+public class EmptyCacheFactory implements CacheFactory {
+ public static CacheFactory INSTANCE = new EmptyCacheFactory();
+
+ private EmptyCacheFactory() {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <K, V> Cache<K, V> create(int size) {
+ return new EmptyCache<>();
+ }
+
+ private static class EmptyCache<K, V> implements Cache<K, V> {
+ @Override
+ public @Nullable V get(K key) {
+ return null;
+ }
+
+ @Override
+ public void put(K key, V value) {
+ // NO-OP
+ }
+
+ @Override
+ public void clear() {
+ // NO-OP
+ }
+ }
+}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/StatementChecker.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/StatementChecker.java
index 47cf0eccc8..fb066e329f 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/StatementChecker.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/StatementChecker.java
@@ -272,7 +272,9 @@ public class StatementChecker {
// Capture current stacktrace to show error location.
AssertionError exception = new AssertionError("Statement check failed");
- return shouldFail(name, exception, new TypeSafeMatcher<>() {
+ // please do not replace generic in TypeSafeMatcher with diamond.
+ // Sometimes IDEA goes crazy and start throwing error on compilation V
+ return shouldFail(name, exception, new TypeSafeMatcher<Throwable>() {
@Override
protected boolean matchesSafely(Throwable t) {
return t.getMessage().contains(errorMessage);