Skip to content

Commit 0ed51dd

Browse files
committed
refactor options
1 parent 37d48a9 commit 0ed51dd

File tree

8 files changed

+123
-153
lines changed

8 files changed

+123
-153
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.expression.function;
9+
10+
import org.apache.lucene.util.BytesRef;
11+
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
12+
import org.elasticsearch.xpack.esql.core.expression.EntryExpression;
13+
import org.elasticsearch.xpack.esql.core.expression.Expression;
14+
import org.elasticsearch.xpack.esql.core.expression.Literal;
15+
import org.elasticsearch.xpack.esql.core.expression.MapExpression;
16+
import org.elasticsearch.xpack.esql.core.expression.TypeResolutions;
17+
import org.elasticsearch.xpack.esql.core.tree.Source;
18+
import org.elasticsearch.xpack.esql.core.type.DataType;
19+
import org.elasticsearch.xpack.esql.core.type.DataTypeConverter;
20+
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
24+
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
25+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isFoldable;
26+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression;
27+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull;
28+
29+
public class Options {
30+
31+
public static Expression.TypeResolution resolve(
32+
Expression options,
33+
Source source,
34+
TypeResolutions.ParamOrdinal paramOrdinal,
35+
Map<String, DataType> allowedOptions
36+
) {
37+
if (options != null) {
38+
Expression.TypeResolution resolution = isNotNull(options, source.text(), paramOrdinal);
39+
if (resolution.unresolved()) {
40+
return resolution;
41+
}
42+
// MapExpression does not have a DataType associated with it
43+
resolution = isMapExpression(options, source.text(), paramOrdinal);
44+
if (resolution.unresolved()) {
45+
return resolution;
46+
}
47+
48+
try {
49+
Map<String, Object> optionsMap = new HashMap<>();
50+
populateMap((MapExpression) options, optionsMap, source, paramOrdinal, allowedOptions);
51+
} catch (InvalidArgumentException e) {
52+
return new Expression.TypeResolution(e.getMessage());
53+
}
54+
}
55+
return Expression.TypeResolution.TYPE_RESOLVED;
56+
}
57+
58+
public static void populateMap(
59+
final MapExpression options,
60+
final Map<String, Object> optionsMap,
61+
final Source source,
62+
final TypeResolutions.ParamOrdinal paramOrdinal,
63+
final Map<String, DataType> allowedOptions
64+
) throws InvalidArgumentException {
65+
for (EntryExpression entry : options.entryExpressions()) {
66+
Expression optionExpr = entry.key();
67+
Expression valueExpr = entry.value();
68+
Expression.TypeResolution resolution =
69+
isFoldable(optionExpr, source.text(), paramOrdinal).and(isFoldable(valueExpr, source.text(), paramOrdinal));
70+
if (resolution.unresolved()) {
71+
throw new InvalidArgumentException(resolution.message());
72+
}
73+
Object optionExprLiteral = ((Literal) optionExpr).value();
74+
Object valueExprLiteral = ((Literal) valueExpr).value();
75+
String optionName = optionExprLiteral instanceof BytesRef br ? br.utf8ToString() : optionExprLiteral.toString();
76+
String optionValue = valueExprLiteral instanceof BytesRef br ? br.utf8ToString() : valueExprLiteral.toString();
77+
// validate the optionExpr is supported
78+
DataType dataType = allowedOptions.get(optionName);
79+
if (dataType == null) {
80+
throw new InvalidArgumentException(format(
81+
null,
82+
"Invalid option [{}] in [{}], expected one of {}",
83+
optionName,
84+
source.text(),
85+
allowedOptions.keySet()
86+
));
87+
}
88+
try {
89+
optionsMap.put(optionName, DataTypeConverter.convert(optionValue, dataType));
90+
} catch (InvalidArgumentException e) {
91+
throw new InvalidArgumentException(format(
92+
null,
93+
"Invalid option [{}] in [{}], {}",
94+
optionName,
95+
source.text(),
96+
e.getMessage()
97+
));
98+
}
99+
}
100+
}
101+
}

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

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
package org.elasticsearch.xpack.esql.expression.function.fulltext;
99

