diff --git a/modules/calcite/pom.xml b/modules/calcite/pom.xml
index e39877b84f237..1be401039596a 100644
--- a/modules/calcite/pom.xml
+++ b/modules/calcite/pom.xml
@@ -81,6 +81,13 @@
test
+
+ com.thoughtworks.xstream
+ xstream
+ ${xstream.version}
+ test
+
+
org.apache.calcite
calcite-core
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
index ca6fbd6f390a8..2fafad476dcf7 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
@@ -834,8 +834,8 @@ public QueryRegistry queryRegistry() {
return qryReg;
}
- /** */
- public CalciteQueryEngineConfiguration config() {
+ /** {@inheritDoc} */
+ @Override public CalciteQueryEngineConfiguration config() {
return cfg;
}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CalciteOnlyNodeIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CalciteOnlyNodeIntegrationTest.java
new file mode 100644
index 0000000000000..c04349e5433ac
--- /dev/null
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CalciteOnlyNodeIntegrationTest.java
@@ -0,0 +1,362 @@
+/*
+ * 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.processors.query.calcite.integration;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.cache.query.SqlQuery;
+import org.apache.ignite.cache.query.TextQuery;
+import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
+import org.apache.ignite.client.ClientCache;
+import org.apache.ignite.client.ClientException;
+import org.apache.ignite.client.Config;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.compute.ComputeJob;
+import org.apache.ignite.compute.ComputeJobAdapter;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.compute.ComputeTaskSplitAdapter;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.configuration.ClientConnectorConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.ThinClientConfiguration;
+import org.apache.ignite.internal.IgniteComponentType;
+import org.apache.ignite.internal.IgniteFutureTimeoutCheckedException;
+import org.apache.ignite.internal.processors.query.calcite.GridCommonAbstractWrapperTest;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.apache.ignite.testframework.CallbackExecutorLogListener;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.ListeningTestLogger;
+import org.apache.ignite.testframework.junits.multijvm.IgniteProcessProxy;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause;
+
+/**
+ * Checks behaviour of a node that has the Calcite engine on the classpath but not the H2 engine
+ * ({@code ignite-indexing}).
+ * This is the default node layout after the H2 module is moved to {@code libs/optional} (IGNITE-29117).
+ */
+@SuppressWarnings("deprecation")
+public class CalciteOnlyNodeIntegrationTest extends GridCommonAbstractWrapperTest {
+ /** */
+ private static final String JDBC_URL = "jdbc:ignite:thin://127.0.0.1";
+
+ /**
+ * Fragment that must be present in the error message of the query types that are implemented by the H2 engine only
+ * ({@link SqlQuery}, {@link TextQuery}), so that a user knows which module to add.
+ */
+ private static final String H2_ONLY_FEATURE_MSG = "ignite-indexing";
+
+ /** Error message of the H2-only SQL commands ({@code SET STREAMING}, {@code COPY}): Calcite cannot parse them. */
+ private static final String PARSE_ERR_MSG = "Failed to parse query";
+
+ /** {@link NodeTask} operation: is the H2 engine visible to the node. */
+ private static final String OP_H2_IN_CLASSPATH = "h2InClassPath";
+
+ /** {@link NodeTask} operation: {@code SELECT QUERY_ENGINE()} via the cache API. */
+ private static final String OP_QUERY_ENGINE = "queryEngine";
+
+ /** {@link NodeTask} operation: deprecated {@link SqlQuery} via the cache API, returns the matching keys. */
+ private static final String OP_SQL_QUERY = "sqlQuery";
+
+ /** {@link NodeTask} operation: {@link TextQuery} via the cache API, returns the matching keys. */
+ private static final String OP_TEXT_QUERY = "textQuery";
+
+ /** Prefix of a {@link NodeTask} result when the operation has failed; the messages of the cause chain follow. */
+ private static final String ERR_PREFIX = "error: ";
+
+ /** {@inheritDoc} */
+ @BeforeAll
+ @Override protected void beforeTestsStarted() throws Exception {
+ super.beforeTestsStarted();
+
+ startCalciteOnlyNode();
+
+ assertEquals("H2 engine must not be visible to the Calcite-only node", "false", nodeOp(OP_H2_IN_CLASSPATH));
+
+ try (IgniteClient cli = client()) {
+ ClientCache cache = cli.cache(DEFAULT_CACHE_NAME);
+
+ cache.put(1, "v1");
+ cache.put(2, "v2");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @AfterAll
+ @Override protected void afterTestsStopped() throws Exception {
+ IgniteProcessProxy.killAll();
+
+ super.afterTestsStopped();
+ }
+
+ /**
+ * Starts a node in a separate JVM with {@code ignite-indexing}, H2 and Lucene removed from the classpath.
+ * No engine is configured explicitly: the node must pick the only available one.
+ */
+ private void startCalciteOnlyNode() throws Exception {
+ IgniteConfiguration cfg = optimize(getConfiguration("calcite-only"))
+ .setClientConnectorConfiguration(new ClientConnectorConfiguration()
+ .setThinClientConfiguration(new ThinClientConfiguration().setMaxActiveComputeTasksPerConnection(1)))
+ .setCacheConfiguration(
+ new CacheConfiguration(DEFAULT_CACHE_NAME).setIndexedTypes(Integer.class, String.class));
+
+ CountDownLatch started = new CountDownLatch(1);
+
+ ListeningTestLogger lsnrLog = new ListeningTestLogger(log);
+
+ lsnrLog.registerListener(new CallbackExecutorLogListener(".*Topology snapshot \\[ver=1,.*", started::countDown));
+
+ new IgniteProcessProxy(cfg, lsnrLog, null, false) {
+ @Override protected Collection filteredJvmArgs() throws Exception {
+ Collection args = super.filteredJvmArgs();
+
+ args.add("-cp");
+ args.add(Stream.of(System.getProperty("java.class.path"), System.getProperty("surefire.test.class.path"))
+ .filter(Objects::nonNull)
+ .flatMap(s -> Arrays.stream(s.split(File.pathSeparator)))
+ .filter(e -> !isH2Entry(e))
+ .collect(Collectors.joining(File.pathSeparator)));
+
+ return args;
+ }
+ };
+
+ assertTrue("Calcite-only node has not started", started.await(getTestTimeout(), TimeUnit.MILLISECONDS));
+ }
+
+ /** Baseline: {@link SqlFieldsQuery}, DDL and DML work on a Calcite-only node, Calcite is the default engine. */
+ @Test
+ public void testSqlFieldsQuery() throws Exception {
+ assertEquals(CalciteQueryEngineConfiguration.ENGINE_NAME, nodeOp(OP_QUERY_ENGINE));
+
+ try (Connection conn = DriverManager.getConnection(JDBC_URL);
+ Statement stmt = conn.createStatement()) {
+ stmt.executeUpdate("CREATE TABLE t(id INT PRIMARY KEY, val VARCHAR) WITH \"template=replicated\"");
+ stmt.executeUpdate("INSERT INTO t VALUES (1, 'a')");
+
+ try (ResultSet rs = stmt.executeQuery("SELECT val FROM t WHERE id = 1")) {
+ assertTrue(rs.next());
+ assertEquals("a", rs.getString(1));
+ }
+
+ stmt.executeUpdate("DROP TABLE t");
+ }
+ }
+
+ /** */
+ @Test
+ public void testSqlQueryNotSupported() throws Exception {
+ String msg = nodeOp(OP_SQL_QUERY);
+
+ assertTrue(msg, msg.startsWith(ERR_PREFIX) && msg.contains(H2_ONLY_FEATURE_MSG));
+ }
+
+ /** Deprecated {@link SqlQuery} sent by the Java thin client fails on the server node with the same error. */
+ @Test
+ @SuppressWarnings("ThrowableNotThrown")
+ public void testThinClientSqlQuery() {
+ try (IgniteClient cli = client()) {
+ assertThrows(
+ log,
+ () -> cli.cache(DEFAULT_CACHE_NAME).query(new SqlQuery(String.class, "_val = ?").setArgs("v2")).getAll(),
+ ClientException.class,
+ H2_ONLY_FEATURE_MSG
+ );
+ }
+ }
+
+ /** {@link TextQuery} is implemented by the H2 engine only: the error must name the missing module. */
+ @Test
+ public void testTextQuery() throws Exception {
+ String res = nodeOp(OP_TEXT_QUERY);
+
+ assertTrue(res, res.startsWith(ERR_PREFIX) && res.contains(H2_ONLY_FEATURE_MSG));
+ }
+
+ /**
+ * {@code SET STREAMING} is implemented by the H2 engine only and is rejected by the Calcite parser. A rejected
+ * {@code SET STREAMING ON} must not leave the thin JDBC connection in a state where {@code close()} hangs.
+ */
+ @Test
+ public void testSetStreaming() throws Exception {
+ Connection conn = DriverManager.getConnection(JDBC_URL);
+
+ try (Statement stmt = conn.createStatement()) {
+ assertThrowsAnyCause(
+ log,
+ () -> stmt.executeUpdate("SET STREAMING ON"),
+ SQLException.class,
+ PARSE_ERR_MSG
+ );
+ }
+ finally {
+ try {
+ GridTestUtils.runAsync(conn::close).get(10_000);
+ }
+ catch (IgniteFutureTimeoutCheckedException e) {
+ fail("Connection close hangs after a rejected SET STREAMING ON");
+ }
+ }
+ }
+
+ /** {@code COPY} (bulk load) is implemented by the H2 engine only and is rejected by the Calcite parser. */
+ @Test
+ public void testCopy() throws Exception {
+ try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) {
+ assertThrowsAnyCause(
+ log,
+ () -> stmt.executeUpdate("COPY FROM '/nonexistent.csv' INTO \"test\".String(_key, _val) FORMAT CSV"),
+ SQLException.class,
+ PARSE_ERR_MSG
+ );
+ }
+ }
+
+ /** The only available engine can be selected by name via the JDBC property without an explicit configuration. */
+ @Test
+ public void testJdbcQueryEngineProperty() throws Exception {
+ try (Connection conn = DriverManager.getConnection(JDBC_URL + "?queryEngine=" + CalciteQueryEngineConfiguration.ENGINE_NAME);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT QUERY_ENGINE()")) {
+ assertTrue(rs.next());
+ assertEquals(CalciteQueryEngineConfiguration.ENGINE_NAME, rs.getString(1));
+ }
+ }
+
+ /** The only available engine can be selected by the query hint without an explicit configuration. */
+ @Test
+ public void testQueryEngineHint() throws Exception {
+ String qry = "SELECT /*+ QUERY_ENGINE('" + CalciteQueryEngineConfiguration.ENGINE_NAME + "') */ QUERY_ENGINE()";
+
+ try (Connection conn = DriverManager.getConnection(JDBC_URL);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(qry)) {
+ assertTrue(rs.next());
+ assertEquals(CalciteQueryEngineConfiguration.ENGINE_NAME, rs.getString(1));
+ }
+ }
+
+ /** @return {@code True} if the classpath entry belongs to the H2 engine. */
+ private static boolean isH2Entry(String entry) {
+ String name = new File(entry).getName();
+
+ return name.startsWith("ignite-indexing")
+ || name.startsWith("h2-")
+ || name.startsWith("lucene-")
+ || entry.replace('\\', '/').contains("/modules/indexing/target/");
+ }
+
+ /** */
+ private static IgniteClient client() {
+ return Ignition.startClient(new ClientConfiguration().setAddresses(Config.SERVER));
+ }
+
+ /**
+ * Runs a cache API operation inside the Calcite-only node.
+ *
+ * @param op One of the {@code OP_*} constants.
+ * @return Operation result, or {@link #ERR_PREFIX} followed by the error messages if the operation has failed.
+ */
+ private static String nodeOp(String op) throws Exception {
+ try (IgniteClient cli = client()) {
+ return cli.compute().execute(NodeTask.class.getName(), op);
+ }
+ }
+
+ /** Runs a cache API operation inside the Calcite-only node, see {@link #nodeOp(String)}. */
+ public static class NodeTask extends ComputeTaskSplitAdapter {
+ /** {@inheritDoc} */
+ @Override protected Collection extends ComputeJob> split(int gridSize, String op) {
+ return Collections.singleton(new NodeJob(op));
+ }
+
+ /** {@inheritDoc} */
+ @Override public String reduce(List results) {
+ return results.get(0).getData();
+ }
+ }
+
+ /** */
+ private static class NodeJob extends ComputeJobAdapter {
+ /** */
+ @IgniteInstanceResource
+ private Ignite ignite;
+
+ /** */
+ private final String op;
+
+ /** */
+ private NodeJob(String op) {
+ this.op = op;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object execute() {
+ try {
+ IgniteCache cache = ignite.cache(DEFAULT_CACHE_NAME);
+
+ return switch (op) {
+ case OP_H2_IN_CLASSPATH ->
+ String.valueOf(IgniteComponentType.INDEXING.inClassPath());
+
+ case OP_QUERY_ENGINE ->
+ String.valueOf(cache.query(new SqlFieldsQuery("SELECT QUERY_ENGINE()")).getAll().get(0).get(0));
+
+ case OP_SQL_QUERY ->
+ cache.query(new SqlQuery(String.class, "_val = ?").setArgs("v1")).getAll();
+
+ case OP_TEXT_QUERY ->
+ cache.query(new TextQuery(String.class, "v1")).getAll();
+
+ default ->
+ throw new IllegalArgumentException("Unknown operation: " + op);
+ };
+ }
+ catch (Throwable e) {
+ StringBuilder sb = new StringBuilder(ERR_PREFIX);
+
+ for (Throwable t = e; t != null; t = t.getCause())
+ sb.append(t.getMessage()).append(t.getCause() == null ? "" : " <- ");
+
+ return sb.toString();
+ }
+ }
+ }
+}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java
index 833729b5e0b60..6cfa81743f9cb 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java
@@ -32,6 +32,7 @@
import org.apache.ignite.internal.processors.query.calcite.integration.CacheWithInterceptorIntegrationTest;
import org.apache.ignite.internal.processors.query.calcite.integration.CalciteBasicSecondaryIndexIntegrationTest;
import org.apache.ignite.internal.processors.query.calcite.integration.CalciteErrorHandlilngIntegrationTest;
+import org.apache.ignite.internal.processors.query.calcite.integration.CalciteOnlyNodeIntegrationTest;
import org.apache.ignite.internal.processors.query.calcite.integration.CalcitePlanningDumpTest;
import org.apache.ignite.internal.processors.query.calcite.integration.CollectIntegrationTest;
import org.apache.ignite.internal.processors.query.calcite.integration.CorrelatesIntegrationTest;
@@ -182,6 +183,7 @@
TpchScale010Test.class,
TpchScale100Test.class,
UnnestIntegrationTest.class,
+ CalciteOnlyNodeIntegrationTest.class,
CalcitePlanningDumpTest.class,
KeyClassChangeIntegrationTest.class,
QueryEntityValueColumnAliasTest.class,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
index 60a6a4921e628..deae742d32906 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
@@ -472,12 +472,14 @@ void executeNative(String sql, SqlCommand cmd, JdbcThinStatement stmt) throws SQ
+ cliIo.igniteVersion() + ']', INTERNAL_ERROR);
}
- streamState = new StreamState((SqlSetStreamingCommand)cmd, cliIo);
+ StreamState streamState0 = new StreamState(cmd0, cliIo);
sendRequest(new JdbcQueryExecuteRequest(JdbcStatementType.ANY_STATEMENT_TYPE,
schema, 1, 1, autoCommit, stmt.explicitTimeout, sql, null, NONE_TX), stmt, cliIo);
- streamState.start();
+ streamState = streamState0;
+
+ streamState0.start();
}
}
else
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxyImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxyImpl.java
index 2ab4e3d990a7c..ec86be411348a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxyImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxyImpl.java
@@ -891,10 +891,16 @@ private void validate(Query qry) {
throw new CacheException("Indexing is disabled for cache: " + cacheName +
". Use setIndexedTypes or setTypeMetadata methods on CacheConfiguration to enable.");
- if (!ctx.kernalContext().query().moduleEnabled() &&
- (qry instanceof SqlQuery || qry instanceof SqlFieldsQuery || qry instanceof TextQuery))
+ if (!ctx.kernalContext().query().moduleEnabled() && qry instanceof SqlFieldsQuery) {
throw new CacheException("Failed to execute query. Add module 'ignite-indexing' to the classpath " +
- "of all Ignite nodes or configure any query engine.");
+ "of all Ignite nodes or configure any query engine.");
+ }
+
+ if ((qry instanceof SqlQuery || qry instanceof TextQuery) && !ctx.kernalContext().query().indexingEnabled()) {
+ throw new CacheException("Failed to execute query. " + qry.getClass().getSimpleName() + " is supported " +
+ "by the H2 query engine only, add module 'ignite-indexing' to the classpath of all Ignite nodes" +
+ (qry instanceof SqlQuery ? " or use SqlFieldsQuery instead." : "."));
+ }
}
/** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java
index 1ecfdd02f0076..a1478ae43c246 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java
@@ -23,7 +23,6 @@
import java.util.concurrent.atomic.AtomicReference;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteLogger;
-import org.apache.ignite.configuration.QueryEngineConfiguration;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.binary.BinaryReaderEx;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
@@ -36,7 +35,6 @@
import org.apache.ignite.internal.processors.odbc.ClientListenerResponseSender;
import org.apache.ignite.internal.processors.platform.client.tx.ClientTxContext;
import org.apache.ignite.internal.processors.query.IgniteSQLException;
-import org.apache.ignite.internal.processors.query.QueryEngineConfigurationEx;
import org.apache.ignite.internal.util.GridSpinBusyLock;
import org.apache.ignite.internal.util.nio.GridNioSession;
import org.apache.ignite.internal.util.typedef.F;
@@ -217,23 +215,8 @@ public JdbcConnectionContext(GridKernalContext ctx, GridNioSession ses, GridSpin
if (ver.compareTo(VER_2_13_0) >= 0) {
qryEngine = reader.readString();
- if (qryEngine != null) {
- QueryEngineConfiguration[] cfgs = ctx.config().getSqlConfiguration().getQueryEnginesConfiguration();
-
- boolean found = false;
-
- if (cfgs != null) {
- for (int i = 0; i < cfgs.length; i++) {
- if (qryEngine.equalsIgnoreCase(((QueryEngineConfigurationEx)cfgs[i]).engineName())) {
- found = true;
- break;
- }
- }
- }
-
- if (!found)
- throw new IgniteCheckedException("Not found configuration for query engine: " + qryEngine);
- }
+ if (qryEngine != null && !ctx.query().queryEngineConfigured(qryEngine))
+ throw new IgniteCheckedException("Not found configuration for query engine: " + qryEngine);
}
TransactionConcurrency concurrency = null;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java
index 626ccf5d63b58..6b641f7fcdad5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java
@@ -21,7 +21,6 @@
import java.util.Set;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteLogger;
-import org.apache.ignite.configuration.QueryEngineConfiguration;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.binary.BinaryReaderEx;
import org.apache.ignite.internal.processors.odbc.ClientListenerAbstractConnectionContext;
@@ -31,7 +30,6 @@
import org.apache.ignite.internal.processors.odbc.ClientListenerResponse;
import org.apache.ignite.internal.processors.odbc.ClientListenerResponseSender;
import org.apache.ignite.internal.processors.platform.client.tx.ClientTxContext;
-import org.apache.ignite.internal.processors.query.QueryEngineConfigurationEx;
import org.apache.ignite.internal.util.GridSpinBusyLock;
import org.apache.ignite.internal.util.nio.GridNioSession;
@@ -166,23 +164,8 @@ public OdbcConnectionContext(GridKernalContext ctx, GridNioSession ses, GridSpin
if (ver.compareTo(VER_2_13_0) >= 0) {
qryEngine = reader.readString();
- if (qryEngine != null) {
- QueryEngineConfiguration[] cfgs = ctx.config().getSqlConfiguration().getQueryEnginesConfiguration();
-
- boolean found = false;
-
- if (cfgs != null) {
- for (int i = 0; i < cfgs.length; i++) {
- if (qryEngine.equalsIgnoreCase(((QueryEngineConfigurationEx)cfgs[i]).engineName())) {
- found = true;
- break;
- }
- }
- }
-
- if (!found)
- throw new IgniteCheckedException("Not found configuration for query engine: " + qryEngine);
- }
+ if (qryEngine != null && !ctx.query().queryEngineConfigured(qryEngine))
+ throw new IgniteCheckedException("Not found configuration for query engine: " + qryEngine);
}
authenticate(ses, user, passwd);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
index f88deb1c5c424..c1277a37fa1a0 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
@@ -198,6 +198,10 @@ public class GridQueryProcessor extends GridProcessorAdapter {
Pattern.compile("QUERY_ENGINE[\\s]*\\([\\s]*'([a-z0-9]+)'[\\s]*\\)",
CASE_INSENSITIVE);
+ /** Error message for the features that require the H2 query engine when it is not on the classpath. */
+ private static final String INDEXING_DISABLED_MSG = "Failed to execute query because indexing is disabled " +
+ "(consider adding module " + INDEXING.module() + " to classpath or moving it from 'optional' to 'libs' folder).";
+
/** */
private static final ThreadLocal requestTopVer = new ThreadLocal<>();
@@ -562,9 +566,12 @@ private void initQueryEngines() throws IgniteCheckedException {
// If indexing is disabled, try to find any query engine in components.
if (!indexingEnabled()) {
for (GridComponent cmp : ctx.components()) {
- if (cmp instanceof QueryEngine) {
- qryEngines = new QueryEngine[] {(QueryEngine)cmp};
- dfltQryEngine = (QueryEngine)cmp;
+ if (cmp instanceof QueryEngine && ((QueryEngine)cmp).config() != null) {
+ QueryEngine qryEngine = (QueryEngine)cmp;
+
+ qryEngines = new QueryEngine[] {qryEngine};
+ this.qryEnginesCfg = new QueryEngineConfigurationEx[] {qryEngine.config()};
+ dfltQryEngine = qryEngine;
}
}
}
@@ -1019,6 +1026,23 @@ public boolean moduleEnabled() {
return indexingEnabled() || dfltQryEngine != null;
}
+ /**
+ * @param engineName Query engine name.
+ * @return {@code True} if a query engine with the given name can be selected by the {@code QUERY_ENGINE} hint or
+ * by the client connection property.
+ */
+ public boolean queryEngineConfigured(String engineName) {
+ if (qryEnginesCfg == null)
+ return false;
+
+ for (QueryEngineConfigurationEx cfg : qryEnginesCfg) {
+ if (engineName.equalsIgnoreCase(cfg.engineName()))
+ return true;
+ }
+
+ return false;
+ }
+
/**
* @return Indexing.
* @throws IgniteException If module is not enabled.
@@ -2899,7 +2923,7 @@ private QueryTypeDescriptorImpl type(String cacheName, CacheObject val) throws I
*/
private void checkIndexingEnabled() throws IgniteCheckedException {
if (idx == null)
- throw new IgniteCheckedException("Indexing is disabled.");
+ throw new IgniteCheckedException(INDEXING_DISABLED_MSG);
}
/**
@@ -2907,8 +2931,7 @@ private void checkIndexingEnabled() throws IgniteCheckedException {
*/
private void checkxIndexingEnabled() throws IgniteException {
if (idx == null)
- throw new IgniteException("Failed to execute query because indexing is disabled (consider adding module " +
- INDEXING.module() + " to classpath or moving it from 'optional' to 'libs' folder).");
+ throw new IgniteException(INDEXING_DISABLED_MSG);
}
/**
@@ -3301,6 +3324,8 @@ public long streamUpdateQuery(@Nullable final String cacheName, final String sch
String qryInitiatorId) {
assert streamer != null;
+ checkxIndexingEnabled();
+
if (!busyLock.enterBusy())
throw new IllegalStateException("Failed to execute query (grid is stopping).");
@@ -3363,6 +3388,8 @@ public QueryCursor> querySql(
final SqlQuery qry,
boolean keepBinary
) {
+ checkxIndexingEnabled();
+
// Generate.
String type = qry.getType();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryEngine.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryEngine.java
index faf2f78ad6bc2..b21b49fce3285 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryEngine.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryEngine.java
@@ -81,4 +81,9 @@ List>> queryBatched(
String qry,
List