From 05a4561793e870c2b9b5f7280e67e879aab2fb39 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Wed, 7 May 2025 09:58:27 -0400 Subject: [PATCH 01/16] abstract function --- .../elasticsearch/xpack/esql/core/expression/Attribute.java | 5 +++++ .../xpack/esql/core/expression/EmptyAttribute.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Attribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Attribute.java index 1af98f4b21dc5..2e3a29ef21770 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Attribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Attribute.java @@ -134,4 +134,9 @@ public String nodeString() { } protected abstract String label(); + + /** + * @return true if the attribute represents a TSDB dimension type + */ + public abstract boolean isDimension(); } diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/EmptyAttribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/EmptyAttribute.java index 1cacf74a8207c..cf6a696aabb7b 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/EmptyAttribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/EmptyAttribute.java @@ -44,6 +44,11 @@ protected String label() { return "e"; } + @Override + public boolean isDimension() { + return false; + } + @Override public boolean resolved() { return true; From 9d79b2f7f767ecb583167028f373cdfd6951359f Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Tue, 17 Jun 2025 12:58:42 -0400 Subject: [PATCH 02/16] stub in field attribute --- .../xpack/esql/core/expression/FieldAttribute.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java index 832745f7cb15e..b670faa5dab3a 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java @@ -206,6 +206,11 @@ protected String label() { return "f"; } + @Override + public boolean isDimension() { + return false; + } + public EsField field() { return field; } From c11fa129239fec9d87d19a042e3bdd2a9be3bffe Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Mon, 7 Jul 2025 13:47:39 -0400 Subject: [PATCH 03/16] begin wiring up the time series types --- .../xpack/esql/core/type/EsField.java | 52 +++++++++++++++++-- .../xpack/esql/analysis/Analyzer.java | 2 +- .../xpack/esql/session/IndexResolver.java | 12 ++++- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java index daf26a7b5348f..57baad61f0437 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.core.type; import org.elasticsearch.TransportVersions; +import org.elasticsearch.action.fieldcaps.IndexFieldCapabilities; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; @@ -31,10 +32,36 @@ public class EsField implements Writeable { * roles within the ESQL query processing pipeline. */ public enum TimeSeriesFieldType implements Writeable { - UNKNOWN(0), - NONE(1), - METRIC(2), - DIMENSION(3); + UNKNOWN(0) { + @Override + public TimeSeriesFieldType merge(TimeSeriesFieldType other) { + return other; + } + }, + NONE(1) { + @Override + public TimeSeriesFieldType merge(TimeSeriesFieldType other) { + return other; + } + }, + METRIC(2) { + @Override + public TimeSeriesFieldType merge(TimeSeriesFieldType other) { + if (other != DIMENSION) { + return METRIC; + } + throw new IllegalStateException("Time Series Metadata conflict. Cannot merge [" + other + "] with [METRIC]."); + } + }, + DIMENSION(3) { + @Override + public TimeSeriesFieldType merge(TimeSeriesFieldType other) { + if (other != METRIC) { + return DIMENSION; + } + throw new IllegalStateException("Time Series Metadata conflict. Cannot merge [" + other + "] with [DIMENSION]."); + } + }; private final int id; @@ -57,6 +84,18 @@ public static TimeSeriesFieldType readFromStream(StreamInput in) throws IOExcept default -> throw new IOException("Unexpected value for TimeSeriesFieldType: " + id); }; } + + public static TimeSeriesFieldType fromIndexFieldCapabilities(IndexFieldCapabilities capabilities) { + if (capabilities.isDimension()) { + return DIMENSION; + } + if (capabilities.metricType() != null) { + return METRIC; + } + return NONE; + } + + public abstract TimeSeriesFieldType merge(TimeSeriesFieldType other); } private static Map> readers = Map.ofEntries( @@ -83,7 +122,6 @@ public static Reader getReader(String name) { private final Map properties; private final String name; private final boolean isAlias; - // Because the subclasses all reimplement serialization, this needs to be writeable from subclass constructors private final TimeSeriesFieldType timeSeriesFieldType; public EsField(String name, DataType esDataType, Map properties, boolean aggregatable) { @@ -247,6 +285,10 @@ public Exact getExactInfo() { return Exact.EXACT_FIELD; } + public TimeSeriesFieldType getTimeSeriesFieldType() { + return timeSeriesFieldType; + } + @Override public String toString() { return name + "@" + esDataType.typeName() + "=" + properties; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index 1b48ffd22b491..1510ff1da5340 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -294,7 +294,7 @@ private static void mappingAsAttributes(List list, Source source, Str // due to a bug also copy the field since the Attribute hierarchy extracts the data type // directly even if the data type is passed explicitly if (type != t.getDataType()) { - t = new EsField(t.getName(), type, t.getProperties(), t.isAggregatable(), t.isAlias()); + t = new EsField(t.getName(), type, t.getProperties(), t.isAggregatable(), t.isAlias(), t.getTimeSeriesFieldType()); } FieldAttribute attribute = t instanceof UnsupportedEsField uef diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java index d2f79ceb1316f..a8b3cca60a2d6 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java @@ -100,6 +100,7 @@ public static IndexResolution mergedMappings(String indexPattern, FieldCapabilit return IndexResolution.notFound(indexPattern); } + // For each field name, store a list of the field caps responses from each index Map> fieldsCaps = collectFieldCaps(fieldCapsResponse); // Build hierarchical fields - it's easier to do it in sorted order so the object fields come first. @@ -121,7 +122,8 @@ public static IndexResolution mergedMappings(String indexPattern, FieldCapabilit String parent = name.substring(0, nextDot); EsField obj = fields.get(parent); if (obj == null) { - obj = new EsField(parent, OBJECT, new HashMap<>(), false, true); + // Object fields can't be dimensions, so we can safely hard code that here + obj = new EsField(parent, OBJECT, new HashMap<>(), false, true, EsField.TimeSeriesFieldType.NONE); isAlias = true; fields.put(parent, obj); } else if (firstUnsupportedParent == null && obj instanceof UnsupportedEsField unsupportedParent) { @@ -204,11 +206,17 @@ private static EsField createField( List rest = fcs.subList(1, fcs.size()); DataType type = EsqlDataTypeRegistry.INSTANCE.fromEs(first.type(), first.metricType()); boolean aggregatable = first.isAggregatable(); + EsField.TimeSeriesFieldType timeSeriesFieldType = EsField.TimeSeriesFieldType.UNKNOWN; if (rest.isEmpty() == false) { for (IndexFieldCapabilities fc : rest) { if (first.metricType() != fc.metricType()) { return conflictingMetricTypes(name, fullName, fieldCapsResponse); } + try { + timeSeriesFieldType = timeSeriesFieldType.merge(EsField.TimeSeriesFieldType.fromIndexFieldCapabilities(fc)); + } catch (IllegalArgumentException e) { + return new InvalidMappedField(name, e.getMessage()); + } } for (IndexFieldCapabilities fc : rest) { if (type != EsqlDataTypeRegistry.INSTANCE.fromEs(fc.type(), fc.metricType())) { @@ -223,7 +231,7 @@ private static EsField createField( // TODO I think we only care about unmapped fields if we're aggregating on them. do we even then? if (type == TEXT) { - return new TextEsField(name, new HashMap<>(), false, isAlias); + return new TextEsField(name, new HashMap<>(), false, isAlias, timeSeriesFieldType); } if (type == KEYWORD) { int length = Short.MAX_VALUE; From 28a3aea8709333779b4c0a0aff1eb5a983398c47 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Mon, 7 Jul 2025 14:55:56 -0400 Subject: [PATCH 04/16] stub in abstract methods --- .../xpack/esql/core/expression/MetadataAttribute.java | 5 +++++ .../xpack/esql/core/expression/ReferenceAttribute.java | 5 +++++ .../xpack/esql/core/expression/UnresolvedAttribute.java | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/MetadataAttribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/MetadataAttribute.java index 34ff2cec2960a..07fe6b16b3bd1 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/MetadataAttribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/MetadataAttribute.java @@ -120,6 +120,11 @@ protected String label() { return "m"; } + @Override + public boolean isDimension() { + return false; + } + @Override protected NodeInfo info() { return NodeInfo.create(this, MetadataAttribute::new, name(), dataType(), nullable(), id(), synthetic(), searchable); diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/ReferenceAttribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/ReferenceAttribute.java index 404cd75edd5e4..9e203f84b68d9 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/ReferenceAttribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/ReferenceAttribute.java @@ -117,4 +117,9 @@ protected NodeInfo info() { protected String label() { return "r"; } + + @Override + public boolean isDimension() { + return false; + } } diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/UnresolvedAttribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/UnresolvedAttribute.java index 428ab4023a3f9..3b59b678dc91f 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/UnresolvedAttribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/UnresolvedAttribute.java @@ -98,6 +98,11 @@ protected String label() { return UNRESOLVED_PREFIX; } + @Override + public boolean isDimension() { + return false; + } + @Override public String nodeString() { return toString(); From 001047d7cd0003208730a747b57b112804caa3d7 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Tue, 8 Jul 2025 10:15:03 -0400 Subject: [PATCH 05/16] explicit metadata type for some benchmarks --- .../_nightly/esql/QueryPlanningBenchmark.java | 3 +- .../compute/operator/EvalBenchmark.java | 26 +++++-- .../esql/_snippets/operators/types/is_null.md | 3 - .../definition/functions/st_geohash.json | 2 +- .../definition/functions/st_geohex.json | 2 +- .../functions/st_geohex_to_long.json | 2 +- .../functions/st_geohex_to_string.json | 2 +- .../definition/functions/st_geotile.json | 2 +- .../functions/st_geotile_to_long.json | 2 +- .../functions/st_geotile_to_string.json | 2 +- .../kibana/definition/operators/is_null.json | 72 +++++-------------- .../esql/kibana/docs/functions/knn.md | 2 +- .../esql/kibana/docs/operators/is_null.md | 7 +- 13 files changed, 55 insertions(+), 72 deletions(-) diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java index d2811962dd29d..2117d8f61cf2a 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java @@ -92,7 +92,8 @@ public void setup() { var fields = 10_000; var mapping = LinkedHashMap.newLinkedHashMap(fields); for (int i = 0; i < fields; i++) { - mapping.put("field" + i, new EsField("field-" + i, TEXT, emptyMap(), true)); + // We're creating a standard index, so none of these fields should be marked as dimensions. + mapping.put("field" + i, new EsField("field-" + i, TEXT, emptyMap(), true, EsField.TimeSeriesFieldType.NONE)); } var esIndex = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java index 5bd003fe4271f..6357c0e9c396f 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java @@ -212,7 +212,7 @@ private static EvalOperator.ExpressionEvaluator evaluator(String operation) { FieldAttribute timestamp = new FieldAttribute( Source.EMPTY, "timestamp", - new EsField("timestamp", DataType.DATETIME, Map.of(), true) + new EsField("timestamp", DataType.DATETIME, Map.of(), true, EsField.TimeSeriesFieldType.NONE) ); yield EvalMapper.toEvaluator( FOLD_CONTEXT, @@ -321,19 +321,35 @@ private static EvalOperator.ExpressionEvaluator evaluator(String operation) { } private static FieldAttribute longField() { - return new FieldAttribute(Source.EMPTY, "long", new EsField("long", DataType.LONG, Map.of(), true)); + return new FieldAttribute( + Source.EMPTY, + "long", + new EsField("long", DataType.LONG, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ); } private static FieldAttribute doubleField() { - return new FieldAttribute(Source.EMPTY, "double", new EsField("double", DataType.DOUBLE, Map.of(), true)); + return new FieldAttribute( + Source.EMPTY, + "double", + new EsField("double", DataType.DOUBLE, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ); } private static FieldAttribute intField() { - return new FieldAttribute(Source.EMPTY, "int", new EsField("int", DataType.INTEGER, Map.of(), true)); + return new FieldAttribute( + Source.EMPTY, + "int", + new EsField("int", DataType.INTEGER, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ); } private static FieldAttribute keywordField() { - return new FieldAttribute(Source.EMPTY, "keyword", new EsField("keyword", DataType.KEYWORD, Map.of(), true)); + return new FieldAttribute( + Source.EMPTY, + "keyword", + new EsField("keyword", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ); } private static Configuration configuration() { diff --git a/docs/reference/query-languages/esql/_snippets/operators/types/is_null.md b/docs/reference/query-languages/esql/_snippets/operators/types/is_null.md index f2b65a11de81e..19fc0a9465976 100644 --- a/docs/reference/query-languages/esql/_snippets/operators/types/is_null.md +++ b/docs/reference/query-languages/esql/_snippets/operators/types/is_null.md @@ -7,9 +7,6 @@ | boolean | boolean | | cartesian_point | boolean | | cartesian_shape | boolean | -| counter_double | boolean | -| counter_integer | boolean | -| counter_long | boolean | | date | boolean | | date_nanos | boolean | | double | boolean | diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_geohash.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_geohash.json index d2fc83008c150..43633b336453a 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_geohash.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_geohash.json @@ -52,4 +52,4 @@ ], "preview" : true, "snapshot_only" : true -} \ No newline at end of file +} diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex.json index 9a3a04cb0a7f8..f29db14ed50e7 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex.json @@ -55,4 +55,4 @@ ], "preview" : true, "snapshot_only" : true -} \ No newline at end of file +} diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex_to_long.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex_to_long.json index 52c7918a0c3ad..d582739620024 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex_to_long.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex_to_long.json @@ -34,4 +34,4 @@ ], "preview" : true, "snapshot_only" : true -} \ No newline at end of file +} diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex_to_string.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex_to_string.json index 612b13691d40c..a1abce7c75adb 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex_to_string.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_geohex_to_string.json @@ -34,4 +34,4 @@ ], "preview" : true, "snapshot_only" : true -} \ No newline at end of file +} diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile.json index 06df5e3076fea..d728f186fc5ae 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile.json @@ -52,4 +52,4 @@ ], "preview" : true, "snapshot_only" : true -} \ No newline at end of file +} diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile_to_long.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile_to_long.json index 2eb49b5c320f9..b2c7c01aea606 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile_to_long.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile_to_long.json @@ -34,4 +34,4 @@ ], "preview" : true, "snapshot_only" : true -} \ No newline at end of file +} diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile_to_string.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile_to_string.json index df8e91514dc7b..5a327c2c50976 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile_to_string.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_geotile_to_string.json @@ -34,4 +34,4 @@ ], "preview" : true, "snapshot_only" : true -} \ No newline at end of file +} diff --git a/docs/reference/query-languages/esql/kibana/definition/operators/is_null.json b/docs/reference/query-languages/esql/kibana/definition/operators/is_null.json index 0f6e70f8a0b91..6eda2b9db776e 100644 --- a/docs/reference/query-languages/esql/kibana/definition/operators/is_null.json +++ b/docs/reference/query-languages/esql/kibana/definition/operators/is_null.json @@ -3,8 +3,7 @@ "type" : "operator", "operator" : "IS NULL", "name" : "is_null", - "description" : "Returns `true` if the value is `NULL`, `false` otherwise.", - "note" : "If a field is only in some documents it will be `NULL` in the documents that did not contain it.", + "description" : "Use `IS NULL` to filter data based on whether the field exists or not.", "signatures" : [ { "params" : [ @@ -12,7 +11,7 @@ "name" : "field", "type" : "boolean", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -24,7 +23,7 @@ "name" : "field", "type" : "cartesian_point", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -36,43 +35,7 @@ "name" : "field", "type" : "cartesian_shape", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." - } - ], - "variadic" : false, - "returnType" : "boolean" - }, - { - "params" : [ - { - "name" : "field", - "type" : "counter_double", - "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." - } - ], - "variadic" : false, - "returnType" : "boolean" - }, - { - "params" : [ - { - "name" : "field", - "type" : "counter_integer", - "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." - } - ], - "variadic" : false, - "returnType" : "boolean" - }, - { - "params" : [ - { - "name" : "field", - "type" : "counter_long", - "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -84,7 +47,7 @@ "name" : "field", "type" : "date", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -96,7 +59,7 @@ "name" : "field", "type" : "date_nanos", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -108,7 +71,7 @@ "name" : "field", "type" : "double", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -120,7 +83,7 @@ "name" : "field", "type" : "geo_point", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -132,7 +95,7 @@ "name" : "field", "type" : "geo_shape", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -144,7 +107,7 @@ "name" : "field", "type" : "integer", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -156,7 +119,7 @@ "name" : "field", "type" : "ip", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -168,7 +131,7 @@ "name" : "field", "type" : "keyword", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -180,7 +143,7 @@ "name" : "field", "type" : "long", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -192,7 +155,7 @@ "name" : "field", "type" : "text", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -204,7 +167,7 @@ "name" : "field", "type" : "unsigned_long", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, @@ -216,13 +179,16 @@ "name" : "field", "type" : "version", "optional" : false, - "description" : "Value to check. It can be a single- or multi-valued column or an expression." + "description" : "Input value. The input can be a single- or multi-valued column or an expression." } ], "variadic" : false, "returnType" : "boolean" } ], + "examples" : [ + "FROM employees\n| WHERE birth_date IS NULL" + ], "preview" : false, "snapshot_only" : false } diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/knn.md b/docs/reference/query-languages/esql/kibana/docs/functions/knn.md index c7af797488ba4..f32319b080dbb 100644 --- a/docs/reference/query-languages/esql/kibana/docs/functions/knn.md +++ b/docs/reference/query-languages/esql/kibana/docs/functions/knn.md @@ -1,4 +1,4 @@ -% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. +% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. ### KNN Finds the k nearest vectors to a query vector, as measured by a similarity metric. knn function finds nearest vectors through approximate search on indexed dense_vectors. diff --git a/docs/reference/query-languages/esql/kibana/docs/operators/is_null.md b/docs/reference/query-languages/esql/kibana/docs/operators/is_null.md index 625b819935332..8a21704a6a70b 100644 --- a/docs/reference/query-languages/esql/kibana/docs/operators/is_null.md +++ b/docs/reference/query-languages/esql/kibana/docs/operators/is_null.md @@ -1,6 +1,9 @@ % This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. ### IS NULL -Returns `true` if the value is `NULL`, `false` otherwise. +Use `IS NULL` to filter data based on whether the field exists or not. -Note: If a field is only in some documents it will be `NULL` in the documents that did not contain it. +```esql +FROM employees +| WHERE birth_date IS NULL +``` From 3397960a158e299c6d0deef62fed0c7212bb2e22 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Tue, 8 Jul 2025 14:03:16 -0400 Subject: [PATCH 06/16] explicit metadata type --- .../org/elasticsearch/xpack/esql/core/util/TestUtils.java | 4 ++-- .../java/org/elasticsearch/xpack/esql/EsqlTestUtils.java | 4 ++-- .../java/org/elasticsearch/xpack/esql/LoadMapping.java | 2 +- .../org/elasticsearch/xpack/esql/analysis/Analyzer.java | 2 +- .../xpack/esql/plan/physical/EsQueryExec.java | 8 +++++++- .../test/java/org/elasticsearch/xpack/esql/CsvTests.java | 8 +++++++- .../expression/function/AbstractFunctionTestCase.java | 4 ++-- .../xpack/esql/expression/function/fulltext/KnnTests.java | 2 +- .../expression/function/scalar/NamedExpressionTests.java | 6 +++++- 9 files changed, 28 insertions(+), 12 deletions(-) diff --git a/x-pack/plugin/esql-core/src/test/java/org/elasticsearch/xpack/esql/core/util/TestUtils.java b/x-pack/plugin/esql-core/src/test/java/org/elasticsearch/xpack/esql/core/util/TestUtils.java index 8f07d40bd0665..c86f704e62cb2 100644 --- a/x-pack/plugin/esql-core/src/test/java/org/elasticsearch/xpack/esql/core/util/TestUtils.java +++ b/x-pack/plugin/esql-core/src/test/java/org/elasticsearch/xpack/esql/core/util/TestUtils.java @@ -54,7 +54,7 @@ public static FieldAttribute fieldAttribute() { } public static FieldAttribute fieldAttribute(String name, DataType type) { - return new FieldAttribute(EMPTY, name, new EsField(name, type, emptyMap(), randomBoolean())); + return new FieldAttribute(EMPTY, name, new EsField(name, type, emptyMap(), randomBoolean(), EsField.TimeSeriesFieldType.NONE)); } public static FieldAttribute getFieldAttribute(String name) { @@ -62,7 +62,7 @@ public static FieldAttribute getFieldAttribute(String name) { } public static FieldAttribute getFieldAttribute(String name, DataType dataType) { - return new FieldAttribute(EMPTY, name, new EsField(name + "f", dataType, emptyMap(), true)); + return new FieldAttribute(EMPTY, name, new EsField(name + "f", dataType, emptyMap(), true, EsField.TimeSeriesFieldType.NONE)); } /** diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java index 7ec9ee6344551..b980539428a46 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java @@ -206,7 +206,7 @@ public static FieldAttribute getFieldAttribute(String name) { } public static FieldAttribute getFieldAttribute(String name, DataType dataType) { - return new FieldAttribute(EMPTY, name, new EsField(name + "f", dataType, emptyMap(), true)); + return new FieldAttribute(EMPTY, name, new EsField(name + "f", dataType, emptyMap(), true, EsField.TimeSeriesFieldType.NONE)); } public static FieldAttribute fieldAttribute() { @@ -214,7 +214,7 @@ public static FieldAttribute fieldAttribute() { } public static FieldAttribute fieldAttribute(String name, DataType type) { - return new FieldAttribute(EMPTY, name, new EsField(name, type, emptyMap(), randomBoolean())); + return new FieldAttribute(EMPTY, name, new EsField(name, type, emptyMap(), randomBoolean(), EsField.TimeSeriesFieldType.NONE)); } public static Literal of(Object value) { diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java index 1146d7729a8d4..2e59b889042c8 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java @@ -114,7 +114,7 @@ private static void walkMapping(String name, Object value, Map field = new UnsupportedEsField(name, List.of(type), null, properties); propagateUnsupportedType(name, type, properties); } else { - field = new EsField(name, esDataType, properties, docValues); + field = new EsField(name, esDataType, properties, docValues, EsField.TimeSeriesFieldType.NONE); } mapping.put(name, field); } else { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index 1510ff1da5340..d98ca92a29601 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -1819,7 +1819,7 @@ private static boolean canConvertOriginalTypes(MultiTypeEsField multiTypeEsField } private static Expression typeSpecificConvert(ConvertFunction convert, Source source, DataType type, InvalidMappedField mtf) { - EsField field = new EsField(mtf.getName(), type, mtf.getProperties(), mtf.isAggregatable()); + EsField field = new EsField(mtf.getName(), type, mtf.getProperties(), mtf.isAggregatable(), mtf.getTimeSeriesFieldType()); FieldAttribute originalFieldAttr = (FieldAttribute) convert.field(); FieldAttribute resolvedAttr = new FieldAttribute( source, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/EsQueryExec.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/EsQueryExec.java index 2e74c7153f77e..f29554081ed01 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/EsQueryExec.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/EsQueryExec.java @@ -46,7 +46,13 @@ public class EsQueryExec extends LeafExec implements EstimatesRowSize { EsQueryExec::readFrom ); - public static final EsField DOC_ID_FIELD = new EsField("_doc", DataType.DOC_DATA_TYPE, Map.of(), false); + public static final EsField DOC_ID_FIELD = new EsField( + "_doc", + DataType.DOC_DATA_TYPE, + Map.of(), + false, + EsField.TimeSeriesFieldType.NONE + ); public static final List NO_SORTS = List.of(); // only exists to mimic older serialization, but we no longer serialize sorts private final String indexPattern; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java index 9062bdef62d76..90c47b58189e0 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java @@ -420,7 +420,13 @@ private static Map createMappingForIndex(CsvTestsDataLoader.Tes if (mapping.containsKey(entry.getKey())) { DataType dataType = DataType.fromTypeName(entry.getValue()); EsField field = mapping.get(entry.getKey()); - EsField editedField = new EsField(field.getName(), dataType, field.getProperties(), field.isAggregatable()); + EsField editedField = new EsField( + field.getName(), + dataType, + field.getProperties(), + field.isAggregatable(), + field.getTimeSeriesFieldType() + ); mapping.put(entry.getKey(), editedField); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java index 00f20b9376a6f..be3d70527c3f4 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java @@ -447,7 +447,7 @@ public static Stream validFunctionParameters() { * Build an {@link Attribute} that loads a field. */ public static FieldAttribute field(String name, DataType type) { - return new FieldAttribute(Source.synthetic(name), name, new EsField(name, type, Map.of(), true)); + return new FieldAttribute(Source.synthetic(name), name, new EsField(name, type, Map.of(), true, EsField.TimeSeriesFieldType.NONE)); } /** @@ -456,7 +456,7 @@ public static FieldAttribute field(String name, DataType type) { public static Expression deepCopyOfField(String name, DataType type) { return new DeepCopy( Source.synthetic(name), - new FieldAttribute(Source.synthetic(name), name, new EsField(name, type, Map.of(), true)) + new FieldAttribute(Source.synthetic(name), name, new EsField(name, type, Map.of(), true, EsField.TimeSeriesFieldType.NONE)) ); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KnnTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KnnTests.java index 4a5708b398b18..3bcd10fef1efb 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KnnTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KnnTests.java @@ -66,7 +66,7 @@ private static List testCaseSuppliers() { new FieldAttribute( Source.EMPTY, randomIdentifier(), - new EsField(randomIdentifier(), DENSE_VECTOR, Map.of(), false) + new EsField(randomIdentifier(), DENSE_VECTOR, Map.of(), false, EsField.TimeSeriesFieldType.NONE) ), DENSE_VECTOR, "dense_vector field" diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/NamedExpressionTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/NamedExpressionTests.java index 06e60fc437df0..00afd4947c890 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/NamedExpressionTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/NamedExpressionTests.java @@ -53,7 +53,11 @@ public void testArithmeticFunctionName() { } public void testNameForArithmeticFunctionAppliedOnTableColumn() { - FieldAttribute fa = new FieldAttribute(EMPTY, "myField", new EsField("myESField", DataType.INTEGER, emptyMap(), true)); + FieldAttribute fa = new FieldAttribute( + EMPTY, + "myField", + new EsField("myESField", DataType.INTEGER, emptyMap(), true, EsField.TimeSeriesFieldType.NONE) + ); String e = "myField + 10"; Add add = new Add(s(e), fa, l(10)); assertEquals(e, add.sourceText()); From 038f743960d1d91659d27c1c02a50e1895f1bc9f Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Thu, 10 Jul 2025 10:12:40 -0400 Subject: [PATCH 07/16] stuff --- .../expression/function/scalar/nulls/CoalesceTests.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/CoalesceTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/CoalesceTests.java index 1235a175294af..e7d34e17b44c8 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/CoalesceTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/CoalesceTests.java @@ -205,7 +205,11 @@ protected Coalesce build(Source source, List args) { public void testCoalesceIsLazy() { List sub = new ArrayList<>(testCase.getDataAsFields()); - FieldAttribute evil = new FieldAttribute(Source.EMPTY, "evil", new EsField("evil", sub.get(0).dataType(), Map.of(), true)); + FieldAttribute evil = new FieldAttribute( + Source.EMPTY, + "evil", + new EsField("evil", sub.get(0).dataType(), Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ); sub.add(evil); Coalesce exp = build(Source.EMPTY, sub); Layout.Builder builder = new Layout.Builder(); From 37849ed8462a6b349231be4c96a9d578e1174015 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Wed, 16 Jul 2025 14:46:43 -0400 Subject: [PATCH 08/16] explicit default for a bunch of constructors --- .../scalar/string/EndsWithStaticTests.java | 18 ++++++++-- .../scalar/string/RepeatStaticTests.java | 2 +- .../scalar/string/ReplaceStaticTests.java | 2 +- .../scalar/string/StartsWithStaticTests.java | 18 ++++++++-- .../operator/comparison/InStaticTests.java | 6 +++- .../esql/index/EsIndexSerializationTests.java | 8 ++--- .../AbstractLogicalPlanOptimizerTests.java | 5 ++- .../LocalLogicalPlanOptimizerTests.java | 6 +++- .../LocalPhysicalPlanOptimizerTests.java | 4 +-- .../optimizer/PhysicalPlanOptimizerTests.java | 33 ++++++++++--------- .../physical/local/PushTopNToSourceTests.java | 5 ++- .../xpack/esql/planner/EvalMapperTests.java | 6 +++- .../planner/LocalExecutionPlannerTests.java | 12 +++++-- .../esql/querydsl/query/MatchQueryTests.java | 4 +-- .../esql/tree/EsqlNodeSubclassTests.java | 6 +++- .../esql/type/MultiTypeEsFieldTests.java | 2 +- 16 files changed, 97 insertions(+), 40 deletions(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/EndsWithStaticTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/EndsWithStaticTests.java index ddde306deed7a..7d8ab4f65b498 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/EndsWithStaticTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/EndsWithStaticTests.java @@ -33,8 +33,16 @@ public void testLuceneQuery_AllLiterals_NonTranslatable() { public void testLuceneQuery_NonFoldableSuffix_NonTranslatable() { EndsWith function = new EndsWith( Source.EMPTY, - new FieldAttribute(Source.EMPTY, "field", new EsField("field", DataType.KEYWORD, Map.of(), true)), - new FieldAttribute(Source.EMPTY, "field", new EsField("suffix", DataType.KEYWORD, Map.of(), true)) + new FieldAttribute( + Source.EMPTY, + "field", + new EsField("field", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ), + new FieldAttribute( + Source.EMPTY, + "field", + new EsField("suffix", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ) ); assertThat(function.translatable(LucenePushdownPredicates.DEFAULT), equalTo(TranslationAware.Translatable.NO)); @@ -43,7 +51,11 @@ public void testLuceneQuery_NonFoldableSuffix_NonTranslatable() { public void testLuceneQuery_NonFoldableSuffix_Translatable() { EndsWith function = new EndsWith( Source.EMPTY, - new FieldAttribute(Source.EMPTY, "field", new EsField("suffix", DataType.KEYWORD, Map.of(), true)), + new FieldAttribute( + Source.EMPTY, + "field", + new EsField("suffix", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ), Literal.keyword(Source.EMPTY, "a*b?c\\") ); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatStaticTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatStaticTests.java index 95db9daa21283..539e285b485cf 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatStaticTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatStaticTests.java @@ -83,7 +83,7 @@ private static Page row(List values) { } private static FieldAttribute field(String name, DataType type) { - return new FieldAttribute(Source.synthetic(name), name, new EsField(name, type, Map.of(), true)); + return new FieldAttribute(Source.synthetic(name), name, new EsField(name, type, Map.of(), true, EsField.TimeSeriesFieldType.NONE)); } private DriverContext driverContext() { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ReplaceStaticTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ReplaceStaticTests.java index cac4b5acfa320..c9796e3bc34bb 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ReplaceStaticTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ReplaceStaticTests.java @@ -120,7 +120,7 @@ private static Page row(List values) { } private static FieldAttribute field(String name, DataType type) { - return new FieldAttribute(Source.synthetic(name), name, new EsField(name, type, Map.of(), true)); + return new FieldAttribute(Source.synthetic(name), name, new EsField(name, type, Map.of(), true, EsField.TimeSeriesFieldType.NONE)); } private DriverContext driverContext() { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/StartsWithStaticTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/StartsWithStaticTests.java index 105ce6a9e4142..9c206908d58c5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/StartsWithStaticTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/StartsWithStaticTests.java @@ -33,8 +33,16 @@ public void testLuceneQuery_AllLiterals_NonTranslatable() { public void testLuceneQuery_NonFoldablePrefix_NonTranslatable() { var function = new StartsWith( Source.EMPTY, - new FieldAttribute(Source.EMPTY, "field", new EsField("field", DataType.KEYWORD, Map.of(), true)), - new FieldAttribute(Source.EMPTY, "field", new EsField("prefix", DataType.KEYWORD, Map.of(), true)) + new FieldAttribute( + Source.EMPTY, + "field", + new EsField("field", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ), + new FieldAttribute( + Source.EMPTY, + "field", + new EsField("prefix", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ) ); assertThat(function.translatable(LucenePushdownPredicates.DEFAULT), equalTo(TranslationAware.Translatable.NO)); @@ -43,7 +51,11 @@ public void testLuceneQuery_NonFoldablePrefix_NonTranslatable() { public void testLuceneQuery_NonFoldablePrefix_Translatable() { var function = new StartsWith( Source.EMPTY, - new FieldAttribute(Source.EMPTY, "field", new EsField("prefix", DataType.KEYWORD, Map.of(), true)), + new FieldAttribute( + Source.EMPTY, + "field", + new EsField("prefix", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ), Literal.keyword(Source.EMPTY, "a*b?c\\") ); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InStaticTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InStaticTests.java index b2fa9f4221769..48bf4f6dbb6fc 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InStaticTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InStaticTests.java @@ -59,7 +59,11 @@ public void testHandleNullsOnRightValue() { public void testConvertedNull() { In in = new In( EMPTY, - new FieldAttribute(Source.EMPTY, "field", new EsField("suffix", DataType.KEYWORD, Map.of(), true)), + new FieldAttribute( + Source.EMPTY, + "field", + new EsField("suffix", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ), Arrays.asList(ONE, new Literal(Source.EMPTY, null, randomFrom(DataType.types())), THREE) ); var query = in.asQuery(LucenePushdownPredicates.DEFAULT, TranslatorHandler.TRANSLATOR_HANDLER); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/index/EsIndexSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/index/EsIndexSerializationTests.java index 05a08cc1402c1..22cdef70182cc 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/index/EsIndexSerializationTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/index/EsIndexSerializationTests.java @@ -115,11 +115,11 @@ public static EsIndex indexWithManyConflicts(boolean withParent) { } for (int i = 0; i < nonConflictingCount; i++) { String name = String.format(Locale.ROOT, "blah.blah.blah.blah.blah.blah.nonconflict.name%04d", i); - fields.put(name, new EsField(name, DataType.KEYWORD, Map.of(), true)); + fields.put(name, new EsField(name, DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE)); } if (withParent) { - EsField parent = new EsField("parent", DataType.OBJECT, Map.copyOf(fields), false); + EsField parent = new EsField("parent", DataType.OBJECT, Map.copyOf(fields), false, EsField.TimeSeriesFieldType.NONE); fields.put("parent", parent); } @@ -199,7 +199,7 @@ private static EsField fieldWithRecursiveChildren(int depth, int childrenPerLeve if (depth == 1) { for (int i = 0; i < childrenPerLevel; i++) { childName = "leaf" + i; - children.put(childName, new EsField(childName, DataType.KEYWORD, Map.of(), true)); + children.put(childName, new EsField(childName, DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE)); } } else { for (int i = 0; i < childrenPerLevel; i++) { @@ -208,7 +208,7 @@ private static EsField fieldWithRecursiveChildren(int depth, int childrenPerLeve } } - return new EsField(name, DataType.OBJECT, children, false); + return new EsField(name, DataType.OBJECT, children, false, EsField.TimeSeriesFieldType.NONE); } /** diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java index 4263b8d07c8f2..8c334c9ca29db 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java @@ -150,7 +150,10 @@ public static void init() { ); var multiIndexMapping = loadMapping("mapping-basic.json"); - multiIndexMapping.put("partial_type_keyword", new EsField("partial_type_keyword", KEYWORD, emptyMap(), true)); + multiIndexMapping.put( + "partial_type_keyword", + new EsField("partial_type_keyword", KEYWORD, emptyMap(), true, EsField.TimeSeriesFieldType.NONE) + ); var multiIndex = IndexResolution.valid( new EsIndex( "multi_index", diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java index f89c79079c876..c6bb95bc8e93c 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java @@ -340,7 +340,11 @@ public void testMissingFieldInNewCommand() { new MockFieldAttributeCommand( EMPTY, new Row(EMPTY, List.of()), - new FieldAttribute(EMPTY, "last_name", new EsField("last_name", DataType.KEYWORD, Map.of(), true)) + new FieldAttribute( + EMPTY, + "last_name", + new EsField("last_name", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ) ), testStats ); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java index a604e1d26d313..614e1fe0150f6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java @@ -221,8 +221,8 @@ public void init() { List.of("a", "b"), Map.of("", "idx"), Map.ofEntries( - Map.entry("a", new EsField("a", DataType.INTEGER, Map.of(), true)), - Map.entry("b", new EsField("b", DataType.LONG, Map.of(), true)) + Map.entry("a", new EsField("a", DataType.INTEGER, Map.of(), true, EsField.TimeSeriesFieldType.NONE)), + Map.entry("b", new EsField("b", DataType.LONG, Map.of(), true, EsField.TimeSeriesFieldType.NONE)) ) ) ); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java index 79d58341783aa..49c6200b66781 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java @@ -402,8 +402,8 @@ private static EnrichResolution setupEnrichResolution() { List.of("a", "b"), Map.of("", "idx"), Map.ofEntries( - Map.entry("a", new EsField("a", DataType.INTEGER, Map.of(), true)), - Map.entry("b", new EsField("b", DataType.LONG, Map.of(), true)) + Map.entry("a", new EsField("a", DataType.INTEGER, Map.of(), true, EsField.TimeSeriesFieldType.NONE)), + Map.entry("b", new EsField("b", DataType.LONG, Map.of(), true, EsField.TimeSeriesFieldType.NONE)) ) ) ); @@ -416,10 +416,13 @@ private static EnrichResolution setupEnrichResolution() { List.of("city", "airport", "region", "city_boundary"), Map.of("", "airport_city_boundaries"), Map.ofEntries( - Map.entry("city", new EsField("city", DataType.KEYWORD, Map.of(), true)), - Map.entry("airport", new EsField("airport", DataType.TEXT, Map.of(), false)), - Map.entry("region", new EsField("region", DataType.TEXT, Map.of(), false)), - Map.entry("city_boundary", new EsField("city_boundary", DataType.GEO_SHAPE, Map.of(), false)) + Map.entry("city", new EsField("city", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE)), + Map.entry("airport", new EsField("airport", DataType.TEXT, Map.of(), false, EsField.TimeSeriesFieldType.NONE)), + Map.entry("region", new EsField("region", DataType.TEXT, Map.of(), false, EsField.TimeSeriesFieldType.NONE)), + Map.entry( + "city_boundary", + new EsField("city_boundary", DataType.GEO_SHAPE, Map.of(), false, EsField.TimeSeriesFieldType.NONE) + ) ) ) ); @@ -431,7 +434,7 @@ private static EnrichResolution setupEnrichResolution() { EnrichPolicy.MATCH_TYPE, List.of("department"), Map.of("", ".enrich-departments-1", "cluster_1", ".enrich-departments-2"), - Map.of("department", new EsField("department", DataType.KEYWORD, Map.of(), true)) + Map.of("department", new EsField("department", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE)) ) ); enrichResolution.addResolvedPolicy( @@ -442,7 +445,7 @@ private static EnrichResolution setupEnrichResolution() { EnrichPolicy.MATCH_TYPE, List.of("department"), Map.of("", ".enrich-departments-3"), - Map.of("department", new EsField("department", DataType.KEYWORD, Map.of(), true)) + Map.of("department", new EsField("department", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE)) ) ); enrichResolution.addResolvedPolicy( @@ -453,7 +456,7 @@ private static EnrichResolution setupEnrichResolution() { EnrichPolicy.MATCH_TYPE, List.of("department"), Map.of("cluster_1", ".enrich-departments-2"), - Map.of("department", new EsField("department", DataType.KEYWORD, Map.of(), true)) + Map.of("department", new EsField("department", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE)) ) ); enrichResolution.addResolvedPolicy( @@ -464,7 +467,7 @@ private static EnrichResolution setupEnrichResolution() { EnrichPolicy.MATCH_TYPE, List.of("supervisor"), Map.of("", ".enrich-supervisors-a", "cluster_1", ".enrich-supervisors-b"), - Map.of("supervisor", new EsField("supervisor", DataType.KEYWORD, Map.of(), true)) + Map.of("supervisor", new EsField("supervisor", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE)) ) ); enrichResolution.addResolvedPolicy( @@ -475,7 +478,7 @@ private static EnrichResolution setupEnrichResolution() { EnrichPolicy.MATCH_TYPE, List.of("supervisor"), Map.of("", ".enrich-supervisors-c"), - Map.of("supervisor", new EsField("supervisor", DataType.KEYWORD, Map.of(), true)) + Map.of("supervisor", new EsField("supervisor", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE)) ) ); enrichResolution.addResolvedPolicy( @@ -486,7 +489,7 @@ private static EnrichResolution setupEnrichResolution() { EnrichPolicy.MATCH_TYPE, List.of("supervisor"), Map.of("cluster_1", ".enrich-supervisors-b"), - Map.of("supervisor", new EsField("supervisor", DataType.KEYWORD, Map.of(), true)) + Map.of("supervisor", new EsField("supervisor", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE)) ) ); return enrichResolution; @@ -2990,9 +2993,9 @@ public void testProjectAwayColumns() { "test", Map.of( "some_field1", - new EsField("some_field1", DataType.KEYWORD, Map.of(), true), + new EsField("some_field1", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE), "some_field2", - new EsField("some_field2", DataType.KEYWORD, Map.of(), true) + new EsField("some_field2", DataType.KEYWORD, Map.of(), true, EsField.TimeSeriesFieldType.NONE) ) ), IndexMode.STANDARD @@ -7845,7 +7848,7 @@ private Map fields(Collection fieldNames) { Map fields = new HashMap<>(); for (String fieldName : fieldNames) { - fields.put(fieldName, new EsField(fieldName, DataType.KEYWORD, Map.of(), false)); + fields.put(fieldName, new EsField(fieldName, DataType.KEYWORD, Map.of(), false, EsField.TimeSeriesFieldType.NONE)); } return fields; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushTopNToSourceTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushTopNToSourceTests.java index 00f8dfb9aaacc..15fc4d75f4f13 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushTopNToSourceTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushTopNToSourceTests.java @@ -505,7 +505,10 @@ private static void addSortableFieldAttributes(Map field } private static void addFieldAttribute(Map fields, String name, DataType type) { - fields.put(name, new FieldAttribute(Source.EMPTY, name, new EsField(name, type, new HashMap<>(), true))); + fields.put( + name, + new FieldAttribute(Source.EMPTY, name, new EsField(name, type, new HashMap<>(), true, EsField.TimeSeriesFieldType.NONE)) + ); } static TestPhysicalPlanBuilder from(String index) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/EvalMapperTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/EvalMapperTests.java index 3ea56d6d15abd..21bbedd0313f6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/EvalMapperTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/EvalMapperTests.java @@ -161,7 +161,11 @@ public void testExpressionSerialization() { } private static FieldAttribute field(String name, DataType type) { - return new FieldAttribute(Source.EMPTY, name, new EsField(name, type, Collections.emptyMap(), false)); + return new FieldAttribute( + Source.EMPTY, + name, + new EsField(name, type, Collections.emptyMap(), false, EsField.TimeSeriesFieldType.NONE) + ); } static DriverContext driverContext() { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlannerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlannerTests.java index 6749f03bedde7..380f153aa1962 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlannerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlannerTests.java @@ -149,7 +149,11 @@ public void testLuceneSourceOperatorHugeRowSize() throws IOException { public void testLuceneTopNSourceOperator() throws IOException { int estimatedRowSize = randomEstimatedRowSize(estimatedRowSizeIsHuge); - FieldAttribute sortField = new FieldAttribute(Source.EMPTY, "field", new EsField("field", DataType.INTEGER, Map.of(), true)); + FieldAttribute sortField = new FieldAttribute( + Source.EMPTY, + "field", + new EsField("field", DataType.INTEGER, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ); EsQueryExec.FieldSort sort = new EsQueryExec.FieldSort(sortField, Order.OrderDirection.ASC, Order.NullsPosition.LAST); Literal limit = new Literal(Source.EMPTY, 10, DataType.INTEGER); LocalExecutionPlanner.LocalExecutionPlan plan = planner().plan( @@ -176,7 +180,11 @@ public void testLuceneTopNSourceOperator() throws IOException { public void testLuceneTopNSourceOperatorDistanceSort() throws IOException { int estimatedRowSize = randomEstimatedRowSize(estimatedRowSizeIsHuge); - FieldAttribute sortField = new FieldAttribute(Source.EMPTY, "point", new EsField("point", DataType.GEO_POINT, Map.of(), true)); + FieldAttribute sortField = new FieldAttribute( + Source.EMPTY, + "point", + new EsField("point", DataType.GEO_POINT, Map.of(), true, EsField.TimeSeriesFieldType.NONE) + ); EsQueryExec.GeoDistanceSort sort = new EsQueryExec.GeoDistanceSort(sortField, Order.OrderDirection.ASC, 1, -1); Literal limit = new Literal(Source.EMPTY, 10, DataType.INTEGER); LocalExecutionPlanner.LocalExecutionPlan plan = planner().plan( diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/querydsl/query/MatchQueryTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/querydsl/query/MatchQueryTests.java index bf3f3baa0b634..2df727f9bc000 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/querydsl/query/MatchQueryTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/querydsl/query/MatchQueryTests.java @@ -65,14 +65,14 @@ public void testQueryBuilding() { private static MatchQueryBuilder getBuilder(Map options) { final Source source = new Source(1, 1, StringUtils.EMPTY); - FieldAttribute fa = new FieldAttribute(EMPTY, "a", new EsField("af", KEYWORD, emptyMap(), true)); + FieldAttribute fa = new FieldAttribute(EMPTY, "a", new EsField("af", KEYWORD, emptyMap(), true, EsField.TimeSeriesFieldType.NONE)); final MatchQuery mmq = new MatchQuery(source, "eggplant", "foo", options); return (MatchQueryBuilder) mmq.asBuilder(); } public void testToString() { final Source source = new Source(1, 1, StringUtils.EMPTY); - FieldAttribute fa = new FieldAttribute(EMPTY, "a", new EsField("af", KEYWORD, emptyMap(), true)); + FieldAttribute fa = new FieldAttribute(EMPTY, "a", new EsField("af", KEYWORD, emptyMap(), true, EsField.TimeSeriesFieldType.NONE)); final MatchQuery mmq = new MatchQuery(source, "eggplant", "foo"); assertEquals("MatchQuery@1:2[eggplant:foo]", mmq.toString()); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/tree/EsqlNodeSubclassTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/tree/EsqlNodeSubclassTests.java index 716ae56d85d7a..e24e6adc2becb 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/tree/EsqlNodeSubclassTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/tree/EsqlNodeSubclassTests.java @@ -733,7 +733,11 @@ static EsRelation randomEsRelation() { } static FieldAttribute field(String name, DataType type) { - return new FieldAttribute(Source.EMPTY, name, new EsField(name, type, Collections.emptyMap(), false)); + return new FieldAttribute( + Source.EMPTY, + name, + new EsField(name, type, Collections.emptyMap(), false, EsField.TimeSeriesFieldType.NONE) + ); } public static Set> subclassesOf(Class clazz) throws IOException { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/MultiTypeEsFieldTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/MultiTypeEsFieldTests.java index 154605a199d22..d8ff95e41807e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/MultiTypeEsFieldTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/MultiTypeEsFieldTests.java @@ -169,6 +169,6 @@ private static Expression testConvertExpression(String name, DataType fromType, } private static FieldAttribute fieldAttribute(String name, DataType dataType) { - return new FieldAttribute(Source.EMPTY, name, new EsField(name, dataType, Map.of(), true)); + return new FieldAttribute(Source.EMPTY, name, new EsField(name, dataType, Map.of(), true, EsField.TimeSeriesFieldType.NONE)); } } From cf4873343909a8b1ca6a89ecdc5d8519ea814c1f Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Wed, 16 Jul 2025 15:43:45 -0400 Subject: [PATCH 09/16] fix the other constructor --- .../org/elasticsearch/xpack/esql/core/type/EsField.java | 4 ---- .../org/elasticsearch/xpack/esql/analysis/Analyzer.java | 2 +- .../xpack/esql/enrich/EnrichPolicyResolver.java | 3 ++- .../xpack/esql/enrich/ResolvedEnrichPolicy.java | 9 ++++++++- .../elasticsearch/xpack/esql/session/IndexResolver.java | 2 +- .../esql/optimizer/LocalLogicalPlanOptimizerTests.java | 2 +- .../org/elasticsearch/xpack/esql/type/EsFieldTests.java | 9 ++++++--- 7 files changed, 19 insertions(+), 12 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java index 57baad61f0437..7178fa46290c5 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java @@ -138,10 +138,6 @@ public EsField( this(name, esDataType, properties, aggregatable, false, timeSeriesFieldType); } - public EsField(String name, DataType esDataType, Map properties, boolean aggregatable, boolean isAlias) { - this(name, esDataType, properties, aggregatable, isAlias, TimeSeriesFieldType.UNKNOWN); - } - public EsField( String name, DataType esDataType, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index b53ae17871617..fb6d799bf137b 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -464,7 +464,7 @@ private LocalRelation tableMapAsRelation(Source source, Map mapT String name = entry.getKey(); Column column = entry.getValue(); // create a fake ES field - alternative is to use a ReferenceAttribute - EsField field = new EsField(name, column.type(), Map.of(), false, false); + EsField field = new EsField(name, column.type(), Map.of(), false, false, EsField.TimeSeriesFieldType.UNKNOWN); attributes.add(new FieldAttribute(source, null, name, field)); // prepare the block for the supplier blocks[i++] = column.values(); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolver.java index d0a0843a234b0..c922e5d91d844 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolver.java @@ -238,7 +238,8 @@ private Tuple mergeLookupResults( DataType.fromTypeName(field.getDataType().typeName()), field.getProperties(), field.isAggregatable(), - field.isAlias() + field.isAlias(), + field.getTimeSeriesFieldType() ); EsField old = mappings.putIfAbsent(m.getKey(), field); if (old != null && old.getDataType().equals(field.getDataType()) == false) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/ResolvedEnrichPolicy.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/ResolvedEnrichPolicy.java index 64595e776a96e..a42881a13835d 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/ResolvedEnrichPolicy.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/ResolvedEnrichPolicy.java @@ -54,7 +54,14 @@ public void writeTo(StreamOutput out) throws IOException { * as though it were the base class. */ (o, v) -> { - var field = new EsField(v.getName(), v.getDataType(), v.getProperties(), v.isAggregatable(), v.isAlias()); + var field = new EsField( + v.getName(), + v.getDataType(), + v.getProperties(), + v.isAggregatable(), + v.isAlias(), + v.getTimeSeriesFieldType() + ); if (out.getTransportVersion().onOrAfter(TransportVersions.V_8_15_2)) { field.writeTo(o); } else { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java index 1ee6ff2c6747c..3f675240c3ba0 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java @@ -234,7 +234,7 @@ private static EsField createField( return unsupported(name, first); } - return new EsField(name, type, new HashMap<>(), aggregatable, isAlias); + return new EsField(name, type, new HashMap<>(), aggregatable, isAlias, timeSeriesFieldType); } private static UnsupportedEsField unsupported(String name, IndexFieldCapabilities fc) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java index c6bb95bc8e93c..6157530bf5ee8 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java @@ -498,7 +498,7 @@ public void testSparseDocument() throws Exception { Map large = Maps.newLinkedHashMapWithExpectedSize(size); for (int i = 0; i < size; i++) { var name = String.format(Locale.ROOT, "field%03d", i); - large.put(name, new EsField(name, DataType.INTEGER, emptyMap(), true, false)); + large.put(name, new EsField(name, DataType.INTEGER, emptyMap(), true, false, EsField.TimeSeriesFieldType.NONE)); } SearchStats searchStats = statsForExistingField("field000", "field001", "field002", "field003", "field004"); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsFieldTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsFieldTests.java index 18e0405a65892..c29cbde8a7877 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsFieldTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsFieldTests.java @@ -19,7 +19,8 @@ public static EsField randomEsField(int maxPropertiesDepth) { Map properties = randomProperties(maxPropertiesDepth); boolean aggregatable = randomBoolean(); boolean isAlias = randomBoolean(); - return new EsField(name, esDataType, properties, aggregatable, isAlias); + EsField.TimeSeriesFieldType tsType = randomFrom(EsField.TimeSeriesFieldType.values()); + return new EsField(name, esDataType, properties, aggregatable, isAlias, tsType); } @Override @@ -34,14 +35,16 @@ protected EsField mutate(EsField instance) { Map properties = instance.getProperties(); boolean aggregatable = instance.isAggregatable(); boolean isAlias = instance.isAlias(); - switch (between(0, 4)) { + EsField.TimeSeriesFieldType tsType = instance.getTimeSeriesFieldType(); + switch (between(0, 5)) { case 0 -> name = randomAlphaOfLength(name.length() + 1); case 1 -> esDataType = randomValueOtherThan(esDataType, () -> randomFrom(DataType.types())); case 2 -> properties = randomValueOtherThan(properties, () -> randomProperties(4)); case 3 -> aggregatable = false == aggregatable; case 4 -> isAlias = false == isAlias; + case 5 -> tsType = randomValueOtherThan(tsType, () -> randomFrom(EsField.TimeSeriesFieldType.values())); default -> throw new IllegalArgumentException(); } - return new EsField(name, esDataType, properties, aggregatable, isAlias); + return new EsField(name, esDataType, properties, aggregatable, isAlias, tsType); } } From f246efa5bf8adc5bdaa61719784cabd42907dbb2 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Wed, 16 Jul 2025 17:25:32 -0400 Subject: [PATCH 10/16] multi-type fields --- .../elasticsearch/xpack/esql/core/type/EsField.java | 4 ---- .../xpack/esql/core/type/MultiTypeEsField.java | 13 +++++++------ .../elasticsearch/xpack/esql/analysis/Analyzer.java | 3 ++- .../xpack/esql/type/MultiTypeEsFieldTests.java | 9 ++++++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java index 7178fa46290c5..bdc96ee71f02c 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java @@ -124,10 +124,6 @@ public static Reader getReader(String name) { private final boolean isAlias; private final TimeSeriesFieldType timeSeriesFieldType; - public EsField(String name, DataType esDataType, Map properties, boolean aggregatable) { - this(name, esDataType, properties, aggregatable, false, TimeSeriesFieldType.UNKNOWN); - } - public EsField( String name, DataType esDataType, diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/MultiTypeEsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/MultiTypeEsField.java index 8056580b13431..b26f1f060afc4 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/MultiTypeEsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/MultiTypeEsField.java @@ -33,11 +33,6 @@ public class MultiTypeEsField extends EsField { private final Map indexToConversionExpressions; - public MultiTypeEsField(String name, DataType dataType, boolean aggregatable, Map indexToConversionExpressions) { - super(name, dataType, Map.of(), aggregatable); - this.indexToConversionExpressions = indexToConversionExpressions; - } - public MultiTypeEsField( String name, DataType dataType, @@ -99,7 +94,13 @@ public static MultiTypeEsField resolveFrom( indexToConversionExpressions.put(indexName, convertExpr); } } - return new MultiTypeEsField(invalidMappedField.getName(), resolvedDataType, false, indexToConversionExpressions); + return new MultiTypeEsField( + invalidMappedField.getName(), + resolvedDataType, + false, + indexToConversionExpressions, + invalidMappedField.getTimeSeriesFieldType() + ); } @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index fb6d799bf137b..59cb54f950252 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -1768,7 +1768,8 @@ private Expression resolveConvertFunction(ConvertFunction convert, List indexToConvertExpressions = randomConvertExpressions(name, toString, dataType); - return new MultiTypeEsField(name, toType, false, indexToConvertExpressions); + EsField.TimeSeriesFieldType tsType = randomFrom(EsField.TimeSeriesFieldType.values()); + return new MultiTypeEsField(name, toType, false, indexToConvertExpressions, tsType); } @Override @@ -80,13 +81,15 @@ protected MultiTypeEsField mutateInstance(MultiTypeEsField instance) throws IOEx String name = instance.getName(); DataType dataType = instance.getDataType(); Map indexToConvertExpressions = instance.getIndexToConversionExpressions(); - switch (between(0, 2)) { + EsField.TimeSeriesFieldType tsType = instance.getTimeSeriesFieldType(); + switch (between(0, 3)) { case 0 -> name = randomAlphaOfLength(name.length() + 1); case 1 -> dataType = randomValueOtherThan(dataType, () -> randomFrom(DataType.types())); case 2 -> indexToConvertExpressions = mutateConvertExpressions(name, dataType, indexToConvertExpressions); + case 3 -> tsType = randomValueOtherThan(tsType, () -> randomFrom(EsField.TimeSeriesFieldType.values())); default -> throw new IllegalArgumentException(); } - return new MultiTypeEsField(name, dataType, false, indexToConvertExpressions); + return new MultiTypeEsField(name, dataType, false, indexToConvertExpressions, tsType); } @Override From 370353364e433de2c317999bafb97a4fdf01f757 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Thu, 17 Jul 2025 09:48:20 -0400 Subject: [PATCH 11/16] text-field subclass --- .../org/elasticsearch/xpack/esql/core/type/EsField.java | 1 + .../elasticsearch/xpack/esql/core/type/TextEsField.java | 8 -------- .../java/org/elasticsearch/xpack/esql/LoadMapping.java | 2 +- .../elasticsearch/xpack/esql/type/TextEsFieldTests.java | 9 ++++++--- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java index bdc96ee71f02c..234a56abdb39f 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java @@ -87,6 +87,7 @@ public static TimeSeriesFieldType readFromStream(StreamInput in) throws IOExcept public static TimeSeriesFieldType fromIndexFieldCapabilities(IndexFieldCapabilities capabilities) { if (capabilities.isDimension()) { + assert capabilities.metricType() == null; return DIMENSION; } if (capabilities.metricType() != null) { diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/TextEsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/TextEsField.java index 7c9508959dcae..bb05dbd4f9030 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/TextEsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/TextEsField.java @@ -25,14 +25,6 @@ */ public class TextEsField extends EsField { - public TextEsField(String name, Map properties, boolean hasDocValues) { - this(name, properties, hasDocValues, false); - } - - public TextEsField(String name, Map properties, boolean hasDocValues, boolean isAlias) { - super(name, TEXT, properties, hasDocValues, isAlias, TimeSeriesFieldType.UNKNOWN); - } - public TextEsField( String name, Map properties, diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java index 2e59b889042c8..4d967aaba0010 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java @@ -102,7 +102,7 @@ private static void walkMapping(String name, Object value, Map boolean docValues = boolSetting(content.get("doc_values"), esDataType.hasDocValues()); final EsField field; if (esDataType == TEXT) { - field = new TextEsField(name, properties, docValues); + field = new TextEsField(name, properties, docValues, false, EsField.TimeSeriesFieldType.NONE); } else if (esDataType == KEYWORD) { int length = intSetting(content.get("ignore_above"), Short.MAX_VALUE); boolean normalized = Strings.hasText(textSetting(content.get("normalizer"), null)); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/TextEsFieldTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/TextEsFieldTests.java index 9af3b7376f2b2..cc2270d94ddfd 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/TextEsFieldTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/TextEsFieldTests.java @@ -18,7 +18,8 @@ static TextEsField randomTextEsField(int maxPropertiesDepth) { Map properties = randomProperties(maxPropertiesDepth); boolean hasDocValues = randomBoolean(); boolean isAlias = randomBoolean(); - return new TextEsField(name, properties, hasDocValues, isAlias); + EsField.TimeSeriesFieldType tsType = randomFrom(EsField.TimeSeriesFieldType.values()); + return new TextEsField(name, properties, hasDocValues, isAlias, tsType); } @Override @@ -32,13 +33,15 @@ protected TextEsField mutate(TextEsField instance) { Map properties = instance.getProperties(); boolean hasDocValues = instance.isAggregatable(); boolean isAlias = instance.isAlias(); - switch (between(0, 3)) { + EsField.TimeSeriesFieldType tsType = instance.getTimeSeriesFieldType(); + switch (between(0, 4)) { case 0 -> name = randomAlphaOfLength(name.length() + 1); case 1 -> properties = randomValueOtherThan(properties, () -> randomProperties(4)); case 2 -> hasDocValues = false == hasDocValues; case 3 -> isAlias = false == isAlias; + case 4 -> tsType = randomValueOtherThan(tsType, () -> randomFrom(EsField.TimeSeriesFieldType.values())); default -> throw new IllegalArgumentException(); } - return new TextEsField(name, properties, hasDocValues, isAlias); + return new TextEsField(name, properties, hasDocValues, isAlias, tsType); } } From 47df3b4ca1b72e877f199e0b425eb065c531d290 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Thu, 17 Jul 2025 10:42:17 -0400 Subject: [PATCH 12/16] keyword fields --- .../xpack/esql/core/type/KeywordEsField.java | 14 +++----------- .../type/PotentiallyUnmappedKeywordEsField.java | 3 ++- .../org/elasticsearch/xpack/esql/LoadMapping.java | 2 +- .../xpack/esql/session/IndexResolver.java | 2 +- .../xpack/esql/type/KeywordEsFieldTests.java | 9 ++++++--- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/KeywordEsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/KeywordEsField.java index e56aafa267400..85a348a7a87d6 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/KeywordEsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/KeywordEsField.java @@ -10,7 +10,6 @@ import org.elasticsearch.common.io.stream.StreamOutput; import java.io.IOException; -import java.util.Collections; import java.util.Map; import java.util.Objects; @@ -26,23 +25,16 @@ public class KeywordEsField extends EsField { private final int precision; private final boolean normalized; - public KeywordEsField(String name) { - this(name, Collections.emptyMap(), true, Short.MAX_VALUE, false); - } - - public KeywordEsField(String name, Map properties, boolean hasDocValues, int precision, boolean normalized) { - this(name, properties, hasDocValues, precision, normalized, false); - } - public KeywordEsField( String name, Map properties, boolean hasDocValues, int precision, boolean normalized, - boolean isAlias + boolean isAlias, + TimeSeriesFieldType timeSeriesFieldType ) { - this(name, KEYWORD, properties, hasDocValues, precision, normalized, isAlias, TimeSeriesFieldType.UNKNOWN); + this(name, KEYWORD, properties, hasDocValues, precision, normalized, isAlias, timeSeriesFieldType); } protected KeywordEsField( diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/PotentiallyUnmappedKeywordEsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/PotentiallyUnmappedKeywordEsField.java index 8672b6b61dee7..679e77ad2c636 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/PotentiallyUnmappedKeywordEsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/PotentiallyUnmappedKeywordEsField.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import java.io.IOException; +import java.util.Collections; /** * This class is used as a marker for fields that may be unmapped, where an unmapped field is a field which exists in the _source but is not @@ -17,7 +18,7 @@ */ public class PotentiallyUnmappedKeywordEsField extends KeywordEsField { public PotentiallyUnmappedKeywordEsField(String name) { - super(name); + super(name, Collections.emptyMap(), true, Short.MAX_VALUE, false, false, TimeSeriesFieldType.UNKNOWN); } public PotentiallyUnmappedKeywordEsField(StreamInput in) throws IOException { diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java index 4d967aaba0010..e5e80e5c405be 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java @@ -106,7 +106,7 @@ private static void walkMapping(String name, Object value, Map } else if (esDataType == KEYWORD) { int length = intSetting(content.get("ignore_above"), Short.MAX_VALUE); boolean normalized = Strings.hasText(textSetting(content.get("normalizer"), null)); - field = new KeywordEsField(name, properties, docValues, length, normalized); + field = new KeywordEsField(name, properties, docValues, length, normalized, false, EsField.TimeSeriesFieldType.NONE); } else if (esDataType == DATETIME) { field = DateEsField.dateEsField(name, properties, docValues); } else if (esDataType == UNSUPPORTED) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java index 3f675240c3ba0..62a83e74f279e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java @@ -225,7 +225,7 @@ private static EsField createField( int length = Short.MAX_VALUE; // TODO: to check whether isSearchable/isAggregateable takes into account the presence of the normalizer boolean normalized = false; - return new KeywordEsField(name, new HashMap<>(), aggregatable, length, normalized, isAlias); + return new KeywordEsField(name, new HashMap<>(), aggregatable, length, normalized, isAlias, timeSeriesFieldType); } if (type == DATETIME) { return DateEsField.dateEsField(name, new HashMap<>(), aggregatable); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/KeywordEsFieldTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/KeywordEsFieldTests.java index ef04f0e27c096..9441b2e896f10 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/KeywordEsFieldTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/KeywordEsFieldTests.java @@ -21,7 +21,8 @@ static KeywordEsField randomKeywordEsField(int maxPropertiesDepth) { int precision = randomInt(); boolean normalized = randomBoolean(); boolean isAlias = randomBoolean(); - return new KeywordEsField(name, properties, hasDocValues, precision, normalized, isAlias); + EsField.TimeSeriesFieldType tsType = randomFrom(EsField.TimeSeriesFieldType.values()); + return new KeywordEsField(name, properties, hasDocValues, precision, normalized, isAlias, tsType); } @Override @@ -37,15 +38,17 @@ protected KeywordEsField mutate(KeywordEsField instance) { int precision = instance.getPrecision(); boolean normalized = instance.getNormalized(); boolean isAlias = instance.isAlias(); - switch (between(0, 5)) { + EsField.TimeSeriesFieldType tsType = instance.getTimeSeriesFieldType(); + switch (between(0, 6)) { case 0 -> name = randomAlphaOfLength(name.length() + 1); case 1 -> properties = randomValueOtherThan(properties, () -> randomProperties(4)); case 2 -> hasDocValues = false == hasDocValues; case 3 -> precision = randomValueOtherThan(precision, ESTestCase::randomInt); case 4 -> normalized = false == normalized; case 5 -> isAlias = false == isAlias; + case 6 -> tsType = randomValueOtherThan(tsType, () -> randomFrom(EsField.TimeSeriesFieldType.values())); default -> throw new IllegalArgumentException(); } - return new KeywordEsField(name, properties, hasDocValues, precision, normalized, isAlias); + return new KeywordEsField(name, properties, hasDocValues, precision, normalized, isAlias, tsType); } } From 5250dceb792786c39d7041cb9bf5d94e92e9502e Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Thu, 17 Jul 2025 11:01:50 -0400 Subject: [PATCH 13/16] date fields --- .../xpack/esql/core/type/DateEsField.java | 4 ++-- .../org/elasticsearch/xpack/esql/LoadMapping.java | 2 +- .../xpack/esql/session/IndexResolver.java | 2 +- .../xpack/esql/type/DateEsFieldTests.java | 13 ++++++++++--- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DateEsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DateEsField.java index bd66519f2f5e2..7eab34f01a1c2 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DateEsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DateEsField.java @@ -20,8 +20,8 @@ */ public class DateEsField extends EsField { - public static DateEsField dateEsField(String name, Map properties, boolean hasDocValues) { - return new DateEsField(name, DataType.DATETIME, properties, hasDocValues, TimeSeriesFieldType.UNKNOWN); + public static DateEsField dateEsField(String name, Map properties, boolean hasDocValues, TimeSeriesFieldType tsType) { + return new DateEsField(name, DataType.DATETIME, properties, hasDocValues, tsType); } private DateEsField( diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java index e5e80e5c405be..29f2f6ed4e927 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/LoadMapping.java @@ -108,7 +108,7 @@ private static void walkMapping(String name, Object value, Map boolean normalized = Strings.hasText(textSetting(content.get("normalizer"), null)); field = new KeywordEsField(name, properties, docValues, length, normalized, false, EsField.TimeSeriesFieldType.NONE); } else if (esDataType == DATETIME) { - field = DateEsField.dateEsField(name, properties, docValues); + field = DateEsField.dateEsField(name, properties, docValues, EsField.TimeSeriesFieldType.NONE); } else if (esDataType == UNSUPPORTED) { String type = content.get("type").toString(); field = new UnsupportedEsField(name, List.of(type), null, properties); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java index 62a83e74f279e..67058e37d596f 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java @@ -228,7 +228,7 @@ private static EsField createField( return new KeywordEsField(name, new HashMap<>(), aggregatable, length, normalized, isAlias, timeSeriesFieldType); } if (type == DATETIME) { - return DateEsField.dateEsField(name, new HashMap<>(), aggregatable); + return DateEsField.dateEsField(name, new HashMap<>(), aggregatable, timeSeriesFieldType); } if (type == UNSUPPORTED) { return unsupported(name, first); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/DateEsFieldTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/DateEsFieldTests.java index bf0494d5fd043..451cf879fa2dd 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/DateEsFieldTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/DateEsFieldTests.java @@ -14,7 +14,12 @@ public class DateEsFieldTests extends AbstractEsFieldTypeTests { static DateEsField randomDateEsField(int maxPropertiesDepth) { - return DateEsField.dateEsField(randomAlphaOfLength(5), randomProperties(maxPropertiesDepth), randomBoolean()); + return DateEsField.dateEsField( + randomAlphaOfLength(5), + randomProperties(maxPropertiesDepth), + randomBoolean(), + randomFrom(EsField.TimeSeriesFieldType.values()) + ); } @Override @@ -27,12 +32,14 @@ protected DateEsField mutate(DateEsField instance) { String name = instance.getName(); Map properties = instance.getProperties(); boolean aggregatable = instance.isAggregatable(); - switch (between(0, 2)) { + EsField.TimeSeriesFieldType tsType = instance.getTimeSeriesFieldType(); + switch (between(0, 3)) { case 0 -> name = randomAlphaOfLength(name.length() + 1); case 1 -> properties = randomValueOtherThan(properties, () -> randomProperties(4)); case 2 -> aggregatable = false == aggregatable; + case 3 -> tsType = randomFrom(EsField.TimeSeriesFieldType.values()); default -> throw new IllegalArgumentException(); } - return DateEsField.dateEsField(name, properties, aggregatable); + return DateEsField.dateEsField(name, properties, aggregatable, tsType); } } From c7dfe134b4c280cb3313e71b376675e1c18eee2d Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Thu, 17 Jul 2025 11:47:50 -0400 Subject: [PATCH 14/16] equals and hashcode --- .../java/org/elasticsearch/xpack/esql/core/type/EsField.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java index 234a56abdb39f..87dadaed4da91 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java @@ -300,12 +300,13 @@ public boolean equals(Object o) { && isAlias == field.isAlias && esDataType == field.esDataType && Objects.equals(name, field.name) - && Objects.equals(properties, field.properties); + && Objects.equals(properties, field.properties) + && Objects.equals(timeSeriesFieldType, field.timeSeriesFieldType); } @Override public int hashCode() { - return Objects.hash(esDataType, aggregatable, properties, name, isAlias); + return Objects.hash(esDataType, aggregatable, properties, name, isAlias, timeSeriesFieldType); } public static final class Exact { From ec5dfd477fa6d4eab3597a9e712a5739559fb0d0 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Thu, 17 Jul 2025 12:53:12 -0400 Subject: [PATCH 15/16] fix a couple of tests --- .../xpack/esql/core/expression/FieldAttribute.java | 2 +- .../org/elasticsearch/xpack/esql/type/DateEsFieldTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java index 23ae78b567c78..e9cca5c79c4bf 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java @@ -218,7 +218,7 @@ protected String label() { @Override public boolean isDimension() { - return false; + return field.getTimeSeriesFieldType() == EsField.TimeSeriesFieldType.DIMENSION; } public EsField field() { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/DateEsFieldTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/DateEsFieldTests.java index 451cf879fa2dd..a7cf5b499e7ee 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/DateEsFieldTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/DateEsFieldTests.java @@ -37,7 +37,7 @@ protected DateEsField mutate(DateEsField instance) { case 0 -> name = randomAlphaOfLength(name.length() + 1); case 1 -> properties = randomValueOtherThan(properties, () -> randomProperties(4)); case 2 -> aggregatable = false == aggregatable; - case 3 -> tsType = randomFrom(EsField.TimeSeriesFieldType.values()); + case 3 -> tsType = randomValueOtherThan(tsType, () -> randomFrom(EsField.TimeSeriesFieldType.values())); default -> throw new IllegalArgumentException(); } return DateEsField.dateEsField(name, properties, aggregatable, tsType); From 2f638e7211d16d32e039a377de09ba4c2cdb7499 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Fri, 18 Jul 2025 16:38:02 -0400 Subject: [PATCH 16/16] Remove unnecessary check --- .../org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java index 65587cf4d6876..b5a10f8957ae4 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java @@ -165,7 +165,6 @@ public void testFallbackIndicesOptions() throws Exception { assertThat(cloned.clusterAlias(), equalTo(request.clusterAlias())); assertThat(cloned.sessionId(), equalTo(request.sessionId())); RemoteClusterPlan plan = cloned.remoteClusterPlan(); - assertThat(plan.plan(), equalTo(request.remoteClusterPlan().plan())); assertThat(plan.targetIndices(), equalTo(request.remoteClusterPlan().targetIndices())); OriginalIndices originalIndices = plan.originalIndices(); assertThat(originalIndices.indices(), equalTo(request.remoteClusterPlan().originalIndices().indices()));