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..c138ddf5718c2 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/AutoUpdateUtils.java @@ -0,0 +1,84 @@ +/* + * 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; + + // 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; + + 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() {