From 36e09619ab4203b25030849458af9d4a6baacc45 Mon Sep 17 00:00:00 2001 From: Svilen Mihaylov Date: Wed, 6 Aug 2025 16:51:41 -0400 Subject: [PATCH 1/2] Auto-update --- .../xpack/esql/AutoUpdateUtils.java | 83 +++++++++++++++++++ .../xpack/esql/analysis/AnalyzerTests.java | 27 +++--- 2 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/AutoUpdateUtils.java diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/AutoUpdateUtils.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/AutoUpdateUtils.java new file mode 100644 index 0000000000000..024fb7f328867 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/AutoUpdateUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql; + +import org.apache.logging.log4j.util.StringBuilders; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +public class AutoUpdateUtils { + // TODO: CLI option. + public static boolean isAutoUpdating = false; + + private static void handleAutoUpdate(String expected, String actual, String fileName, int lineNumber) throws IOException { + var expLineCount = expected.chars().filter(ch -> ch == '\n').count() + 1; + + var outFilePath = Files.createTempFile("autoupdate-", ""); + var writer = new BufferedWriter(new FileWriter(outFilePath.toString())); + var reader = new BufferedReader(new FileReader(fileName)); + for (int li = 0;; li++) { + var line = reader.readLine(); + if (line == null) { + break; + } + if (li < lineNumber || li >= lineNumber + expLineCount) { + writer.write(line + "\n"); + } else if (li == lineNumber) { + var actualLines = actual.split("\n"); + for (int actualLineId = 0; actualLineId < actualLines.length; actualLineId++) { + var sb = new StringBuilder(actualLines[actualLineId]); + StringBuilders.escapeJson(sb, 0); + writer.write(sb.toString()); + if (actualLineId + 1 < actualLines.length) { + writer.write("\n"); + } + } + writer.write(line.stripLeading() + "\n"); + } + } + reader.close(); + writer.close(); + Files.move(outFilePath, Paths.get(fileName), REPLACE_EXISTING); + } + + public static void assertStr(String expected, String actual) { + if (expected.equals(actual)) { + return; + } + assert AutoUpdateUtils.isAutoUpdating; + + final var stacktrace = Thread.currentThread().getStackTrace(); + assert stacktrace.length > 2; + final var callerFrame = stacktrace[2]; + + try { + final String cn = callerFrame.getClassName().replace('.', '/') + ".class"; + final var uri = Class.forName(callerFrame.getClassName()).getClassLoader().getResource(cn); + assert uri != null; + + // Navigate from .class to .java source file. + final var fileName = uri.toString() + .replace(".class", ".java") + .replace("file:", "") + .replace("/build/classes/java/test/", "/src/test/java/"); + + handleAutoUpdate(expected, actual, fileName, callerFrame.getLineNumber()); + } catch (Exception e) { + throw new RuntimeException("Encountered an error while auto-updating: " + e.getMessage()); + } + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index e94fff4c682f1..815b0352e43dc 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -18,6 +18,7 @@ import org.elasticsearch.index.analysis.IndexAnalyzers; import org.elasticsearch.logging.LogManager; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.esql.AutoUpdateUtils; import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.LoadMapping; import org.elasticsearch.xpack.esql.VerificationException; @@ -47,7 +48,6 @@ import org.elasticsearch.xpack.esql.expression.function.aggregate.Max; import org.elasticsearch.xpack.esql.expression.function.aggregate.Min; import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; -import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchOperator; import org.elasticsearch.xpack.esql.expression.function.fulltext.MultiMatch; import org.elasticsearch.xpack.esql.expression.function.fulltext.QueryString; import org.elasticsearch.xpack.esql.expression.function.grouping.Bucket; @@ -2878,19 +2878,22 @@ public void testInvalidNamedParamsForIdentifierPatterns() { public void testFromEnrichAndMatchColonUsage() { LogicalPlan plan = analyze(""" from *:test - | EVAL x = to_string(languages) - | ENRICH _any:languages ON x + | EVAL y = to_string(languages) + | ENRICH _any:languages ON y | WHERE first_name: "Anna" """, "mapping-default.json"); - var limit = as(plan, Limit.class); - var filter = as(limit.child(), Filter.class); - var match = as(filter.condition(), MatchOperator.class); - var enrich = as(filter.child(), Enrich.class); - assertEquals(enrich.mode(), Enrich.Mode.ANY); - assertEquals(enrich.policy().getMatchField(), "language_code"); - var eval = as(enrich.child(), Eval.class); - var esRelation = as(eval.child(), EsRelation.class); - assertEquals(esRelation.indexPattern(), "test"); + + AutoUpdateUtils.isAutoUpdating = true; + AutoUpdateUtils.assertStr( + """ + Limit[1000[INTEGER],false] + \\_Filter[:(first_name{f}#8,Anna[KEYWORD])] + \\_Enrich[ANY,languages[KEYWORD],y{r}#4,{\"match\":{\"indices\":[],\"match_field\":\"language_code\",\"enrich_fields\":[\"language_nam + e\"]}},{=languages_idx},[language_name{r}#32]] + \\_Eval[[TOSTRING(languages{f}#14) AS y#4]] + \\_EsRelation[test][avg_worked_seconds{f}#23, birth_date{f}#11, emp_no{..]""", + plan.toString() + ); } public void testFunctionNamedParamsAsFunctionArgument() { From 6f739669803c1e71cfc2fba7a1d931901b2a5e63 Mon Sep 17 00:00:00 2001 From: Svilen Mihaylov Date: Wed, 6 Aug 2025 16:56:52 -0400 Subject: [PATCH 2/2] Add todo --- .../test/java/org/elasticsearch/xpack/esql/AutoUpdateUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/AutoUpdateUtils.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/AutoUpdateUtils.java index 024fb7f328867..c138ddf5718c2 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/AutoUpdateUtils.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/AutoUpdateUtils.java @@ -23,6 +23,7 @@ public class AutoUpdateUtils { // TODO: CLI option. public static boolean isAutoUpdating = false; + // TODO: handle more than one update at a time. private static void handleAutoUpdate(String expected, String actual, String fileName, int lineNumber) throws IOException { var expLineCount = expected.chars().filter(ch -> ch == '\n').count() + 1;