This is an automated email from the ASF dual-hosted git repository.

alexpl 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 f6c387fc7e0 IGNITE-26438 SQL: Add H2 query plan normalization - Fixes 
#12723.
f6c387fc7e0 is described below

commit f6c387fc7e00a09fd47ee58a236fa7c6ee66e9da
Author: chesnokoff <[email protected]>
AuthorDate: Fri Mar 6 14:46:54 2026 +0300

    IGNITE-26438 SQL: Add H2 query plan normalization - Fixes #12723.
    
    Signed-off-by: Aleksey Plekhanov <[email protected]>
---
 .../integration/SqlPlanHistoryIntegrationTest.java | 20 +++++
 .../internal/processors/query/h2/H2QueryInfo.java  | 98 ++++++++++++++++++++--
 2 files changed, 111 insertions(+), 7 deletions(-)

diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SqlPlanHistoryIntegrationTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SqlPlanHistoryIntegrationTest.java
index 0d30c3d8863..e2d0671b869 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SqlPlanHistoryIntegrationTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SqlPlanHistoryIntegrationTest.java
@@ -551,6 +551,26 @@ public class SqlPlanHistoryIntegrationTest extends 
GridCommonAbstractTest {
         checkMetrics(getSqlPlanHistory());
     }
 
+    /**
+     * Ensures H2 auto-generated numeric aliases (e.g. "_1", "_4") are 
normalized so that repeated executions of the same
+     * query produce a single unique plan in SQL plan history.
+     */
+    @Test
+    public void testH2AutoAliasInPlanHistory() throws Exception {
+        
assumeTrue(IndexingQueryEngineConfiguration.ENGINE_NAME.equals(sqlEngine));
+
+        startTestGrid();
+
+        
queryNode().context().query().runningQueryManager().resetPlanHistoryMetrics();
+
+        String qry = "SELECT _key, _val FROM (SELECT _key, _val FROM String) 
WHERE _key = 0";
+
+        for (int i = 0; i < planHistorySize; i++)
+            cacheQuery(new SqlFieldsQuery(qry), "A");
+
+        checkSqlPlanHistory(1);
+    }
+
     /**
      * @param qry Query.
      */
diff --git 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2QueryInfo.java
 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2QueryInfo.java
index 5fb931e5d42..4cd64c7e082 100644
--- 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2QueryInfo.java
+++ 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2QueryInfo.java
@@ -19,6 +19,8 @@ package org.apache.ignite.internal.processors.query.h2;
 
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.UUID;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
@@ -129,12 +131,22 @@ public class H2QueryInfo implements TrackableQuery {
         if (plan == null) {
             String plan0 = stmt.getPlanSQL();
 
-            plan = (plan0 != null) ? planWithoutScanCount(plan0) : "";
+            plan = (plan0 != null) ? normalizePlan(plan0) : "";
         }
 
         return plan;
     }
 
+    /**
+     * @param plan Plan.
+     */
+    private String normalizePlan(String plan) {
+        plan = planWithoutScanCount(plan);
+        plan = planWithoutSystemAliases(plan);
+
+        return plan;
+    }
+
     /** */
     public String schema() {
         return schema;
@@ -242,13 +254,13 @@ public class H2QueryInfo implements TrackableQuery {
      * @return SQL plan without the scanCount suffix.
      */
     public String planWithoutScanCount(String plan) {
-        if (plan == null || !plan.contains("scanCount:"))
+        if (!plan.contains("scanCount:"))
             return plan;
 
         StringBuilder res = new StringBuilder(plan);
 
-        removePattern(res, "\n    /* scanCount:", "*/");
-        removePattern(res, "\n    /++ scanCount:", "++/");
+        removeLineWithPattern(res, "/* scanCount:", "*/");
+        removeLineWithPattern(res, "/++ scanCount:", "++/");
 
         return res.toString();
     }
@@ -258,10 +270,16 @@ public class H2QueryInfo implements TrackableQuery {
      * @param startPattern Start pattern.
      * @param endMarker End marker.
      */
-    private void removePattern(StringBuilder sb, String startPattern, String 
endMarker) {
-        int start = sb.lastIndexOf(startPattern);
+    private void removeLineWithPattern(StringBuilder sb, String startPattern, 
String endMarker) {
+        int start = sb.indexOf(startPattern, 0);
 
         while (start != -1) {
+            while (start > 0 && sb.charAt(start - 1) == ' ')
+                --start;
+
+            if (start > 0 && sb.charAt(start - 1) == '\n')
+                --start;
+
             int end = sb.indexOf(endMarker, start);
 
             if (end == -1)
@@ -269,10 +287,76 @@ public class H2QueryInfo implements TrackableQuery {
 
             sb.delete(start, end + endMarker.length());
 
-            start = sb.lastIndexOf(startPattern);
+            start = sb.indexOf(startPattern, start);
         }
     }
 
+    /**
+     * Normalizes H2 auto-generated numeric aliases (e.g. "_1", "_4") in a 
plan to make plan history stable
+     * across repeated executions of the same logical query.
+     */
+    private String planWithoutSystemAliases(String plan) {
+        if (plan.indexOf('_') < 0)
+            return plan;
+
+        int n = plan.length();
+
+        Map<String, String> aliasMap = new HashMap<>();
+
+        StringBuilder out = new StringBuilder(n);
+
+        for (int l = 0; l < n; ) {
+            char c = plan.charAt(l);
+
+            if (c != '_') {
+                out.append(c);
+                ++l;
+                continue;
+            }
+
+            if (l > 0) {
+                char prev = plan.charAt(l - 1);
+
+                if (Character.isLetterOrDigit(prev) || prev == '_' || prev == 
'"') {
+                    out.append(c);
+                    ++l;
+                    continue;
+                }
+            }
+
+            int r = l + 1;
+
+            if (r >= n || !Character.isDigit(plan.charAt(r))) {
+                out.append(c);
+                ++l;
+                continue;
+            }
+
+            while (r < n && Character.isDigit(plan.charAt(r)))
+                ++r;
+
+            if (r < n) {
+                char next = plan.charAt(r);
+
+                if (next != '.' && !Character.isWhitespace(next)) {
+                    out.append(c);
+                    ++l;
+                    continue;
+                }
+            }
+
+            String token = plan.substring(l, r);
+
+            String repl = aliasMap.computeIfAbsent(token, k -> 
"__IGNITE_H2_ALIAS_" + aliasMap.size());
+
+            out.append(repl);
+
+            l = r;
+        }
+
+        return out.toString();
+    }
+
     /**
      * Query type.
      */

Reply via email to