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.
*/