10-
import org.apache.lucene.util.BytesRef;
1110
import org.elasticsearch.common.lucene.BytesRefs;
1211
import org.elasticsearch.compute.lucene.LuceneQueryEvaluator.ShardConfig;
1312
import org.elasticsearch.compute.lucene.LuceneQueryExpressionEvaluator;
@@ -19,20 +18,15 @@
1918
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware;
2019
import org.elasticsearch.xpack.esql.capabilities.TranslationAware;
2120
import org.elasticsearch.xpack.esql.common.Failures;
22-
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
23-
import org.elasticsearch.xpack.esql.core.expression.EntryExpression;
2421
import org.elasticsearch.xpack.esql.core.expression.Expression;
2522
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
2623
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
27-
import org.elasticsearch.xpack.esql.core.expression.Literal;
28-
import org.elasticsearch.xpack.esql.core.expression.MapExpression;
2924
import org.elasticsearch.xpack.esql.core.expression.Nullability;
3025
import org.elasticsearch.xpack.esql.core.expression.TypeResolutions;
3126
import org.elasticsearch.xpack.esql.core.expression.function.Function;
3227
import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
3328
import org.elasticsearch.xpack.esql.core.tree.Source;
3429
import org.elasticsearch.xpack.esql.core.type.DataType;
35-
import org.elasticsearch.xpack.esql.core.type.DataTypeConverter;
3630
import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField;
3731
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
3832
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction;
@@ -52,17 +46,12 @@
5246

5347
import java.util.List;
5448
import java.util.Locale;
55-
import java.util.Map;
5649
import java.util.Objects;
5750
import java.util.function.BiConsumer;
5851
import java.util.function.Predicate;
5952

60-
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
6153
import static org.elasticsearch.xpack.esql.common.Failure.fail;
6254
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT;
63-
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isFoldable;
64-
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression;
65-
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull;
6655
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable;
6756
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
6857

@@ -376,66 +365,6 @@ public ScoreOperator.ExpressionScorer.Factory toScorer(ToScorer toScorer) {
376365
return new LuceneQueryScoreEvaluator.Factory(shardConfigs);
377366
}
378367

