Skip to content

Commit 1875ece

Browse files
authored
Adjust source with offsets (#137260)
1 parent cd4466e commit 1875ece

File tree

11 files changed

+114
-51
lines changed

11 files changed

+114
-51
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizer.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,10 @@ public LogicalPlanOptimizer(LogicalOptimizerContext optimizerContext) {
118118
public LogicalPlan optimize(LogicalPlan verified) {
119119
var optimized = execute(verified);
120120

121-
// Failures failures = verifier.verify(optimized, verified.output());
122-
// if (failures.hasFailures()) {
123-
// throw new VerificationException(failures);
124-
// }
121+
// Failures failures = verifier.verify(optimized, verified.output());
122+
// if (failures.hasFailures()) {
123+
// throw new VerificationException(failures);
124+
// }
125125
optimized.setOptimized();
126126
return optimized;
127127
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package org.elasticsearch.xpack.esql.parser;
99

1010
import org.antlr.v4.runtime.ParserRuleContext;
11+
import org.antlr.v4.runtime.Token;
1112
import org.antlr.v4.runtime.tree.ParseTree;
1213
import org.apache.lucene.util.BytesRef;
1314
import org.elasticsearch.Build;
@@ -52,6 +53,7 @@
5253
import org.elasticsearch.xpack.esql.expression.predicate.logical.Not;
5354
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
5455
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison;
56+
import org.elasticsearch.xpack.esql.parser.promql.ParsingUtils;
5557
import org.elasticsearch.xpack.esql.plan.EsqlStatement;
5658
import org.elasticsearch.xpack.esql.plan.IndexPattern;
5759
import org.elasticsearch.xpack.esql.plan.QuerySetting;
@@ -1266,46 +1268,33 @@ public PlanFactory visitPromqlCommand(EsqlBaseParser.PromqlCommandContext ctx) {
12661268

12671269
// TODO: Perform type and value validation
12681270
var queryCtx = ctx.promqlQueryPart();
1271+
if (queryCtx == null || queryCtx.isEmpty()) {
1272+
throw new ParsingException(source, "PromQL expression cannot be empty");
1273+
}
12691274

1270-
String promqlQuery = queryCtx == null || queryCtx.isEmpty()
1271-
? StringUtils.EMPTY
1272-
// copy the query verbatim to avoid missing tokens interpreted by the enclosing lexer
1273-
: source(queryCtx.get(0).start, queryCtx.get(queryCtx.size() - 1).stop).text();
1274-
1275-
if (promqlQuery.isEmpty()) {
1275+
Token startToken = queryCtx.getFirst().start;
1276+
Token stopToken = queryCtx.getLast().stop;
1277+
// copy the query verbatim to avoid missing tokens interpreted by the enclosing lexer
1278+
String promqlQuery = source(startToken, stopToken).text();
1279+
if (promqlQuery.isBlank()) {
12761280
throw new ParsingException(source, "PromQL expression cannot be empty");
12771281
}
12781282

1279-
int promqlStartLine = source.source().getLineNumber();
1280-
int promqlStartColumn = queryCtx != null && !queryCtx.isEmpty()
1281-
? queryCtx.get(0).start.getCharPositionInLine()
1282-
: source.source().getColumnNumber();
1283+
int promqlStartLine = startToken.getLine();
1284+
int promqlStartColumn = startToken.getCharPositionInLine();
12831285

12841286
PromqlParser promqlParser = new PromqlParser();
12851287
LogicalPlan promqlPlan;
12861288
try {
12871289
// The existing PromqlParser is used to parse the inner query
1288-
promqlPlan = promqlParser.createStatement(promqlQuery);
1290+
promqlPlan = promqlParser.createStatement(promqlQuery, null, null, promqlStartLine, promqlStartColumn);
12891291
} catch (ParsingException pe) {
1290-
throw getParsingException(pe, promqlStartLine, promqlStartColumn);
1292+
throw ParsingUtils.adjustParsingException(pe, promqlStartLine, promqlStartColumn);
12911293
}
12921294

12931295
return plan -> time != null
12941296
? new PromqlCommand(source, plan, promqlPlan, time)
12951297
: new PromqlCommand(source, plan, promqlPlan, start, end, step);
12961298
}
12971299

1298-
private static ParsingException getParsingException(ParsingException pe, int promqlStartLine, int promqlStartColumn) {
1299-
int adjustedLine = promqlStartLine + (pe.getLineNumber() - 1);
1300-
int adjustedColumn = (pe.getLineNumber() == 1 ? promqlStartColumn + pe.getColumnNumber() : pe.getColumnNumber()) - 1;
1301-
1302-
ParsingException adjusted = new ParsingException(
1303-
pe.getErrorMessage(),
1304-
pe.getCause() instanceof Exception ? (Exception) pe.getCause() : null,
1305-
adjustedLine,
1306-
adjustedColumn
1307-
);
1308-
adjusted.setStackTrace(pe.getStackTrace());
1309-
return adjusted;
1310-
}
13111300
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/PromqlParser.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,26 @@ public class PromqlParser {
4646
* Parses an PromQL expression into execution plan
4747
*/
4848
public LogicalPlan createStatement(String query) {
49-
return createStatement(query, null, null);
49+
return createStatement(query, null, null, 0, 0);
5050
}
5151

52-
public LogicalPlan createStatement(String query, Instant start, Instant end) {
52+
public LogicalPlan createStatement(String query, Instant start, Instant end, int startLine, int startColumn) {
5353
if (log.isDebugEnabled()) {
5454
log.debug("Parsing as expression: {}", query);
5555
}
5656

5757
if (start == null) {
5858
start = Instant.now(UTC);
5959
}
60-
return invokeParser(query, start, end, PromqlBaseParser::singleStatement, PromqlAstBuilder::plan);
60+
return invokeParser(query, start, end, startLine, startColumn, PromqlBaseParser::singleStatement, PromqlAstBuilder::plan);
6161
}
6262

6363
private <T> T invokeParser(
6464
String query,
6565
Instant start,
6666
Instant end,
67+
int startLine,
68+
int startColumn,
6769
Function<PromqlBaseParser, ParserRuleContext> parseFunction,
6870
BiFunction<PromqlAstBuilder, ParserRuleContext, T> visitor
6971
) {
@@ -97,7 +99,7 @@ private <T> T invokeParser(
9799
if (log.isTraceEnabled()) {
98100
log.trace("Parse tree: {}", tree.toStringTree());
99101
}
100-
return visitor.apply(new PromqlAstBuilder(start, end), tree);
102+
return visitor.apply(new PromqlAstBuilder(start, end, startLine, startColumn), tree);
101103
} catch (StackOverflowError e) {
102104
throw new ParsingException(
103105
"PromQL statement is too large, causing stack overflow when generating the parsing tree: [{}]",

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/promql/AbstractBuilder.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@
77

88
package org.elasticsearch.xpack.esql.parser.promql;
99

10+
import org.antlr.v4.runtime.ParserRuleContext;
11+
import org.antlr.v4.runtime.Token;
1012
import org.antlr.v4.runtime.tree.ParseTree;
1113
import org.antlr.v4.runtime.tree.TerminalNode;
1214
import org.elasticsearch.xpack.esql.core.tree.Source;
1315
import org.elasticsearch.xpack.esql.parser.ParserUtils;
1416
import org.elasticsearch.xpack.esql.parser.ParsingException;
1517
import org.elasticsearch.xpack.esql.parser.PromqlBaseParserBaseVisitor;
1618

17-
import static org.elasticsearch.xpack.esql.parser.ParserUtils.source;
18-
1919
class AbstractBuilder extends PromqlBaseParserBaseVisitor<Object> {
20+
private final int startLine, startColumn;
21+
22+
AbstractBuilder(int startLine, int startColumn) {
23+
this.startLine = startLine;
24+
this.startColumn = startColumn;
25+
}
26+
2027
@Override
2128
public Object visit(ParseTree tree) {
2229
return ParserUtils.visit(super::visit, tree);
@@ -25,7 +32,7 @@ public Object visit(ParseTree tree) {
2532
/**
2633
* Extracts the actual unescaped string (literal) value of a terminal node.
2734
*/
28-
static String string(TerminalNode node) {
35+
String string(TerminalNode node) {
2936
return node == null ? null : unquote(source(node));
3037
}
3138

@@ -42,4 +49,21 @@ public Object visitTerminal(TerminalNode node) {
4249
Source source = source(node);
4350
throw new ParsingException(source, "Does not know how to handle {}", source.text());
4451
}
52+
53+
protected Source source(TerminalNode terminalNode) {
54+
return ParsingUtils.adjustSource(ParserUtils.source(terminalNode), startLine, startColumn);
55+
}
56+
57+
protected Source source(ParserRuleContext parserRuleContext) {
58+
return ParsingUtils.adjustSource(ParserUtils.source(parserRuleContext), startLine, startColumn);
59+
}
60+
61+
protected Source source(Token token) {
62+
return ParsingUtils.adjustSource(ParserUtils.source(token), startLine, startColumn);
63+
}
64+
65+
protected Source source(Token start, Token stop) {
66+
return ParsingUtils.adjustSource(ParserUtils.source(start, stop), startLine, startColumn);
67+
}
68+
4569
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/promql/ExpressionBuilder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
import java.util.concurrent.TimeUnit;
5151

5252
import static java.util.Collections.emptyList;
53-
import static org.elasticsearch.xpack.esql.parser.ParserUtils.source;
5453
import static org.elasticsearch.xpack.esql.parser.ParserUtils.typedParsing;
5554
import static org.elasticsearch.xpack.esql.parser.ParserUtils.visitList;
5655
import static org.elasticsearch.xpack.esql.parser.PromqlBaseParser.AND;
@@ -80,10 +79,11 @@ class ExpressionBuilder extends IdentifierBuilder {
8079
protected final Instant start, end;
8180

8281
ExpressionBuilder() {
83-
this(null, null);
82+
this(null, null, 0, 0);
8483
}
8584

86-
ExpressionBuilder(Instant start, Instant end) {
85+
ExpressionBuilder(Instant start, Instant end, int startLine, int startColumn) {
86+
super(startLine, startColumn);
8787
Instant now = null;
8888
if (start == null || end == null) {
8989
now = DateUtils.nowWithMillisResolution().toInstant();

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/promql/IdentifierBuilder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
abstract class IdentifierBuilder extends AbstractBuilder {
1313

14+
IdentifierBuilder(int startLine, int startColumn) {
15+
super(startLine, startColumn);
16+
}
17+
1418
@Override
1519
public String visitIdentifier(IdentifierContext ctx) {
1620
return ctx == null ? null : ctx.getText();

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/promql/ParsingUtils.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import org.elasticsearch.core.TimeValue;
1111
import org.elasticsearch.core.Tuple;
12+
import org.elasticsearch.xpack.esql.core.tree.Location;
1213
import org.elasticsearch.xpack.esql.core.tree.Source;
1314
import org.elasticsearch.xpack.esql.parser.ParsingException;
1415

@@ -224,4 +225,49 @@ private static String fromRadix(Source source, char[] chars, int offset, int cou
224225

225226
return String.valueOf(Character.toChars(code));
226227
}
228+
229+
/**
230+
* Adjusts the location of the source by the line and column offsets.
231+
* @see #adjustLocation(Location, int, int)
232+
*/
233+
public static Source adjustSource(Source source, int startLine, int startColumn) {
234+
return new Source(adjustLocation(source.source(), startLine, startColumn), source.text());
235+
}
236+
237+
/**
238+
* Adjusts the location by the line and column offsets.
239+
* The PromQL query inside the PROMQL command is parsed separately,
240+
* so its line and column numbers need to be adjusted to match their
241+
* position inside the full ES|QL query.
242+
*/
243+
public static Location adjustLocation(Location location, int startLine, int startColumn) {
244+
return new Location(adjustLine(location.getLineNumber(), startLine), adjustColumn(location.getColumnNumber(), startColumn));
245+
}
246+
247+
/**
248+
* Adjusts the line and column numbers of the given {@code ParsingException}
249+
* by the provided offsets.
250+
* The PromQL query inside the PROMQL command is parsed separately,
251+
* so its line and column numbers need to be adjusted to match their
252+
* position inside the full ES|QL query.
253+
*/
254+
public static ParsingException adjustParsingException(ParsingException pe, int promqlStartLine, int promqlStartColumn) {
255+
ParsingException adjusted = new ParsingException(
256+
pe.getErrorMessage(),
257+
pe.getCause() instanceof Exception ? (Exception) pe.getCause() : null,
258+
adjustLine(pe.getLineNumber(), promqlStartLine),
259+
adjustColumn(pe.getColumnNumber(), promqlStartColumn)
260+
);
261+
adjusted.setStackTrace(pe.getStackTrace());
262+
return adjusted;
263+
}
264+
265+
private static int adjustLine(int lineNumber, int startLine) {
266+
return lineNumber + startLine - 1;
267+
}
268+
269+
private static int adjustColumn(int columnNumber, int startColumn) {
270+
// the column offset only applies to the first line of the PROMQL command
271+
return columnNumber == 1 ? columnNumber + startColumn - 1 : columnNumber;
272+
}
227273
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/promql/PromqlAstBuilder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ public class PromqlAstBuilder extends PromqlLogicalPlanBuilder {
2020
private int expressionDepth = 0;
2121

2222
public PromqlAstBuilder() {
23-
this(null, null);
23+
this(null, null, 0, 0);
2424
}
2525

26-
public PromqlAstBuilder(Instant start, Instant end) {
27-
super(start, end);
26+
public PromqlAstBuilder(Instant start, Instant end, int startLine, int startColumn) {
27+
super(start, end, startLine, startColumn);
2828
}
2929

3030
public LogicalPlan plan(ParseTree ctx) {

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/promql/PromqlLogicalPlanBuilder.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import static java.util.Collections.emptyList;
4848
import static org.elasticsearch.xpack.esql.expression.promql.function.FunctionType.ACROSS_SERIES_AGGREGATION;
4949
import static org.elasticsearch.xpack.esql.expression.promql.function.FunctionType.WITHIN_SERIES_AGGREGATION;
50-
import static org.elasticsearch.xpack.esql.parser.ParserUtils.source;
5150
import static org.elasticsearch.xpack.esql.parser.ParserUtils.typedParsing;
5251
import static org.elasticsearch.xpack.esql.parser.ParserUtils.visitList;
5352
import static org.elasticsearch.xpack.esql.parser.PromqlBaseParser.AND;
@@ -70,11 +69,11 @@
7069
public class PromqlLogicalPlanBuilder extends ExpressionBuilder {
7170

7271
PromqlLogicalPlanBuilder() {
73-
this(null, null);
72+
this(null, null, 0, 0);
7473
}
7574

76-
PromqlLogicalPlanBuilder(Instant start, Instant end) {
77-
super(start, end);
75+
PromqlLogicalPlanBuilder(Instant start, Instant end, int startLine, int startColumn) {
76+
super(start, end, startLine, startColumn);
7877
}
7978

8079
protected LogicalPlan plan(ParseTree ctx) {

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ public int hashCode() {
102102
public boolean equals(Object obj) {
103103
if (super.equals(obj)) {
104104

105-
PromqlCommand other = (PromqlCommand) obj;
106-
return Objects.equals(child(), other.child()) && Objects.equals(promqlPlan, other.promqlPlan);
107-
}
105+
PromqlCommand other = (PromqlCommand) obj;
106+
return Objects.equals(child(), other.child()) && Objects.equals(promqlPlan, other.promqlPlan);
107+
}
108108

109109
return false;
110110
}

0 commit comments

Comments
 (0)