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 {
 }

Reply via email to