379-
public static void populateOptionsMap(
380-
final MapExpression options,
381-
final Map<String, Object> optionsMap,
382-
final TypeResolutions.ParamOrdinal paramOrdinal,
383-
final String sourceText,
384-
final Map<String, DataType> allowedOptions
385-
) throws InvalidArgumentException {
386-
for (EntryExpression entry : options.entryExpressions()) {
387-
Expression optionExpr = entry.key();
388-
Expression valueExpr = entry.value();
389-
TypeResolution resolution = isFoldable(optionExpr, sourceText, paramOrdinal).and(
390-
isFoldable(valueExpr, sourceText, paramOrdinal)
391-
);
392-
if (resolution.unresolved()) {
393-
throw new InvalidArgumentException(resolution.message());
394-
}
395-
Object optionExprLiteral = ((Literal) optionExpr).value();
396-
Object valueExprLiteral = ((Literal) valueExpr).value();
397-
String optionName = optionExprLiteral instanceof BytesRef br ? br.utf8ToString() : optionExprLiteral.toString();
398-
String optionValue = valueExprLiteral instanceof BytesRef br ? br.utf8ToString() : valueExprLiteral.toString();
399-
// validate the optionExpr is supported
400-
DataType dataType = allowedOptions.get(optionName);
401-
if (dataType == null) {
402-
throw new InvalidArgumentException(
403-
format(null, "Invalid option [{}] in [{}], expected one of {}", optionName, sourceText, allowedOptions.keySet())
404-
);
405-
}
406-
try {
407-
optionsMap.put(optionName, DataTypeConverter.convert(optionValue, dataType));
408-
} catch (InvalidArgumentException e) {
409-
throw new InvalidArgumentException(format(null, "Invalid option [{}] in [{}], {}", optionName, sourceText, e.getMessage()));
410-
}
411-
}
412-
}
413-
414-
protected TypeResolution resolveOptions(Expression options, TypeResolutions.ParamOrdinal paramOrdinal) {
415-
if (options != null) {
416-
TypeResolution resolution = isNotNull(options, sourceText(), paramOrdinal);
417-
if (resolution.unresolved()) {
418-
return resolution;
419-
}
420-
// MapExpression does not have a DataType associated with it
421-
resolution = isMapExpression(options, sourceText(), paramOrdinal);
422-
if (resolution.unresolved()) {
423-
return resolution;
424-
}
425-
426-
try {
427-
resolvedOptions();
428-
} catch (InvalidArgumentException e) {
429-
return new TypeResolution(e.getMessage());
430-
}
431-
}
432-
return TypeResolution.TYPE_RESOLVED;
433-
}
434-
435-
protected Map<String, Object> resolvedOptions() throws InvalidArgumentException {
436-
return Map.of();
437-
}
438-
439368
// TODO: this should likely be replaced by calls to FieldAttribute#fieldName; the MultiTypeEsField case looks
440369
// wrong if `fieldAttribute` is a subfield, e.g. `parent.child` - multiTypeEsField#getName will just return `child`.
441370
public static String getNameFromFieldAttribute(FieldAttribute fieldAttribute) {

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
3434
import org.elasticsearch.xpack.esql.expression.function.MapParam;
3535
import org.elasticsearch.xpack.esql.expression.function.OptionalArgument;
36+
import org.elasticsearch.xpack.esql.expression.function.Options;
3637
import org.elasticsearch.xpack.esql.expression.function.Param;
3738
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
3839
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
@@ -297,7 +298,10 @@ public final void writeTo(StreamOutput out) throws IOException {
297298

298299
@Override
299300
protected TypeResolution resolveParams() {
300-
return resolveField().and(resolveQuery()).and(resolveOptions(options(), THIRD)).and(checkParamCompatibility());
301+
return resolveField()
302+
.and(resolveQuery())
303+
.and(Options.resolve(options(), source(), THIRD, ALLOWED_OPTIONS))
304+
.and(checkParamCompatibility());
301305
}
302306

303307
private TypeResolution resolveField() {
@@ -341,11 +345,6 @@ private TypeResolution checkParamCompatibility() {
341345
return new TypeResolution(formatIncompatibleTypesMessage(fieldType, queryType, sourceText()));
342346
}
343347

344-
@Override
345-
protected Map<String, Object> resolvedOptions() {
346-
return matchQueryOptions();
347-
}
348-
349348
private Map<String, Object> matchQueryOptions() throws InvalidArgumentException {
350349
if (options() == null) {
351350
return Map.of(LENIENT_FIELD.getPreferredName(), true);
@@ -355,7 +354,8 @@ private Map<String, Object> matchQueryOptions() throws InvalidArgumentException
355354
// Match is lenient by default to avoid failing on incompatible types
356355
matchOptions.put(LENIENT_FIELD.getPreferredName(), true);
357356

358-
populateOptionsMap((MapExpression) options(), matchOptions, SECOND, sourceText(), ALLOWED_OPTIONS);
357+
358+
Options.populateMap((MapExpression) options(), matchOptions, source(), SECOND, ALLOWED_OPTIONS);
359359
return matchOptions;
360360
}
361361

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
3131
import org.elasticsearch.xpack.esql.expression.function.MapParam;
3232
import org.elasticsearch.xpack.esql.expression.function.OptionalArgument;
33+
import org.elasticsearch.xpack.esql.expression.function.Options;
3334
import org.elasticsearch.xpack.esql.expression.function.Param;
3435
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
3536
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
@@ -188,7 +189,7 @@ public final void writeTo(StreamOutput out) throws IOException {
188189

189190
@Override
190191
protected TypeResolution resolveParams() {
191-
return resolveField().and(resolveQuery()).and(resolveOptions(options(), THIRD));
192+
return resolveField().and(resolveQuery()).and(Options.resolve(options(), source(), THIRD, ALLOWED_OPTIONS));
192193
}
193194

194195
private TypeResolution resolveField() {
@@ -201,18 +202,13 @@ private TypeResolution resolveQuery() {
201202
);
202203
}
203204

204-
@Override
205-
protected Map<String, Object> resolvedOptions() throws InvalidArgumentException {
206-
return matchPhraseQueryOptions();
207-
}
208-
209205
private Map<String, Object> matchPhraseQueryOptions() throws InvalidArgumentException {
210206
if (options() == null) {
211207
return Map.of();
212208
}
213209

214210
Map<String, Object> matchPhraseOptions = new HashMap<>();
215-
populateOptionsMap((MapExpression) options(), matchPhraseOptions, SECOND, sourceText(), ALLOWED_OPTIONS);
211+
Options.populateMap((MapExpression) options(), matchPhraseOptions, source(), SECOND, ALLOWED_OPTIONS);
216212
return matchPhraseOptions;
217213
}
218214

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
3030
import org.elasticsearch.xpack.esql.expression.function.MapParam;
3131
import org.elasticsearch.xpack.esql.expression.function.OptionalArgument;
32+
import org.elasticsearch.xpack.esql.expression.function.Options;
3233
import org.elasticsearch.xpack.esql.expression.function.Param;
3334
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
3435
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
@@ -367,7 +368,7 @@ private Map<String, Object> getOptions() throws InvalidArgumentException {
367368
return options;
368369
}
369370

370-
Match.populateOptionsMap((MapExpression) options(), options, THIRD, sourceText(), OPTIONS);
371+
Options.populateMap((MapExpression) options(), options, source(), THIRD, OPTIONS);
371372
return options;
372373
}
373374

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
2727
import org.elasticsearch.xpack.esql.expression.function.MapParam;
2828
import org.elasticsearch.xpack.esql.expression.function.OptionalArgument;
29+
import org.elasticsearch.xpack.esql.expression.function.Options;
2930
import org.elasticsearch.xpack.esql.expression.function.Param;
3031
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
3132
import org.elasticsearch.xpack.esql.planner.TranslatorHandler;
@@ -320,18 +321,13 @@ private Map<String, Object> queryStringOptions() throws InvalidArgumentException
320321
}
321322

322323
Map<String, Object> matchOptions = new HashMap<>();
323-
populateOptionsMap((MapExpression) options(), matchOptions, SECOND, sourceText(), ALLOWED_OPTIONS);
324+
Options.populateMap((MapExpression) options(), matchOptions, source(), SECOND, ALLOWED_OPTIONS);
324325
return matchOptions;
325326
}
326327

327-
@Override
328-
protected Map<String, Object> resolvedOptions() {
329-
return queryStringOptions();
330-
}
331-
332328
@Override
333329
protected TypeResolution resolveParams() {
334-
return resolveQuery().and(resolveOptions(options(), SECOND));
330+
return resolveQuery().and(Options.resolve(options(), source(), SECOND, ALLOWED_OPTIONS));
335331
}
336332

337333
@Override

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

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import org.elasticsearch.xpack.esql.expression.function.FunctionType;
2929
import org.elasticsearch.xpack.esql.expression.function.MapParam;
3030
import org.elasticsearch.xpack.esql.expression.function.OptionalArgument;
31+
import org.elasticsearch.xpack.esql.expression.function.Options;
3132
import org.elasticsearch.xpack.esql.expression.function.Param;
32-
import org.elasticsearch.xpack.esql.expression.function.fulltext.FullTextFunction;
3333
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
3434
import org.elasticsearch.xpack.ml.MachineLearning;
3535

@@ -44,8 +44,6 @@
4444
import static org.elasticsearch.xpack.esql.SupportsObservabilityTier.ObservabilityTier.COMPLETE;
4545
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT;
4646
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
47-
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression;
48-
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull;
4947
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
5048
import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER;
5149
import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD;
@@ -165,34 +163,13 @@ public Nullability nullable() {
165163

166164
@Override
167165
protected TypeResolution resolveType() {
168-
return isString(field(), sourceText(), DEFAULT).and(resolveOptions());
169-
}
170-
171-
private TypeResolution resolveOptions() {
172-
if (options != null) {
173-
TypeResolution resolution = isNotNull(options, sourceText(), SECOND);
174-
if (resolution.unresolved()) {
175-
return resolution;
176-
}
177-
// MapExpression does not have a DataType associated with it
178-
resolution = isMapExpression(options, sourceText(), SECOND);
179-
if (resolution.unresolved()) {
180-
return resolution;
181-
}
182-
try {
183-
categorizeDef();
184-
} catch (InvalidArgumentException e) {
185-
return new TypeResolution(e.getMessage());
186-
}
187-
}
188-
return TypeResolution.TYPE_RESOLVED;
166+
return isString(field(), sourceText(), DEFAULT).and(Options.resolve(options, source(), SECOND, ALLOWED_OPTIONS));
189167
}
190168

191169
public CategorizeDef categorizeDef() {
192170
Map<String, Object> optionsMap = new HashMap<>();
193171
if (options != null) {
194-
// TODO: refactor
195-
FullTextFunction.populateOptionsMap((MapExpression) options, optionsMap, SECOND, source().text(), ALLOWED_OPTIONS);
172+
Options.populateMap((MapExpression) options, optionsMap, source(), SECOND, ALLOWED_OPTIONS);
196173
}
197174
Integer similarityThreshold = (Integer) optionsMap.get("similarity_threshold");
198175
if (similarityThreshold != null) {

0 commit comments

Comments
 (0)