From 7762de8bc3bdacd63268bed2fac8f2b054c66823 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 27 Oct 2025 16:33:10 -0700 Subject: [PATCH 1/2] Improving type resolution for Clamp --- .../esql/_snippets/functions/types/clamp.md | 12 + .../_snippets/functions/types/clamp_max.md | 12 + .../_snippets/functions/types/clamp_min.md | 12 + .../kibana/definition/functions/clamp.json | 288 ++++++++++++++++++ .../definition/functions/clamp_max.json | 216 +++++++++++++ .../definition/functions/clamp_min.json | 216 +++++++++++++ .../expression/function/scalar/Clamp.java | 52 +++- .../function/scalar/conditional/ClampMax.java | 51 ++-- .../function/scalar/conditional/ClampMin.java | 45 ++- .../scalar/conditional/ClampMaxTests.java | 47 +-- .../scalar/conditional/ClampMinTests.java | 45 +-- .../scalar/conditional/ClampTests.java | 55 ++-- 12 files changed, 950 insertions(+), 101 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/clamp.md b/docs/reference/query-languages/esql/_snippets/functions/types/clamp.md index 5b76a0978788b..61efe6bef2533 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/clamp.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/clamp.md @@ -7,10 +7,22 @@ | boolean | boolean | boolean | boolean | | date | date | date | date | | double | double | double | double | +| double | integer | integer | double | +| double | long | long | double | +| double | unsigned_long | unsigned_long | double | +| integer | double | double | double | | integer | integer | integer | integer | +| integer | long | long | long | +| integer | unsigned_long | unsigned_long | unsigned_long | | ip | ip | ip | ip | | keyword | keyword | keyword | keyword | +| long | double | double | double | +| long | integer | integer | long | | long | long | long | long | +| long | unsigned_long | unsigned_long | unsigned_long | +| unsigned_long | double | double | double | +| unsigned_long | integer | integer | unsigned_long | +| unsigned_long | long | long | long | | unsigned_long | unsigned_long | unsigned_long | unsigned_long | | version | version | version | version | diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/clamp_max.md b/docs/reference/query-languages/esql/_snippets/functions/types/clamp_max.md index 3a3af0e670f7b..d483784471af7 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/clamp_max.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/clamp_max.md @@ -7,10 +7,22 @@ | boolean | boolean | boolean | | date | date | date | | double | double | double | +| double | integer | double | +| double | long | double | +| double | unsigned_long | double | +| integer | double | double | | integer | integer | integer | +| integer | long | long | +| integer | unsigned_long | unsigned_long | | ip | ip | ip | | keyword | keyword | keyword | +| long | double | double | +| long | integer | long | | long | long | long | +| long | unsigned_long | unsigned_long | +| unsigned_long | double | double | +| unsigned_long | integer | unsigned_long | +| unsigned_long | long | long | | unsigned_long | unsigned_long | unsigned_long | | version | version | version | diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/clamp_min.md b/docs/reference/query-languages/esql/_snippets/functions/types/clamp_min.md index 0100d810fea52..65d9e257421aa 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/clamp_min.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/clamp_min.md @@ -7,10 +7,22 @@ | boolean | boolean | boolean | | date | date | date | | double | double | double | +| double | integer | double | +| double | long | double | +| double | unsigned_long | double | +| integer | double | double | | integer | integer | integer | +| integer | long | long | +| integer | unsigned_long | unsigned_long | | ip | ip | ip | | keyword | keyword | keyword | +| long | double | double | +| long | integer | long | | long | long | long | +| long | unsigned_long | unsigned_long | +| unsigned_long | double | double | +| unsigned_long | integer | unsigned_long | +| unsigned_long | long | long | | unsigned_long | unsigned_long | unsigned_long | | version | version | version | diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/clamp.json b/docs/reference/query-languages/esql/kibana/definition/functions/clamp.json index c5cd62399afae..cf143760dedd1 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/clamp.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/clamp.json @@ -76,6 +76,102 @@ "variadic" : false, "returnType" : "double" }, + { + "params" : [ + { + "name" : "field", + "type" : "double", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "integer", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "integer", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "double", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "long", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "double", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "unsigned_long", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "unsigned_long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "integer", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "double", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "double", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, { "params" : [ { @@ -100,6 +196,54 @@ "variadic" : false, "returnType" : "integer" }, + { + "params" : [ + { + "name" : "field", + "type" : "integer", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "long", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "integer", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "unsigned_long", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "unsigned_long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, { "params" : [ { @@ -156,6 +300,150 @@ "optional" : false, "description" : "Numeric expression. If `null`, the function returns `null`." }, + { + "name" : "min", + "type" : "double", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "double", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "long", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "integer", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "integer", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "long", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "long", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "long", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "unsigned_long", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "unsigned_long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "double", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "double", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, + { + "name" : "min", + "type" : "integer", + "optional" : false, + "description" : "The min value to clamp data into." + }, + { + "name" : "max", + "type" : "integer", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + }, { "name" : "min", "type" : "long", diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/clamp_max.json b/docs/reference/query-languages/esql/kibana/definition/functions/clamp_max.json index e9c8d27baedfb..ba9eb8d7dd388 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/clamp_max.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/clamp_max.json @@ -58,6 +58,78 @@ "variadic" : false, "returnType" : "double" }, + { + "params" : [ + { + "name" : "field", + "type" : "double", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "integer", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "double", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "double", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "unsigned_long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "integer", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "double", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, { "params" : [ { @@ -76,6 +148,42 @@ "variadic" : false, "returnType" : "integer" }, + { + "params" : [ + { + "name" : "field", + "type" : "integer", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "integer", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "unsigned_long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, { "params" : [ { @@ -120,6 +228,114 @@ "optional" : false, "description" : "field to clamp." }, + { + "name" : "max", + "type" : "double", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "long", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "integer", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "long", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "long", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "unsigned_long", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "double", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "max", + "type" : "integer", + "optional" : false, + "description" : "The max value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "field to clamp." + }, { "name" : "max", "type" : "long", diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/clamp_min.json b/docs/reference/query-languages/esql/kibana/definition/functions/clamp_min.json index 5572b05f3a9bb..574111671e580 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/clamp_min.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/clamp_min.json @@ -58,6 +58,78 @@ "variadic" : false, "returnType" : "double" }, + { + "params" : [ + { + "name" : "field", + "type" : "double", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "integer", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "double", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "long", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "double", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "unsigned_long", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "integer", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "double", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, { "params" : [ { @@ -76,6 +148,42 @@ "variadic" : false, "returnType" : "integer" }, + { + "params" : [ + { + "name" : "field", + "type" : "integer", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "long", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "integer", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "unsigned_long", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, { "params" : [ { @@ -120,6 +228,114 @@ "optional" : false, "description" : "field to clamp." }, + { + "name" : "min", + "type" : "double", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "long", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "integer", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "long", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "long", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "long", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "unsigned_long", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "double", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "field to clamp." + }, + { + "name" : "min", + "type" : "integer", + "optional" : false, + "description" : "The min value to clamp data into." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "field to clamp." + }, { "name" : "min", "type" : "long", diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/Clamp.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/Clamp.java index 21c3271163202..803b9a885d871 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/Clamp.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/Clamp.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator; +import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; import org.elasticsearch.xpack.esql.core.tree.NodeInfo; @@ -22,7 +23,9 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.ClampMin; import java.io.IOException; +import java.util.Comparator; import java.util.List; +import java.util.stream.Stream; import static org.elasticsearch.xpack.esql.core.type.DataType.NULL; @@ -33,6 +36,7 @@ public class Clamp extends EsqlScalarFunction implements SurrogateExpression { private final Expression field; private final Expression min; private final Expression max; + private DataType resolvedType; @FunctionInfo( returnType = { "double", "integer", "long", "double", "unsigned_long", "keyword", "ip", "boolean", "date", "version" }, @@ -75,7 +79,9 @@ protected TypeResolution resolveType() { } var field = children().get(0); - var fieldDataType = field.dataType().noText(); + var max = children().get(1); + var min = children().get(2); + var fieldDataType = field.dataType(); TypeResolution resolution = TypeResolutions.isType( field, t -> t.isNumeric() || t == DataType.BOOLEAN || t.isDate() || DataType.isString(t) || t == DataType.IP || t == DataType.VERSION, @@ -89,24 +95,44 @@ protected TypeResolution resolveType() { if (fieldDataType == NULL) { return new TypeResolution("'field' must not be null in clamp()"); } - for (Expression child : List.of(children().get(1), children().get(2))) { - var childRes = TypeResolutions.isType( - child, - t -> t.isNumeric() ? fieldDataType.isNumeric() : t.noText() == fieldDataType, - sourceText(), - child == children().get(1) ? TypeResolutions.ParamOrdinal.SECOND : TypeResolutions.ParamOrdinal.THIRD, - fieldDataType.typeName() - ); - if (childRes.unresolved()) { - return childRes; - } + resolution = TypeResolutions.isType( + max, + t -> t.isNumeric() ? fieldDataType.isNumeric() : t.noText() == fieldDataType.noText(), + sourceText(), + TypeResolutions.ParamOrdinal.SECOND, + fieldDataType.typeName() + ); + if (resolution.unresolved()) { + return resolution; + } + resolution = TypeResolutions.isType( + min, + t -> t.isNumeric() ? fieldDataType.isNumeric() : t.noText() == fieldDataType.noText(), + sourceText(), + TypeResolutions.ParamOrdinal.THIRD, + fieldDataType.typeName() + ); + if (resolution.unresolved()) { + return resolution; + } + if (fieldDataType.isNumeric() == false) { + resolvedType = fieldDataType; + } else { + // When the types are equally wide, prefer rational numbers + resolvedType = Stream.of(fieldDataType, max.dataType(), min.dataType()) + .sorted(Comparator.comparingInt(DataType::estimatedSize).thenComparing(DataType::isRationalNumber)) + .toList() + .getLast(); } return TypeResolution.TYPE_RESOLVED; } @Override public DataType dataType() { - return field.dataType(); + if (resolvedType == null && resolveType().resolved() == false) { + throw new EsqlIllegalArgumentException("Unable to resolve data type for clamp_max"); + } + return resolvedType; } @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMax.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMax.java index 9e2158cf464db..941b827246c64 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMax.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMax.java @@ -38,9 +38,10 @@ */ public class ClampMax extends EsqlScalarFunction { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "ClampMax", ClampMax::new); + private DataType resolvedType; @FunctionInfo( - returnType = { "double", "integer", "long", "double", "unsigned_long", "keyword", "ip", "boolean", "date", "version" }, + returnType = { "double", "integer", "long", "unsigned_long", "double", "keyword", "ip", "boolean", "date", "version" }, description = "Returns clamps the values of all input samples clamped to have an upper limit of max.", examples = @Example(file = "k8s-timeseries-clamp", tag = "clamp-max") ) @@ -48,12 +49,12 @@ public ClampMax( Source source, @Param( name = "field", - type = { "double", "integer", "long", "double", "unsigned_long", "keyword", "ip", "boolean", "date", "version" }, + type = { "double", "integer", "long", "unsigned_long", "double", "keyword", "ip", "boolean", "date", "version" }, description = "field to clamp." ) Expression field, @Param( name = "max", - type = { "double", "integer", "long", "double", "unsigned_long", "keyword", "ip", "boolean", "date", "version" }, + type = { "double", "integer", "long", "unsigned_long", "double", "keyword", "ip", "boolean", "date", "version" }, description = "The max value to clamp data into." ) Expression max ) { @@ -78,7 +79,10 @@ public String getWriteableName() { @Override public DataType dataType() { - return children().getFirst().dataType(); + if (resolvedType == null && resolveType().resolved() == false) { + throw new EsqlIllegalArgumentException("Unable to resolve data type for clamp_max"); + } + return resolvedType; } @Override @@ -113,6 +117,15 @@ protected TypeResolution resolveType() { if (resolution.unresolved()) { return resolution; } + if (fieldDataType.isNumeric() == false) { + resolvedType = fieldDataType; + } else if (fieldDataType.estimatedSize() == max.dataType().estimatedSize()) { + // When the types are equally wide, prefer rational numbers + resolvedType = fieldDataType.isRationalNumber() ? fieldDataType : max.dataType(); + } else { + // Otherwise, prefer the wider type + resolvedType = fieldDataType.estimatedSize() > max.dataType().estimatedSize() ? fieldDataType : max.dataType(); + } return TypeResolution.TYPE_RESOLVED; } @@ -133,20 +146,22 @@ public boolean foldable() { @Override public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { - var outputType = dataType(); - - var max = children().get(1); - var maxF = PlannerUtils.toElementType(outputType) != PlannerUtils.toElementType(max.dataType()) - ? Cast.cast(source(), max.dataType(), outputType, toEvaluator.apply(max)) - : toEvaluator.apply(max); - - return switch (PlannerUtils.toElementType(outputType)) { - case BOOLEAN -> new ClampMaxBooleanEvaluator.Factory(source(), toEvaluator.apply(children().get(0)), maxF); - case DOUBLE -> new ClampMaxDoubleEvaluator.Factory(source(), toEvaluator.apply(children().get(0)), maxF); - case INT -> new ClampMaxIntegerEvaluator.Factory(source(), toEvaluator.apply(children().get(0)), maxF); - case LONG -> new ClampMaxLongEvaluator.Factory(source(), toEvaluator.apply(children().get(0)), maxF); - case BYTES_REF -> new ClampMaxBytesRefEvaluator.Factory(source(), toEvaluator.apply(children().get(0)), maxF); - default -> throw EsqlIllegalArgumentException.illegalDataType(outputType); + var outputType = PlannerUtils.toElementType(dataType()); + + var fieldEval = PlannerUtils.toElementType(children().getFirst().dataType()) != outputType + ? Cast.cast(source(), children().getFirst().dataType(), dataType(), toEvaluator.apply(children().get(0))) + : toEvaluator.apply(children().getFirst()); + var maxEval = PlannerUtils.toElementType(children().get(1).dataType()) != outputType + ? Cast.cast(source(), children().get(1).dataType(), dataType(), toEvaluator.apply(children().get(1))) + : toEvaluator.apply(children().get(1)); + + return switch (outputType) { + case BOOLEAN -> new ClampMaxBooleanEvaluator.Factory(source(), fieldEval, maxEval); + case DOUBLE -> new ClampMaxDoubleEvaluator.Factory(source(), fieldEval, maxEval); + case INT -> new ClampMaxIntegerEvaluator.Factory(source(), fieldEval, maxEval); + case LONG -> new ClampMaxLongEvaluator.Factory(source(), fieldEval, maxEval); + case BYTES_REF -> new ClampMaxBytesRefEvaluator.Factory(source(), fieldEval, maxEval); + default -> throw EsqlIllegalArgumentException.illegalDataType(dataType()); }; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMin.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMin.java index 17fc2ae059e89..3f71b4483b7d0 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMin.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMin.java @@ -38,6 +38,7 @@ */ public class ClampMin extends EsqlScalarFunction { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "ClampMin", ClampMin::new); + private DataType resolvedType; @FunctionInfo( returnType = { "double", "integer", "long", "double", "unsigned_long", "keyword", "ip", "boolean", "date", "version" }, @@ -78,7 +79,10 @@ public String getWriteableName() { @Override public DataType dataType() { - return children().getFirst().dataType(); + if (resolvedType == null && resolveType().resolved() == false) { + throw new EsqlIllegalArgumentException("Unable to resolve data type for clamp_min"); + } + return resolvedType; } @Override @@ -113,6 +117,15 @@ protected TypeResolution resolveType() { if (resolution.unresolved()) { return resolution; } + if (fieldDataType.isNumeric() == false) { + resolvedType = fieldDataType; + } else if (fieldDataType.estimatedSize() == min.dataType().estimatedSize()) { + // When the types are equally wide, prefer rational numbers + resolvedType = fieldDataType.isRationalNumber() ? fieldDataType : min.dataType(); + } else { + // Otherwise, prefer the wider type + resolvedType = fieldDataType.estimatedSize() > min.dataType().estimatedSize() ? fieldDataType : min.dataType(); + } return TypeResolution.TYPE_RESOLVED; } @@ -133,20 +146,22 @@ public boolean foldable() { @Override public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { - var outputType = dataType(); - - var min = children().get(1); - var minF = PlannerUtils.toElementType(outputType) != PlannerUtils.toElementType(min.dataType()) - ? Cast.cast(source(), min.dataType(), outputType, toEvaluator.apply(min)) - : toEvaluator.apply(min); - - return switch (PlannerUtils.toElementType(outputType)) { - case BOOLEAN -> new ClampMinBooleanEvaluator.Factory(source(), toEvaluator.apply(children().get(0)), minF); - case DOUBLE -> new ClampMinDoubleEvaluator.Factory(source(), toEvaluator.apply(children().get(0)), minF); - case INT -> new ClampMinIntegerEvaluator.Factory(source(), toEvaluator.apply(children().get(0)), minF); - case LONG -> new ClampMinLongEvaluator.Factory(source(), toEvaluator.apply(children().get(0)), minF); - case BYTES_REF -> new ClampMinBytesRefEvaluator.Factory(source(), toEvaluator.apply(children().get(0)), minF); - default -> throw EsqlIllegalArgumentException.illegalDataType(outputType); + var outputType = PlannerUtils.toElementType(dataType()); + + var fieldEval = PlannerUtils.toElementType(children().getFirst().dataType()) != outputType + ? Cast.cast(source(), children().getFirst().dataType(), dataType(), toEvaluator.apply(children().get(0))) + : toEvaluator.apply(children().getFirst()); + var minEval = PlannerUtils.toElementType(children().get(1).dataType()) != outputType + ? Cast.cast(source(), children().get(1).dataType(), dataType(), toEvaluator.apply(children().get(1))) + : toEvaluator.apply(children().get(1)); + + return switch (outputType) { + case BOOLEAN -> new ClampMinBooleanEvaluator.Factory(source(), fieldEval, minEval); + case DOUBLE -> new ClampMinDoubleEvaluator.Factory(source(), fieldEval, minEval); + case INT -> new ClampMinIntegerEvaluator.Factory(source(), fieldEval, minEval); + case LONG -> new ClampMinLongEvaluator.Factory(source(), fieldEval, minEval); + case BYTES_REF -> new ClampMinBytesRefEvaluator.Factory(source(), fieldEval, minEval); + default -> throw EsqlIllegalArgumentException.illegalDataType(dataType()); }; } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMaxTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMaxTests.java index c331cc719686f..d146e7405fc2e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMaxTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMaxTests.java @@ -18,12 +18,15 @@ import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; +import org.elasticsearch.xpack.esql.planner.PlannerUtils; import org.hamcrest.Matchers; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; public class ClampMaxTests extends AbstractScalarFunctionTestCase { public ClampMaxTests(@Name("TestCase") Supplier testCaseSupplier) { @@ -67,31 +70,41 @@ public static Iterable parameters() { throw new IllegalArgumentException("Unsupported data type: " + dt); }; // function to make first letter uppercase - Function capitalize = s -> s.substring(0, 1).toUpperCase(Locale.ROOT) + s.substring(1); - for (DataType numericType : DataType.types().stream().filter(DataType::isNumeric).toList()) { - if (numericType == DataType.HALF_FLOAT - || numericType == DataType.SCALED_FLOAT - || numericType == DataType.SHORT - || numericType == DataType.BYTE - // || numericType == DataType.UNSIGNED_LONG // TODO: shouldnt unsigned long be supported? it was giving trouble... - || numericType == DataType.FLOAT) { // TODO: shouldnt float be supported? + Function capitalize = s -> s.substring(0, 1).toUpperCase(Locale.ROOT) + s.substring(1).toLowerCase(Locale.ROOT); + + final Set disallowedTypes = Set.of( + DataType.HALF_FLOAT, + DataType.SCALED_FLOAT, + DataType.SHORT, + DataType.BYTE, + DataType.FLOAT + ); + var allNumericTypes = DataType.types().stream().filter(DataType::isNumeric).toList(); + for (Tuple numericTypes : allNumericTypes.stream() + .flatMap(nt1 -> allNumericTypes.stream().map(nt2 -> Tuple.tuple(nt1, nt2))) + .collect(Collectors.toSet())) { + var nt1 = numericTypes.v1(); + var nt2 = numericTypes.v2(); + if (disallowedTypes.contains(nt1) || disallowedTypes.contains(nt2)) { continue; } + var tmpLargerDt = nt1.estimatedSize() > nt2.estimatedSize() ? nt1 : nt2; + if (nt2.estimatedSize() == nt1.estimatedSize()) { + tmpLargerDt = nt1.isRationalNumber() ? nt1 : nt2; + } + final var largerDt = tmpLargerDt; + final var largerDtName = PlannerUtils.toElementType(largerDt).pascalCaseName(); suppliers.add( new TestCaseSupplier( "(a, b)", - List.of(numericType, numericType), + List.of(nt1, nt2), () -> new TestCaseSupplier.TestCase( List.of( - new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(numericType, 1)), numericType, "a"), - new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(numericType, 2)), numericType, "b") - ), - String.format( - Locale.ROOT, - "ClampMax%sEvaluator[field=Attribute[channel=0], max=Attribute[channel=1]]", - numericType == DataType.UNSIGNED_LONG ? "Long" : capitalize.apply(numericType.esType()) + new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(nt1, 1)), nt1, "a"), + new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(nt2, 2)), nt2, "b") ), - numericType, + Matchers.stringContainsInOrder("ClampMax", largerDtName, "Evaluator[field="), + largerDt, Matchers.allOf( Matchers.notNullValue(), Matchers.not(Matchers.notANumber()), diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMinTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMinTests.java index 888898fbfd2f2..b9143c9e00f85 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMinTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMinTests.java @@ -18,12 +18,15 @@ import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; +import org.elasticsearch.xpack.esql.planner.PlannerUtils; import org.hamcrest.Matchers; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; public class ClampMinTests extends AbstractScalarFunctionTestCase { public ClampMinTests(@Name("TestCase") Supplier testCaseSupplier) { @@ -72,31 +75,39 @@ public static Iterable parameters() { }; // function to make first letter uppercase Function capitalize = s -> s.substring(0, 1).toUpperCase(Locale.ROOT) + s.substring(1); - for (DataType numericType : DataType.types().stream().filter(DataType::isNumeric).toList()) { - if (numericType == DataType.HALF_FLOAT - || numericType == DataType.SCALED_FLOAT - || numericType == DataType.SHORT - || numericType == DataType.BYTE - // || numericType == DataType.UNSIGNED_LONG // TODO: shouldnt unsigned long be supported? it was giving trouble... - || numericType == DataType.FLOAT) { // TODO: shouldnt float be supported? + final Set disallowedTypes = Set.of( + DataType.HALF_FLOAT, + DataType.SCALED_FLOAT, + DataType.SHORT, + DataType.BYTE, + DataType.FLOAT + ); + var allNumericTypes = DataType.types().stream().filter(DataType::isNumeric).toList(); + for (Tuple numericTypes : allNumericTypes.stream() + .flatMap(nt1 -> allNumericTypes.stream().map(nt2 -> Tuple.tuple(nt1, nt2))) + .collect(Collectors.toSet())) { + var nt1 = numericTypes.v1(); + var nt2 = numericTypes.v2(); + if (disallowedTypes.contains(nt1) || disallowedTypes.contains(nt2)) { continue; } + var tmpLargerDt = nt1.estimatedSize() > nt2.estimatedSize() ? nt1 : nt2; + if (nt2.estimatedSize() == nt1.estimatedSize()) { + tmpLargerDt = nt1.isRationalNumber() ? nt1 : nt2; + } + final var largerDt = tmpLargerDt; + final var largerDtName = PlannerUtils.toElementType(largerDt).pascalCaseName(); suppliers.add( new TestCaseSupplier( "(a, b)", - List.of(numericType, numericType), + List.of(nt1, nt2), () -> new TestCaseSupplier.TestCase( List.of( - new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(numericType, 1)), numericType, "a"), - new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(numericType, 2)), numericType, "b") - ), - String.format( - Locale.ROOT, - "ClampMin%sEvaluator[field=Attribute[channel=0], min=Attribute[channel=1]]", - numericType == DataType.UNSIGNED_LONG ? "Long" : capitalize.apply(numericType.esType()), - numericType == DataType.UNSIGNED_LONG ? "Long" : capitalize.apply(numericType.esType()) + new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(nt1, 1)), nt1, "a"), + new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(nt2, 2)), nt2, "b") ), - numericType, + Matchers.stringContainsInOrder("ClampMin", largerDtName, "Evaluator[field="), + largerDt, Matchers.allOf( Matchers.notNullValue(), Matchers.not(Matchers.notANumber()), diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampTests.java index d56c885fed1a3..58d73b75615d5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampTests.java @@ -19,12 +19,14 @@ import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import org.elasticsearch.xpack.esql.expression.function.scalar.Clamp; import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; +import org.elasticsearch.xpack.esql.planner.PlannerUtils; import org.hamcrest.Matchers; import java.util.List; -import java.util.Locale; +import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; public class ClampTests extends AbstractScalarFunctionTestCase { public ClampTests(@Name("TestCase") Supplier testCaseSupplier) { @@ -74,35 +76,46 @@ public static Iterable parameters() { if (dt.v1() == DataType.UNSIGNED_LONG) return dt.v2().longValue(); // we will use long to represent unsigned long throw new IllegalArgumentException("Unsupported data type: " + dt); }; - // function to make first letter uppercase - Function capitalize = s -> s.substring(0, 1).toUpperCase(Locale.ROOT) + s.substring(1); - for (DataType numericType : DataType.types().stream().filter(DataType::isNumeric).toList()) { - if (numericType == DataType.HALF_FLOAT - || numericType == DataType.SCALED_FLOAT - || numericType == DataType.SHORT - || numericType == DataType.BYTE - // || numericType == DataType.UNSIGNED_LONG // TODO: shouldnt unsigned long be supported? it was giving trouble... - || numericType == DataType.FLOAT) { // TODO: shouldnt float be supported? + final Set disallowedTypes = Set.of( + DataType.HALF_FLOAT, + DataType.SCALED_FLOAT, + DataType.SHORT, + DataType.BYTE, + DataType.FLOAT + ); + var allNumericTypes = DataType.types().stream().filter(DataType::isNumeric).toList(); + for (Tuple numericTypes : allNumericTypes.stream() + .flatMap(nt1 -> allNumericTypes.stream().map(nt2 -> Tuple.tuple(nt1, nt2))) + .collect(Collectors.toSet())) { + var nt1 = numericTypes.v1(); + var nt2 = numericTypes.v2(); + if (disallowedTypes.contains(nt1) || disallowedTypes.contains(nt2)) { continue; } + var tmpLargerDt = nt1.estimatedSize() > nt2.estimatedSize() ? nt1 : nt2; + if (nt2.estimatedSize() == nt1.estimatedSize()) { + tmpLargerDt = nt1.isRationalNumber() ? nt1 : nt2; + } + final var largerDt = tmpLargerDt; + final var largerDtName = PlannerUtils.toElementType(largerDt).pascalCaseName(); suppliers.add( new TestCaseSupplier( "(a, b, c)", - List.of(numericType, numericType, numericType), + List.of(nt1, nt2, nt2), () -> new TestCaseSupplier.TestCase( List.of( - new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(numericType, 1)), numericType, "a"), - new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(numericType, 2)), numericType, "b"), - new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(numericType, 3)), numericType, "c") + new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(nt1, 1)), nt1, "a"), + new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(nt2, 2)), nt2, "b"), + new TestCaseSupplier.TypedData(numberValue.apply(Tuple.tuple(nt2, 2)), nt2, "c") ), - String.format( - Locale.ROOT, - "ClampMax%sEvaluator[field=ClampMin%sEvaluator[field=Attribute[channel=0], min=Attribute[channel=1]], " - + "max=Attribute[channel=2]]", - numericType == DataType.UNSIGNED_LONG ? "Long" : capitalize.apply(numericType.esType()), - numericType == DataType.UNSIGNED_LONG ? "Long" : capitalize.apply(numericType.esType()) + Matchers.stringContainsInOrder( + "ClampMax", + largerDtName, + "Evaluator[field=ClampMin", + largerDtName, + "Evaluator[field=" ), - numericType, + largerDt, Matchers.allOf( Matchers.notNullValue(), Matchers.not(Matchers.notANumber()), From 8ddcc4434c3bce91eada6384a0454469e4ba0b12 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 28 Oct 2025 20:12:37 -0700 Subject: [PATCH 2/2] Fixup --- .../xpack/esql/expression/function/scalar/Clamp.java | 2 +- .../esql/expression/function/scalar/conditional/ClampMax.java | 2 +- .../esql/expression/function/scalar/conditional/ClampMin.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/Clamp.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/Clamp.java index 803b9a885d871..8ef0260abd423 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/Clamp.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/Clamp.java @@ -81,7 +81,7 @@ protected TypeResolution resolveType() { var field = children().get(0); var max = children().get(1); var min = children().get(2); - var fieldDataType = field.dataType(); + var fieldDataType = field.dataType().noText(); TypeResolution resolution = TypeResolutions.isType( field, t -> t.isNumeric() || t == DataType.BOOLEAN || t.isDate() || DataType.isString(t) || t == DataType.IP || t == DataType.VERSION, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMax.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMax.java index 941b827246c64..3c05733a6401b 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMax.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMax.java @@ -93,7 +93,7 @@ protected TypeResolution resolveType() { var field = children().get(0); var max = children().get(1); - var fieldDataType = field.dataType(); + var fieldDataType = field.dataType().noText(); TypeResolution resolution = TypeResolutions.isType( field, t -> t.isNumeric() || t == DataType.BOOLEAN || t.isDate() || DataType.isString(t) || t == DataType.IP || t == DataType.VERSION, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMin.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMin.java index 3f71b4483b7d0..35d94c3dae3f7 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMin.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/ClampMin.java @@ -93,7 +93,7 @@ protected TypeResolution resolveType() { var field = children().get(0); var min = children().get(1); - var fieldDataType = field.dataType(); + var fieldDataType = field.dataType().noText(); TypeResolution resolution = TypeResolutions.isType( field, t -> t.isNumeric() || t == DataType.BOOLEAN || t.isDate() || DataType.isString(t) || t == DataType.IP || t == DataType.VERSION,