Skip to content

Commit e87511c

Browse files
committed
refactor options
1 parent bfb9cc3 commit e87511c

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;
@@ -20,20 +19,15 @@
2019
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware;
2120
import org.elasticsearch.xpack.esql.capabilities.TranslationAware;
2221
import org.elasticsearch.xpack.esql.common.Failures;
23-
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
24-
import org.elasticsearch.xpack.esql.core.expression.EntryExpression;
2522
import org.elasticsearch.xpack.esql.core.expression.Expression;
2623
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
2724
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
28-
import org.elasticsearch.xpack.esql.core.expression.Literal;
29-
import org.elasticsearch.xpack.esql.core.expression.MapExpression;
3025
import org.elasticsearch.xpack.esql.core.expression.Nullability;
3126
import org.elasticsearch.xpack.esql.core.expression.TypeResolutions;
3227
import org.elasticsearch.xpack.esql.core.expression.function.Function;
3328
import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
3429
import org.elasticsearch.xpack.esql.core.tree.Source;
3530
import org.elasticsearch.xpack.esql.core.type.DataType;
36-
import org.elasticsearch.xpack.esql.core.type.DataTypeConverter;
3731
import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField;
3832
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
3933
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction;
@@ -55,17 +49,12 @@
5549
import java.util.ArrayList;
5650
import java.util.List;
5751
import java.util.Locale;
58-
import java.util.Map;
5952
import java.util.Objects;
6053
import java.util.function.BiConsumer;
6154
import java.util.function.Predicate;
6255

63-
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
6456
import static org.elasticsearch.xpack.esql.common.Failure.fail;
6557
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT;
66-
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isFoldable;
67-
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression;
68-
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull;
6958
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable;
7059
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
7160

@@ -409,66 +398,6 @@ public ScoreOperator.ExpressionScorer.Factory toScorer(ToScorer toScorer) {
409398
return new LuceneQueryScoreEvaluator.Factory(shardConfigs);
410399
}
411400

412-
public static void populateOptionsMap(
413-
final MapExpression options,
414-
final Map<String, Object> optionsMap,
415-
final TypeResolutions.ParamOrdinal paramOrdinal,
416-
final String sourceText,
417-
final Map<String, DataType> allowedOptions
418-
) throws InvalidArgumentException {
419-
for (EntryExpression entry : options.entryExpressions()) {
420-
Expression optionExpr = entry.key();
421-
Expression valueExpr = entry.value();
422-
TypeResolution resolution = isFoldable(optionExpr, sourceText, paramOrdinal).and(
423-
isFoldable(valueExpr, sourceText, paramOrdinal)
424-
);
425-
if (resolution.unresolved()) {
426-
throw new InvalidArgumentException(resolution.message());
427-
}
428-
Object optionExprLiteral = ((Literal) optionExpr).value();
429-
Object valueExprLiteral = ((Literal) valueExpr).value();
430-
String optionName = optionExprLiteral instanceof BytesRef br ? br.utf8ToString() : optionExprLiteral.toString();
431-
String optionValue = valueExprLiteral instanceof BytesRef br ? br.utf8ToString() : valueExprLiteral.toString();
432-
// validate the optionExpr is supported
433-
DataType dataType = allowedOptions.get(optionName);
434-
if (dataType == null) {
435-
throw new InvalidArgumentException(
436-
format(null, "Invalid option [{}] in [{}], expected one of {}", optionName, sourceText, allowedOptions.keySet())
437-
);
438-
}
439-
try {
440-
optionsMap.put(optionName, DataTypeConverter.convert(optionValue, dataType));
441-
} catch (InvalidArgumentException e) {
442-
throw new InvalidArgumentException(format(null, "Invalid option [{}] in [{}], {}", optionName, sourceText, e.getMessage()));
443-
}
444-
}
445-
}
446-
447-
protected TypeResolution resolveOptions(Expression options, TypeResolutions.ParamOrdinal paramOrdinal) {
448-
if (options != null) {
449-
TypeResolution resolution = isNotNull(options, sourceText(), paramOrdinal);
450-
if (resolution.unresolved()) {
451-
return resolution;
452-
}
453-
// MapExpression does not have a DataType associated with it
454-
resolution = isMapExpression(options, sourceText(), paramOrdinal);
455-
if (resolution.unresolved()) {
456-
return resolution;
457-
}
458-
459-
try {
460-
resolvedOptions();
461-
} catch (InvalidArgumentException e) {
462-
return new TypeResolution(e.getMessage());
463-
}
464-
}
465-
return TypeResolution.TYPE_RESOLVED;
466-
}
467-
468-
protected Map<String, Object> resolvedOptions() throws InvalidArgumentException {
469-
return Map.of();
470-
}
471-
472401
// TODO: this should likely be replaced by calls to FieldAttribute#fieldName; the MultiTypeEsField case looks
473402
// wrong if `fieldAttribute` is a subfield, e.g. `parent.child` - multiTypeEsField#getName will just return `child`.
474403
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.optimizer.rules.physical.local.LucenePushdownPredicates;
@@ -298,7 +299,10 @@ public final void writeTo(StreamOutput out) throws IOException {
298299

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

304308
private TypeResolution resolveField() {
@@ -342,11 +346,6 @@ private TypeResolution checkParamCompatibility() {
342346
return new TypeResolution(formatIncompatibleTypesMessage(fieldType, queryType, sourceText()));
343347
}
344348

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

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

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.optimizer.rules.physical.local.LucenePushdownPredicates;
@@ -187,7 +188,7 @@ public final void writeTo(StreamOutput out) throws IOException {
187188

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

193194
private TypeResolution resolveField() {
@@ -200,18 +201,13 @@ private TypeResolution resolveQuery() {
200201
);
201202
}
202203

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

213209
Map<String, Object> matchPhraseOptions = new HashMap<>();
214-
populateOptionsMap((MapExpression) options(), matchPhraseOptions, SECOND, sourceText(), ALLOWED_OPTIONS);
210+
Options.populateMap((MapExpression) options(), matchPhraseOptions, source(), SECOND, ALLOWED_OPTIONS);
215211
return matchPhraseOptions;
216212
}
217213

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.optimizer.rules.physical.local.LucenePushdownPredicates;
@@ -368,7 +369,7 @@ private Map<String, Object> getOptions() throws InvalidArgumentException {
368369
return options;
369370
}
370371

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

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.optimizer.rules.physical.local.LucenePushdownPredicates;
@@ -321,18 +322,13 @@ private Map<String, Object> queryStringOptions() throws InvalidArgumentException
321322
}
322323

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

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

338334
@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)