This is an automated email from the ASF dual-hosted git repository.
timoninmaxim pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 66291dd28e3 IGNITE-24514 Fixed node crash after SQL query with WITH
clause (#11874)
66291dd28e3 is described below
commit 66291dd28e36d1e4cf24883f4ce75befbeb33bf6
Author: Sergey Korotkov <[email protected]>
AuthorDate: Tue Feb 18 19:12:48 2025 +0700
IGNITE-24514 Fixed node crash after SQL query with WITH clause (#11874)
---
.../internal/processors/query/h2/H2Connection.java | 18 ++
.../query/DropTableAfterCteSqlQueryTest.java | 222 +++++++++++++++++++++
.../testsuites/IgniteCacheQuerySelfTestSuite6.java | 2 +
3 files changed, 242 insertions(+)
diff --git
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Connection.java
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Connection.java
index 1d02b2a5f0d..121a9f29591 100644
---
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Connection.java
+++
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Connection.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.processors.query.h2;
+import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -28,6 +29,8 @@ import
org.apache.ignite.internal.processors.query.h2.sql.GridSqlQueryParser;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
+import org.h2.engine.Session;
+import org.h2.jdbc.JdbcConnection;
import org.h2.jdbc.JdbcStatement;
import org.jetbrains.annotations.Nullable;
@@ -59,6 +62,21 @@ public class H2Connection implements AutoCloseable {
this.log = log;
initStatementCache();
+
+ // Work around the H2 bug (NPE in Session#removeLocalTempTable)
+ // https://github.com/h2database/h2database/issues/1354
+ //
+ // Make sure session always contains not-null list of temp tables.
+ try {
+ Session sess =
(Session)(conn.unwrap(JdbcConnection.class)).getSession();
+
+ Field field = Session.class.getDeclaredField("localTempTables");
+ field.setAccessible(true);
+ field.set(sess, sess.getDatabase().newStringMap());
+ }
+ catch (SQLException | IllegalAccessException | NoSuchFieldException e)
{
+ throw new IgniteSQLException("Failed to initialize DB connection",
e);
+ }
}
/**
diff --git
a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/DropTableAfterCteSqlQueryTest.java
b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/DropTableAfterCteSqlQueryTest.java
new file mode 100644
index 00000000000..8a172a784a4
--- /dev/null
+++
b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/DropTableAfterCteSqlQueryTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.failure.FailureContext;
+import org.apache.ignite.failure.FailureHandler;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import
org.apache.ignite.internal.processors.cache.index.AbstractIndexingCommonTest;
+import org.apache.ignite.internal.util.typedef.F;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static
org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+import static org.apache.ignite.testframework.GridTestUtils.runAsync;
+
+/**
+ * Checks SQL query with the WITH clause (AKA Common Table Expressions (CTEs))
+ * doesn't break the H2 SQL Engine.
+ */
+@RunWith(Parameterized.class)
+public class DropTableAfterCteSqlQueryTest extends AbstractIndexingCommonTest {
+ /** */
+ private static final String SCHEMA = "PUBLIC";
+
+ /** */
+ private static final String VALID_SQL =
+ "WITH cte AS (SELECT id FROM T2) " +
+ "SELECT * FROM T1 JOIN cte ON cte.id = T1.id";
+
+ /** */
+ private static final String INVALID_SQL =
+ "WITH cte AS (SELECT id FROM T2) " +
+ "SELECT * FROM absent_table_to_emulate_statement_parsing_failure";
+
+ /** Node. */
+ private IgniteEx node;
+
+ /** */
+ final AtomicBoolean failed = new AtomicBoolean(false);
+
+ /** */
+ final CountDownLatch completed = new CountDownLatch(1);
+
+ /** */
+ @Parameterized.Parameter(value = 0)
+ public Boolean validSql;
+
+ /** */
+ @Parameterized.Parameter(value = 1)
+ public String sql;
+
+ /** */
+ @Parameterized.Parameters(name = "VALID_SQL={0}")
+ public static Collection<Object[]> params() {
+ return F.asList(
+ new Object[] {true, VALID_SQL},
+ new Object[] {false, INVALID_SQL});
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String
igniteInstanceName) throws Exception {
+ IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+ cfg.setFailureHandler(new TestFailureHandler());
+
+ return cfg;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ node = startGrid(getConfiguration());
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() throws Exception {
+ stopAllGrids();
+ }
+
+ /**
+ * Check that CTE query doesn't prevent the table drop and doesn't crash
the
+ * ignite node on table recreate if used via the Ignite API.
+ *
+ * @throws Exception If fails.
+ */
+ @Test
+ public void testCteQueryDoesNotCrashNode() throws Exception {
+ node.createCache(new CacheConfiguration<>("T1")
+ .setQueryEntities(List.of(
+ new QueryEntity("java.lang.Integer", "T1")
+ .addQueryField("id", Integer.class.getName(), null)
+ .addQueryField("data", String.class.getName(), null)
+ .setKeyFieldName("id")
+ ))
+ .setSqlSchema(SCHEMA));
+
+ CacheConfiguration<Object, Object> cfg = new CacheConfiguration<>("T2")
+ .setQueryEntities(List.of(
+ new QueryEntity("java.lang.Integer", "T2")
+ .addQueryField("id", Integer.class.getName(), null)
+ .addQueryField("data", String.class.getName(), null)
+ .setIndexes(List.of(
+ new QueryIndex("id", false, "id_idx")))
+ .setKeyFieldName("id")
+ ))
+ .setSqlSchema(SCHEMA);
+
+ IgniteCache<Object, Object> cache = node.createCache(cfg);
+
+ addData();
+
+ SqlFieldsQuery qry = new SqlFieldsQuery(sql);
+
+ if (validSql)
+ assertEquals(1, cache.query(qry).getAll().size());
+ else
+ assertThrowsWithCause(() -> cache.query(qry).getAll(),
IgniteSQLException.class);
+
+ node.destroyCache("T2");
+
+ IgniteInternalFuture<Object> fut = runAsync(() -> {
+ node.createCache(cfg);
+
+ completed.countDown();
+ });
+
+ completed.await();
+
+ fut.cancel();
+
+ assertFalse("Ignite node should not crash", failed.get());
+ }
+
+ /**
+ * Check that CTE query doesn't prevent the table drop and doesn't prevent
+ * the table recreate if used via the SQL API.
+ */
+ @Test
+ public void testCteQueryDoesNotPreventTableRecreate() {
+ sql("CREATE TABLE T1 (id INTEGER PRIMARY KEY, data VARCHAR)");
+
+ sql("CREATE TABLE T2 (id INTEGER PRIMARY KEY, data VARCHAR)");
+ sql("CREATE INDEX IDX ON T2 (id)");
+
+ addData();
+
+ if (validSql)
+ assertEquals(1, sql(sql).size());
+ else
+ assertThrowsWithCause(() -> sql(sql), IgniteSQLException.class);
+
+ sql("DROP TABLE T2");
+
+ sql("CREATE TABLE T2 (id INTEGER PRIMARY KEY, data VARCHAR)");
+ sql("CREATE INDEX IDX ON T2 (id)");
+ }
+
+ /**
+ * Test failure handler.
+ */
+ private class TestFailureHandler implements FailureHandler {
+ /** {@inheritDoc} */
+ @Override public boolean onFailure(Ignite ignite, FailureContext
failureCtx) {
+ failed.set(true);
+
+ completed.countDown();
+
+ return true;
+ }
+ }
+
+ /** */
+ private void addData() {
+ sql("INSERT INTO T1 (id, data) VALUES (1, 'one')");
+ sql("INSERT INTO T1 (id, data) VALUES (2, 'two')");
+
+ sql("INSERT INTO T2 (id, data) VALUES (2, 'two')");
+ sql("INSERT INTO T2 (id, data) VALUES (3, 'three')");
+ }
+
+ /**
+ * @param sql SQL query.
+ * @return Results.
+ */
+ private List<List<?>> sql(String sql) {
+ GridQueryProcessor qryProc = node.context().query();
+
+ SqlFieldsQuery qry = new SqlFieldsQuery(sql).setSchema(SCHEMA);
+
+ try (FieldsQueryCursor<List<?>> cursor = qryProc.querySqlFields(qry,
true)) {
+ return cursor.getAll();
+ }
+ }
+}
diff --git
a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite6.java
b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite6.java
index e7274eeb280..a6cc36bba09 100644
---
a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite6.java
+++
b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite6.java
@@ -41,6 +41,7 @@ import
org.apache.ignite.internal.processors.cache.query.continuous.GridCacheCon
import
org.apache.ignite.internal.processors.cache.query.continuous.GridCacheContinuousQueryReplicatedAtomicSelfTest;
import
org.apache.ignite.internal.processors.cache.query.continuous.IgniteCacheContinuousQueryClientTest;
import
org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsQueryTest;
+import
org.apache.ignite.internal.processors.query.DropTableAfterCteSqlQueryTest;
import
org.apache.ignite.internal.processors.query.IgniteInsertNullableDuplicatesSqlTest;
import
org.apache.ignite.internal.processors.query.IgniteSqlSinglePartitionMultiParallelismTest;
import
org.apache.ignite.internal.processors.query.MemLeakOnSqlWithClientReconnectTest;
@@ -82,6 +83,7 @@ import org.junit.runners.Suite;
IgniteSqlSinglePartitionMultiParallelismTest.class,
QueryEntityAliasesTest.class,
CacheContinuousQueryEntriesExpireTest.class,
+ DropTableAfterCteSqlQueryTest.class,
})
public class IgniteCacheQuerySelfTestSuite6 {
}