Skip to content

Commit 13fbfc2

Browse files
authored
ES|QL: Allow nested MapExpressions (#133311)
1 parent aa86793 commit 13fbfc2

File tree

13 files changed

+1143
-1007
lines changed

13 files changed

+1143
-1007
lines changed

x-pack/plugin/esql/src/main/antlr/parser/Expression.g4

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ mapExpression
6363
;
6464

6565
entryExpression
66-
: key=string COLON value=constant
66+
: key=string COLON value=mapValue
67+
;
68+
69+
mapValue
70+
: constant
71+
| mapExpression
6772
;
6873

6974
constant

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/Options.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,26 @@ public static void populateMap(
7878
for (EntryExpression entry : options.entryExpressions()) {
7979
Expression optionExpr = entry.key();
8080
Expression valueExpr = entry.value();
81-
Expression.TypeResolution resolution = isFoldable(optionExpr, source.text(), paramOrdinal).and(
82-
isFoldable(valueExpr, source.text(), paramOrdinal)
83-
);
84-
if (resolution.unresolved()) {
85-
throw new InvalidArgumentException(resolution.message());
81+
82+
Expression.TypeResolution optionNameResolution = isFoldable(optionExpr, source.text(), paramOrdinal);
83+
if (optionNameResolution.unresolved()) {
84+
throw new InvalidArgumentException(optionNameResolution.message());
8685
}
86+
8787
Object optionExprLiteral = ((Literal) optionExpr).value();
88-
Object valueExprLiteral = ((Literal) valueExpr).value();
8988
String optionName = optionExprLiteral instanceof BytesRef br ? br.utf8ToString() : optionExprLiteral.toString();
89+
DataType dataType = allowedOptions.get(optionName);
90+
91+
// valueExpr could be a MapExpression, but for now functions only accept literal values in options
92+
if ((valueExpr instanceof Literal) == false) {
93+
throw new InvalidArgumentException(
94+
format(null, "Invalid option [{}] in [{}], expected a [{}] value", optionName, source.text(), dataType)
95+
);
96+
}
97+
98+
Object valueExprLiteral = ((Literal) valueExpr).value();
9099
String optionValue = valueExprLiteral instanceof BytesRef br ? br.utf8ToString() : valueExprLiteral.toString();
91100
// validate the optionExpr is supported
92-
DataType dataType = allowedOptions.get(optionName);
93101
if (dataType == null) {
94102
throw new InvalidArgumentException(
95103
format(null, "Invalid option [{}] in [{}], expected one of {}", optionName, source.text(), allowedOptions.keySet())

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIp.java

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import org.elasticsearch.common.io.stream.StreamOutput;
1111
import org.elasticsearch.common.lucene.BytesRefs;
1212
import org.elasticsearch.compute.operator.EvalOperator;
13-
import org.elasticsearch.xpack.esql.core.expression.EntryExpression;
1413
import org.elasticsearch.xpack.esql.core.expression.Expression;
1514
import org.elasticsearch.xpack.esql.core.expression.Literal;
1615
import org.elasticsearch.xpack.esql.core.expression.MapExpression;
@@ -22,6 +21,7 @@
2221
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
2322
import org.elasticsearch.xpack.esql.expression.function.MapParam;
2423
import org.elasticsearch.xpack.esql.expression.function.OptionalArgument;
24+
import org.elasticsearch.xpack.esql.expression.function.Options;
2525
import org.elasticsearch.xpack.esql.expression.function.Param;
2626
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
2727

@@ -31,7 +31,6 @@
3131
import java.util.Set;
3232

3333
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
34-
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression;
3534
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isTypeOrUnionType;
3635
import static org.elasticsearch.xpack.esql.core.type.DataType.IP;
3736
import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD;
@@ -173,39 +172,11 @@ protected TypeResolution resolveType() {
173172
sourceText(),
174173
null,
175174
supportedTypesNames(supportedTypes())
176-
);
177-
if (resolution.unresolved()) {
178-
return resolution;
179-
}
180-
if (options == null) {
181-
return resolution;
182-
}
183-
resolution = isMapExpression(options, sourceText(), SECOND);
175+
).and(Options.resolve(options, source(), SECOND, ALLOWED_OPTIONS));
176+
184177
if (resolution.unresolved()) {
185178
return resolution;
186179
}
187-
for (EntryExpression e : ((MapExpression) options).entryExpressions()) {
188-
String key;
189-
if (e.key().dataType() != KEYWORD) {
190-
return new TypeResolution("map keys must be strings");
191-
}
192-
if (e.key() instanceof Literal keyl) {
193-
key = BytesRefs.toString(keyl.value());
194-
} else {
195-
return new TypeResolution("map keys must be literals");
196-
}
197-
DataType expected = ALLOWED_OPTIONS.get(key);
198-
if (expected == null) {
199-
return new TypeResolution("[" + key + "] is not a supported option");
200-
}
201-
202-
if (e.value().dataType() != expected) {
203-
return new TypeResolution("[" + key + "] expects [" + expected + "] but was [" + e.value().dataType() + "]");
204-
}
205-
if (e.value() instanceof Literal == false) {
206-
return new TypeResolution("map values must be literals");
207-
}
208-
}
209180
try {
210181
LeadingZeros.from((MapExpression) options);
211182
} catch (IllegalArgumentException e) {

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

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)