From c2b417a16a56bb144faf8975660e977adc8865f5 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Tue, 29 Aug 2023 16:36:18 -0400 Subject: [PATCH 1/5] ESQL: Mark counter fields as unsupported This marks TSDB's `counter` fields as unsupported by ESQL. We'll support them eventually, but in the short term we were just treating them like their underlying numeric type and that's not great. They have a conceptually different meaning that we'd like to respect one day. So, for now, we'll mark them unsupported. --- .../resources/rest-api-spec/test/40_tsdb.yml | 20 +++- .../test/45_non_tsdb_counter.yml | 112 ++++++++++++++++++ .../xpack/esql/action/EsqlQueryResponse.java | 2 +- .../xpack/esql/type/EsqlDataTypeRegistry.java | 10 +- .../xpack/esql/type/EsqlDataTypes.java | 2 +- .../esql/action/EsqlQueryResponseTests.java | 2 +- .../xpack/ql/index/IndexResolver.java | 20 +++- .../xpack/ql/type/DataTypeRegistry.java | 4 +- .../ql/type/DefaultDataTypeRegistry.java | 4 +- .../elasticsearch/xpack/ql/type/Types.java | 4 +- .../xpack/sql/type/SqlDataTypeRegistry.java | 3 +- 11 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/45_non_tsdb_counter.yml diff --git a/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/40_tsdb.yml b/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/40_tsdb.yml index f37c897d77b4b..a72205b3af064 100644 --- a/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/40_tsdb.yml +++ b/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/40_tsdb.yml @@ -32,8 +32,10 @@ setup: properties: tx: type: long + time_series_metric: counter rx: type: long + time_series_metric: counter - do: bulk: refresh: true @@ -70,9 +72,9 @@ load everything: - match: {columns.2.name: "k8s.pod.name"} - match: {columns.2.type: "keyword"} - match: {columns.3.name: "k8s.pod.network.rx"} - - match: {columns.3.type: "long"} + - match: {columns.3.type: "unsupported"} - match: {columns.4.name: "k8s.pod.network.tx"} - - match: {columns.4.type: "long"} + - match: {columns.4.type: "unsupported"} - match: {columns.5.name: "k8s.pod.uid"} - match: {columns.5.type: "keyword"} - match: {columns.6.name: "metricset"} @@ -84,14 +86,22 @@ load a document: - do: esql.query: body: - query: 'from test | where k8s.pod.network.tx == 1434577921' + query: 'from test | where @timestamp == "2021-04-28T18:50:23.142Z"' - length: {values: 1} - length: {values.0: 7} - match: {values.0.0: "2021-04-28T18:50:23.142Z"} - match: {values.0.1: "10.10.55.3"} - match: {values.0.2: "dog"} - - match: {values.0.3: 530600088} - - match: {values.0.4: 1434577921} + - match: {values.0.3: ""} + - match: {values.0.4: ""} - match: {values.0.5: "df3145b3-0563-4d3b-a0f7-897eb2876ea9"} - match: {values.0.6: "pod"} + +--- +filter on counter: + - do: + catch: /Cannot use field \[k8s.pod.network.tx\] with unsupported type \[counter\]/ + esql.query: + body: + query: 'from test | where k8s.pod.network.tx == 1434577921' diff --git a/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/45_non_tsdb_counter.yml b/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/45_non_tsdb_counter.yml new file mode 100644 index 0000000000000..a4344946aea0d --- /dev/null +++ b/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/45_non_tsdb_counter.yml @@ -0,0 +1,112 @@ +setup: + - do: + indices.create: + index: test + body: + settings: + index: + mode: standard + mappings: + properties: + "@timestamp": + type: date + metricset: + type: keyword + time_series_dimension: true + k8s: + properties: + pod: + properties: + uid: + type: keyword + time_series_dimension: true + name: + type: keyword + ip: + type: ip + network: + properties: + tx: + type: long + time_series_metric: counter + rx: + type: long + time_series_metric: counter + - do: + bulk: + refresh: true + index: test + body: + - '{"index": {}}' + - '{"@timestamp": "2021-04-28T18:50:04.467Z", "metricset": "pod", "k8s": {"pod": {"name": "cat", "uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "ip": "10.10.55.1", "network": {"tx": 2001818691, "rx": 802133794}}}}' + - '{"index": {}}' + - '{"@timestamp": "2021-04-28T18:50:24.467Z", "metricset": "pod", "k8s": {"pod": {"name": "cat", "uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "ip": "10.10.55.1", "network": {"tx": 2005177954, "rx": 801479970}}}}' + - '{"index": {}}' + - '{"@timestamp": "2021-04-28T18:50:44.467Z", "metricset": "pod", "k8s": {"pod": {"name": "cat", "uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "ip": "10.10.55.1", "network": {"tx": 2006223737, "rx": 802337279}}}}' + - '{"index": {}}' + - '{"@timestamp": "2021-04-28T18:51:04.467Z", "metricset": "pod", "k8s": {"pod": {"name": "cat", "uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "ip": "10.10.55.2", "network": {"tx": 2012916202, "rx": 803685721}}}}' + - '{"index": {}}' + - '{"@timestamp": "2021-04-28T18:50:03.142Z", "metricset": "pod", "k8s": {"pod": {"name": "dog", "uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "ip": "10.10.55.3", "network": {"tx": 1434521831, "rx": 530575198}}}}' + - '{"index": {}}' + - '{"@timestamp": "2021-04-28T18:50:23.142Z", "metricset": "pod", "k8s": {"pod": {"name": "dog", "uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "ip": "10.10.55.3", "network": {"tx": 1434577921, "rx": 530600088}}}}' + - '{"index": {}}' + - '{"@timestamp": "2021-04-28T18:50:53.142Z", "metricset": "pod", "k8s": {"pod": {"name": "dog", "uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "ip": "10.10.55.3", "network": {"tx": 1434587694, "rx": 530604797}}}}' + - '{"index": {}}' + - '{"@timestamp": "2021-04-28T18:51:03.142Z", "metricset": "pod", "k8s": {"pod": {"name": "dog", "uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "ip": "10.10.55.3", "network": {"tx": 1434595272, "rx": 530605511}}}}' + +--- +load everything: + - do: + esql.query: + body: + query: 'from test' + + - match: {columns.0.name: "@timestamp"} + - match: {columns.0.type: "date"} + - match: {columns.1.name: "k8s.pod.ip"} + - match: {columns.1.type: "ip"} + - match: {columns.2.name: "k8s.pod.name"} + - match: {columns.2.type: "keyword"} + - match: {columns.3.name: "k8s.pod.network.rx"} + - match: {columns.3.type: "long"} + - match: {columns.4.name: "k8s.pod.network.tx"} + - match: {columns.4.type: "long"} + - match: {columns.5.name: "k8s.pod.uid"} + - match: {columns.5.type: "keyword"} + - match: {columns.6.name: "metricset"} + - match: {columns.6.type: "keyword"} + - length: {values: 8} + +--- +load a document: + - do: + esql.query: + body: + query: 'from test | where @timestamp == "2021-04-28T18:50:23.142Z"' + + - length: {values: 1} + - length: {values.0: 7} + - match: {values.0.0: "2021-04-28T18:50:23.142Z"} + - match: {values.0.1: "10.10.55.3"} + - match: {values.0.2: "dog"} + - match: {values.0.3: 530600088} + - match: {values.0.4: 1434577921} + - match: {values.0.5: "df3145b3-0563-4d3b-a0f7-897eb2876ea9"} + - match: {values.0.6: "pod"} + +--- +filter on counter: + - do: + esql.query: + body: + query: 'from test | where k8s.pod.network.tx == 1434577921' + + - length: {values: 1} + - length: {values.0: 7} + - match: {values.0.0: "2021-04-28T18:50:23.142Z"} + - match: {values.0.1: "10.10.55.3"} + - match: {values.0.2: "dog"} + - match: {values.0.3: 530600088} + - match: {values.0.4: 1434577921} + - match: {values.0.5: "df3145b3-0563-4d3b-a0f7-897eb2876ea9"} + - match: {values.0.6: "pod"} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryResponse.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryResponse.java index 4bf884ddc18e2..36a83ce5d32a0 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryResponse.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryResponse.java @@ -247,7 +247,7 @@ private static Object valueAt(String dataType, Block block, int offset, BytesRef */ private static Page valuesToPage(List dataTypes, List> values) { List results = dataTypes.stream() - .map(c -> LocalExecutionPlanner.toElementType(EsqlDataTypes.fromEs(c)).newBlockBuilder(values.size())) + .map(c -> LocalExecutionPlanner.toElementType(EsqlDataTypes.fromName(c)).newBlockBuilder(values.size())) .toList(); for (List row : values) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistry.java index f2b67196ac11f..01dabd58e289f 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistry.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistry.java @@ -7,9 +7,11 @@ package org.elasticsearch.xpack.esql.type; +import org.elasticsearch.index.mapper.TimeSeriesParams; import org.elasticsearch.xpack.ql.type.DataType; import org.elasticsearch.xpack.ql.type.DataTypeConverter; import org.elasticsearch.xpack.ql.type.DataTypeRegistry; +import org.elasticsearch.xpack.ql.type.DataTypes; import java.util.Collection; @@ -25,8 +27,12 @@ public Collection dataTypes() { } @Override - public DataType fromEs(String typeName) { - return EsqlDataTypes.fromEs(typeName); + public DataType fromEs(String typeName, TimeSeriesParams.MetricType metricType) { + if (metricType == TimeSeriesParams.MetricType.COUNTER) { + // Counter fields will be a counter type, for now they are unsupported + return DataTypes.UNSUPPORTED; + } + return EsqlDataTypes.fromName(typeName); } @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypes.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypes.java index 982905ed56428..391aa9ab8ebf7 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypes.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypes.java @@ -87,7 +87,7 @@ public static DataType fromTypeName(String name) { return NAME_TO_TYPE.get(name.toLowerCase(Locale.ROOT)); } - public static DataType fromEs(String name) { + public static DataType fromName(String name) { DataType type = ES_TO_TYPE.get(name); return type != null ? type : UNSUPPORTED; } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryResponseTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryResponseTests.java index 07899000cba4a..5bf8df1c3fd0b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryResponseTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryResponseTests.java @@ -72,7 +72,7 @@ private ColumnInfo randomColumnInfo() { private Page randomPage(List columns) { return new Page(columns.stream().map(c -> { - Block.Builder builder = LocalExecutionPlanner.toElementType(EsqlDataTypes.fromEs(c.type())).newBlockBuilder(1); + Block.Builder builder = LocalExecutionPlanner.toElementType(EsqlDataTypes.fromName(c.type())).newBlockBuilder(1); switch (c.type()) { case "unsigned_long", "long" -> ((LongBlock.Builder) builder).appendLong(randomLong()); case "integer" -> ((IntBlock.Builder) builder).appendInt(randomInt()); diff --git a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/index/IndexResolver.java b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/index/IndexResolver.java index eb37b9256ad34..11ecc4d44a495 100644 --- a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/index/IndexResolver.java +++ b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/index/IndexResolver.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.util.Maps; import org.elasticsearch.core.Tuple; import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.index.mapper.TimeSeriesParams; import org.elasticsearch.transport.NoSuchRemoteClusterException; import org.elasticsearch.xpack.ql.QlIllegalArgumentException; import org.elasticsearch.xpack.ql.type.DataType; @@ -490,7 +491,7 @@ private static EsField createField( // lack of parent implies the field is an alias if (map == null) { // as such, create the field manually, marking the field to also be an alias - fieldFunction = s -> createField(typeRegistry, s, OBJECT.esType(), new TreeMap<>(), false, true); + fieldFunction = s -> createField(typeRegistry, s, OBJECT.esType(), null, new TreeMap<>(), false, true); } else { Iterator iterator = map.values().iterator(); FieldCapabilities parentCap = iterator.next(); @@ -498,7 +499,15 @@ private static EsField createField( parentCap = iterator.next(); } final FieldCapabilities parentC = parentCap; - fieldFunction = s -> createField(typeRegistry, s, parentC.getType(), new TreeMap<>(), parentC.isAggregatable(), false); + fieldFunction = s -> createField( + typeRegistry, + s, + parentC.getType(), + parentC.getMetricType(), + new TreeMap<>(), + parentC.isAggregatable(), + false + ); } parent = createField(typeRegistry, parentName, globalCaps, hierarchicalMapping, flattedMapping, fieldFunction); @@ -532,11 +541,12 @@ private static EsField createField( DataTypeRegistry typeRegistry, String fieldName, String typeName, + TimeSeriesParams.MetricType metricType, Map props, boolean isAggregateable, boolean isAlias ) { - DataType esType = typeRegistry.fromEs(typeName); + DataType esType = typeRegistry.fromEs(typeName, metricType); if (esType == TEXT) { return new TextEsField(fieldName, props, false, isAlias); @@ -551,7 +561,8 @@ private static EsField createField( return DateEsField.dateEsField(fieldName, props, isAggregateable); } if (esType == UNSUPPORTED) { - return new UnsupportedEsField(fieldName, typeName, null, props); + String originalType = metricType == TimeSeriesParams.MetricType.COUNTER ? "counter" : typeName; + return new UnsupportedEsField(fieldName, originalType, null, props); } return new EsField(fieldName, esType, props, isAggregateable, isAlias); @@ -768,6 +779,7 @@ private static List buildIndices( typeRegistry, s, typeCap.getType(), + typeCap.getMetricType(), emptyMap(), typeCap.isAggregatable(), isAliasFieldType.get() diff --git a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/DataTypeRegistry.java b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/DataTypeRegistry.java index 6989d4572a974..712b7df103947 100644 --- a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/DataTypeRegistry.java +++ b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/DataTypeRegistry.java @@ -7,6 +7,8 @@ package org.elasticsearch.xpack.ql.type; +import org.elasticsearch.index.mapper.TimeSeriesParams; + import java.util.Collection; /** @@ -19,7 +21,7 @@ public interface DataTypeRegistry { // Collection dataTypes(); - DataType fromEs(String typeName); + DataType fromEs(String typeName, TimeSeriesParams.MetricType metricType); DataType fromJava(Object value); diff --git a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/DefaultDataTypeRegistry.java b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/DefaultDataTypeRegistry.java index 3c37998191eaa..472f51870d01c 100644 --- a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/DefaultDataTypeRegistry.java +++ b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/DefaultDataTypeRegistry.java @@ -7,6 +7,8 @@ package org.elasticsearch.xpack.ql.type; +import org.elasticsearch.index.mapper.TimeSeriesParams; + import java.util.Collection; public class DefaultDataTypeRegistry implements DataTypeRegistry { @@ -21,7 +23,7 @@ public Collection dataTypes() { } @Override - public DataType fromEs(String typeName) { + public DataType fromEs(String typeName, TimeSeriesParams.MetricType metricType) { return DataTypes.fromEs(typeName); } diff --git a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/Types.java b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/Types.java index c202e797b7566..a19f4c634f77c 100644 --- a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/Types.java +++ b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/type/Types.java @@ -8,6 +8,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.core.Booleans; +import org.elasticsearch.index.mapper.TimeSeriesParams; import java.util.Collections; import java.util.LinkedHashMap; @@ -52,8 +53,9 @@ private static DataType getType(DataTypeRegistry typeRegistry, Map dataTypes() { } @Override - public DataType fromEs(String typeName) { + public DataType fromEs(String typeName, TimeSeriesParams.MetricType metricType) { return SqlDataTypes.fromEs(typeName); } From b7e2a13bb3c619cf4c551b0fe9df4a916c76cd3a Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Wed, 30 Aug 2023 10:44:09 -0400 Subject: [PATCH 2/5] Update docs/changelog/99054.yaml --- docs/changelog/99054.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/99054.yaml diff --git a/docs/changelog/99054.yaml b/docs/changelog/99054.yaml new file mode 100644 index 0000000000000..a9e4128e7ae97 --- /dev/null +++ b/docs/changelog/99054.yaml @@ -0,0 +1,5 @@ +pr: 99054 +summary: "ESQL: Mark counter fields as unsupported" +area: ES|QL +type: enhancement +issues: [] From e6625165ab9ad0691c9c9ca3da780a21d91bf17f Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Tue, 12 Sep 2023 11:35:21 -0400 Subject: [PATCH 3/5] WIP --- .../esql/type/EsqlDataTypeRegistryTests.java | 577 ++---------------- 1 file changed, 43 insertions(+), 534 deletions(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java index b462f8963cd84..38ba6057db912 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java @@ -8,551 +8,60 @@ import org.elasticsearch.action.fieldcaps.FieldCapabilities; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse; -import org.elasticsearch.common.util.Maps; +import org.elasticsearch.common.Strings; +import org.elasticsearch.index.mapper.TimeSeriesParams; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.ql.index.EsIndex; import org.elasticsearch.xpack.ql.index.IndexResolution; import org.elasticsearch.xpack.ql.index.IndexResolver; import org.elasticsearch.xpack.ql.type.DataType; +import org.elasticsearch.xpack.ql.type.DataTypes; import org.elasticsearch.xpack.ql.type.EsField; -import org.elasticsearch.xpack.ql.type.InvalidMappedField; -import org.elasticsearch.xpack.ql.type.KeywordEsField; -import org.elasticsearch.xpack.ql.type.TypesTests; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Stream; -import static java.util.Collections.singletonMap; -import static org.elasticsearch.common.logging.LoggerMessageFormat.format; -import static org.elasticsearch.xpack.ql.type.DataTypes.INTEGER; -import static org.elasticsearch.xpack.ql.type.DataTypes.KEYWORD; -import static org.elasticsearch.xpack.ql.type.DataTypes.OBJECT; -import static org.elasticsearch.xpack.ql.type.DataTypes.TEXT; -import static org.elasticsearch.xpack.ql.type.DataTypes.UNSUPPORTED; -import static org.elasticsearch.xpack.ql.type.DataTypes.isDateTime; -import static org.elasticsearch.xpack.ql.type.DataTypes.isPrimitive; +import static org.hamcrest.Matchers.equalTo; public class EsqlDataTypeRegistryTests extends ESTestCase { - - public void testMergeSameMapping() { - Map oneMapping = loadMapping("mapping-basic.json"); - Map sameMapping = loadMapping("mapping-basic.json"); - assertNotSame(oneMapping, sameMapping); - assertEquals(oneMapping, sameMapping); - - IndexResolution resolution = merge(new EsIndex("a", oneMapping), new EsIndex("b", sameMapping)); - - assertTrue(resolution.isValid()); - assertEqualsMaps(oneMapping, resolution.get().mapping()); - assertEquals(Set.of("a", "b"), resolution.get().concreteIndices()); - } - - public void testMergeCompatibleMapping() { - Map basicMapping = loadMapping("mapping-basic.json"); - Map numericMapping = loadMapping("mapping-numeric.json"); - - assertNotEquals(basicMapping, numericMapping); - - IndexResolution resolution = merge(new EsIndex("basic", basicMapping), new EsIndex("numeric", numericMapping)); - - assertTrue(resolution.isValid()); - assertEquals(basicMapping.size() + numericMapping.size(), resolution.get().mapping().size()); - assertEquals(Set.of("basic", "numeric"), resolution.get().concreteIndices()); - } - - public void testMergeIncompatibleTypes() throws Exception { - Map basicMapping = loadMapping("mapping-basic.json"); - Map incompatible = loadMapping("mapping-basic-incompatible.json"); - - assertNotEquals(basicMapping, incompatible); - - String wildcard = "*"; - IndexResolution resolution = merge(new EsIndex("basic", basicMapping), new EsIndex("incompatible", incompatible)); - - assertTrue(resolution.isValid()); - - EsIndex esIndex = resolution.get(); - assertEquals(wildcard, esIndex.name()); - assertEquals(Set.of("basic", "incompatible"), esIndex.concreteIndices()); - EsField esField = esIndex.mapping().get("gender"); - assertEquals(InvalidMappedField.class, esField.getClass()); - - assertEquals( - "mapped as [2] incompatible types: [text] in [incompatible], [keyword] in [basic]", - ((InvalidMappedField) esField).errorMessage() - ); - } - - public void testMergeIncompatibleCapabilities() { - Map basicMapping = loadMapping("mapping-basic.json"); - Map incompatible = loadMapping("mapping-basic-nodocvalues.json"); - - assertNotEquals(basicMapping, incompatible); - - String wildcard = "*"; - IndexResolution resolution = merge(new EsIndex("basic", basicMapping), new EsIndex("incompatible", incompatible)); - - assertTrue(resolution.isValid()); - - EsIndex esIndex = resolution.get(); - assertEquals(wildcard, esIndex.name()); - EsField esField = esIndex.mapping().get("emp_no"); - assertEquals(InvalidMappedField.class, esField.getClass()); - assertEquals("mapped as aggregatable except in [incompatible]", ((InvalidMappedField) esField).errorMessage()); - assertEquals(Set.of("basic", "incompatible"), resolution.get().concreteIndices()); - } - - public void testMultiLevelObjectMappings() throws Exception { - Map dottedMapping = loadMapping("mapping-dotted-field.json"); - - IndexResolution resolution = merge(new EsIndex("a", dottedMapping)); - - assertTrue(resolution.isValid()); - assertEqualsMaps(dottedMapping, resolution.get().mapping()); - assertEquals(Set.of("a"), resolution.get().concreteIndices()); - } - - public void testMultiLevelNestedMappings() { - Map nestedMapping = loadMapping("mapping-nested.json"); - - IndexResolution resolution = merge(new EsIndex("a", nestedMapping)); - - assertTrue(resolution.isValid()); - assertEqualsMaps(nestedMapping, resolution.get().mapping()); - } - - public void testMetaFieldsAreIgnored() { - Map> fieldCaps = new HashMap<>(); - addFieldCaps(fieldCaps, "_version", "_version", true, false, false); - addFieldCaps(fieldCaps, "_not_meta_field", "integer", false, true, true); - addFieldCaps(fieldCaps, "_size", "integer", true, true, true); - addFieldCaps(fieldCaps, "_doc_count", "long", true, false, false); - addFieldCaps(fieldCaps, "text", "keyword", true, true); - - String wildcard = "*"; - IndexResolution resolution = mergedMappings(wildcard, new String[] { "index" }, fieldCaps); - assertTrue(resolution.isValid()); - - EsIndex esIndex = resolution.get(); - assertEquals(wildcard, esIndex.name()); - assertNull(esIndex.mapping().get("_version")); - assertNull(esIndex.mapping().get("_size")); - assertNull(esIndex.mapping().get("_doc_count")); - assertEquals(INTEGER, esIndex.mapping().get("_not_meta_field").getDataType()); - assertEquals(KEYWORD, esIndex.mapping().get("text").getDataType()); - assertEquals(Set.of("index"), resolution.get().concreteIndices()); - } - - public void testFlattenedHiddenSubfield() throws Exception { - Map> fieldCaps = new HashMap<>(); - addFieldCaps(fieldCaps, "some_field", "flattened", false, false); - addFieldCaps(fieldCaps, "some_field._keyed", "flattened", false, false); - addFieldCaps(fieldCaps, "another_field", "object", true, false); - addFieldCaps(fieldCaps, "another_field._keyed", "keyword", true, false); - addFieldCaps(fieldCaps, "nested_field", "object", false, false); - addFieldCaps(fieldCaps, "nested_field.sub_field", "flattened", true, true); - addFieldCaps(fieldCaps, "nested_field.sub_field._keyed", "flattened", true, true); - addFieldCaps(fieldCaps, "text", "keyword", true, true); - - String wildcard = "*"; - IndexResolution resolution = mergedMappings(wildcard, new String[] { "index" }, fieldCaps); - assertTrue(resolution.isValid()); - - EsIndex esIndex = resolution.get(); - assertEquals(wildcard, esIndex.name()); - assertEquals(Set.of("index"), resolution.get().concreteIndices()); - assertEquals(UNSUPPORTED, esIndex.mapping().get("some_field").getDataType()); - assertEquals(UNSUPPORTED, esIndex.mapping().get("some_field").getProperties().get("_keyed").getDataType()); - assertEquals(OBJECT, esIndex.mapping().get("nested_field").getDataType()); - assertEquals(UNSUPPORTED, esIndex.mapping().get("nested_field").getProperties().get("sub_field").getDataType()); - assertEquals( - UNSUPPORTED, - esIndex.mapping().get("nested_field").getProperties().get("sub_field").getProperties().get("_keyed").getDataType() - ); - assertEquals(KEYWORD, esIndex.mapping().get("text").getDataType()); - assertEquals(OBJECT, esIndex.mapping().get("another_field").getDataType()); - assertEquals(KEYWORD, esIndex.mapping().get("another_field").getProperties().get("_keyed").getDataType()); - } - - public void testPropagateUnsupportedTypeToSubFields() throws Exception { - // generate a field type having the name of the format "foobar43" - String esFieldType = randomAlphaOfLengthBetween(5, 10) + randomIntBetween(-100, 100); - Map> fieldCaps = new HashMap<>(); - addFieldCaps(fieldCaps, "a", "text", false, false); - addFieldCaps(fieldCaps, "a.b", esFieldType, false, false); - addFieldCaps(fieldCaps, "a.b.c", "object", true, false); - addFieldCaps(fieldCaps, "a.b.c.d", "keyword", true, false); - addFieldCaps(fieldCaps, "a.b.c.e", "foo", true, true); - - String wildcard = "*"; - IndexResolution resolution = mergedMappings(wildcard, new String[] { "index" }, fieldCaps); - assertTrue(resolution.isValid()); - - EsIndex esIndex = resolution.get(); - assertEquals(wildcard, esIndex.name()); - assertEquals(Set.of("index"), resolution.get().concreteIndices()); - assertEquals(TEXT, esIndex.mapping().get("a").getDataType()); - assertEquals(UNSUPPORTED, esIndex.mapping().get("a").getProperties().get("b").getDataType()); - assertEquals(UNSUPPORTED, esIndex.mapping().get("a").getProperties().get("b").getProperties().get("c").getDataType()); - assertEquals( - UNSUPPORTED, - esIndex.mapping().get("a").getProperties().get("b").getProperties().get("c").getProperties().get("d").getDataType() - ); - assertEquals( - UNSUPPORTED, - esIndex.mapping().get("a").getProperties().get("b").getProperties().get("c").getProperties().get("e").getDataType() - ); - } - - public void testRandomMappingFieldTypeMappedAsUnsupported() throws Exception { - // generate a field type having the name of the format "foobar43" - String esFieldType = randomAlphaOfLengthBetween(5, 10) + randomIntBetween(-100, 100); - - Map> fieldCaps = new HashMap<>(); - addFieldCaps(fieldCaps, "some_field", esFieldType, false, false); - addFieldCaps(fieldCaps, "another_field", "object", true, false); - addFieldCaps(fieldCaps, "another_field._foo", esFieldType, true, false); - addFieldCaps(fieldCaps, "nested_field", "object", false, false); - addFieldCaps(fieldCaps, "nested_field.sub_field1", esFieldType, true, true); - addFieldCaps(fieldCaps, "nested_field.sub_field1.bar", esFieldType, true, true); - addFieldCaps(fieldCaps, "nested_field.sub_field2", esFieldType, true, true); - // even if this is of a supported type, because it belongs to an UNSUPPORTED type parent, it should also be UNSUPPORTED - addFieldCaps(fieldCaps, "nested_field.sub_field2.bar", "keyword", true, true); - addFieldCaps(fieldCaps, "text", "keyword", true, true); - - String wildcard = "*"; - IndexResolution resolution = mergedMappings(wildcard, new String[] { "index" }, fieldCaps); - assertTrue(resolution.isValid()); - - EsIndex esIndex = resolution.get(); - assertEquals(wildcard, esIndex.name()); - assertEquals(Set.of("index"), resolution.get().concreteIndices()); - assertEquals(UNSUPPORTED, esIndex.mapping().get("some_field").getDataType()); - assertEquals(OBJECT, esIndex.mapping().get("nested_field").getDataType()); - assertEquals(UNSUPPORTED, esIndex.mapping().get("nested_field").getProperties().get("sub_field1").getDataType()); - assertEquals( - UNSUPPORTED, - esIndex.mapping().get("nested_field").getProperties().get("sub_field1").getProperties().get("bar").getDataType() - ); - assertEquals(UNSUPPORTED, esIndex.mapping().get("nested_field").getProperties().get("sub_field2").getDataType()); - assertEquals( - UNSUPPORTED, - esIndex.mapping().get("nested_field").getProperties().get("sub_field2").getProperties().get("bar").getDataType() - ); - assertEquals(KEYWORD, esIndex.mapping().get("text").getDataType()); - assertEquals(OBJECT, esIndex.mapping().get("another_field").getDataType()); - assertEquals(UNSUPPORTED, esIndex.mapping().get("another_field").getProperties().get("_foo").getDataType()); - } - - public void testMergeIncompatibleCapabilitiesOfObjectFields() throws Exception { - Map> fieldCaps = new HashMap<>(); - - int depth = randomInt(5); - - List level = new ArrayList<>(); - String fieldName = randomAlphaOfLength(3); - level.add(fieldName); - for (int i = 0; i <= depth; i++) { - String l = randomAlphaOfLength(3); - level.add(l); - fieldName += "." + l; - } - - // define a sub-field - addFieldCaps(fieldCaps, fieldName + ".keyword", "keyword", true, true); - - Map multi = new HashMap<>(); - multi.put( - "long", - new FieldCapabilities(fieldName, "long", false, true, true, new String[] { "one-index" }, null, null, Collections.emptyMap()) - ); - multi.put( - "text", - new FieldCapabilities( - fieldName, - "text", - false, - true, - false, - new String[] { "another-index" }, - null, - null, - Collections.emptyMap() + public void testCounter() { + resolve("long", TimeSeriesParams.MetricType.COUNTER, DataTypes.UNSIGNED_LONG); + } + + public void testGauge() { + resolve("long", TimeSeriesParams.MetricType.GAUGE, DataTypes.LONG); + } + + public void testLong() { + resolve("long", null, DataTypes.LONG); + } + + private void resolve(String esTypeName, TimeSeriesParams.MetricType metricType, DataType expected) { + String[] indices = new String[] { "idx-" + randomAlphaOfLength(5) }; + FieldCapabilitiesResponse caps = new FieldCapabilitiesResponse( + indices, + Map.of( + "f", + Map.of( + esTypeName, + new FieldCapabilities( + "c", + esTypeName, + false, + true, + true, + false, + metricType, + indices, + Strings.EMPTY_ARRAY, + Strings.EMPTY_ARRAY, + Strings.EMPTY_ARRAY, + Strings.EMPTY_ARRAY, + Map.of() + ) + ) ) ); - fieldCaps.put(fieldName, multi); - - String wildcard = "*"; - IndexResolution resolution = mergedMappings(wildcard, new String[] { "one-index" }, fieldCaps); - - assertTrue(resolution.isValid()); - - EsIndex esIndex = resolution.get(); - assertEquals(wildcard, esIndex.name()); - assertEquals(Set.of("one-index"), resolution.get().concreteIndices()); - EsField esField = null; - Map props = esIndex.mapping(); - for (String lvl : level) { - esField = props.get(lvl); - props = esField.getProperties(); - } - assertEquals(InvalidMappedField.class, esField.getClass()); - assertEquals( - "mapped as [2] incompatible types: [text] in [another-index], [long] in [one-index]", - ((InvalidMappedField) esField).errorMessage() - ); - } - - public void testSeparateSameMappingDifferentIndices() throws Exception { - Map oneMapping = loadMapping("mapping-basic.json"); - Map sameMapping = loadMapping("mapping-basic.json"); - assertNotSame(oneMapping, sameMapping); - assertEquals(oneMapping, sameMapping); - - List indices = separate(new EsIndex("a", oneMapping), new EsIndex("b", sameMapping)); - - assertEquals(2, indices.size()); - assertEqualsMaps(oneMapping, indices.get(0).mapping()); - assertEquals(Set.of("a"), indices.get(0).concreteIndices()); - assertEqualsMaps(sameMapping, indices.get(1).mapping()); - assertEquals(Set.of("b"), indices.get(1).concreteIndices()); - } - - public void testSeparateIncompatibleTypes() throws Exception { - Map basicMapping = loadMapping("mapping-basic.json"); - Map incompatible = loadMapping("mapping-basic-incompatible.json"); - - assertNotEquals(basicMapping, incompatible); - - List indices = separate(new EsIndex("basic", basicMapping), new EsIndex("incompatible", incompatible)); - - assertEquals(2, indices.size()); - assertEqualsMaps(basicMapping, indices.get(0).mapping()); - assertEquals(Set.of("basic"), indices.get(0).concreteIndices()); - assertEqualsMaps(incompatible, indices.get(1).mapping()); - assertEquals(Set.of("incompatible"), indices.get(1).concreteIndices()); - } - - // covers the scenario described in https://github.com/elastic/elasticsearch/issues/43876 - public void testMultipleCompatibleIndicesWithDifferentFields() { - int indicesCount = randomIntBetween(2, 15); - EsIndex[] expectedIndices = new EsIndex[indicesCount]; - - // each index will have one field with different name than all others - for (int i = 0; i < indicesCount; i++) { - Map mapping = Maps.newMapWithExpectedSize(1); - String fieldName = "field" + (i + 1); - mapping.put(fieldName, new KeywordEsField(fieldName)); - expectedIndices[i] = new EsIndex("index" + (i + 1), mapping); - } - Arrays.sort(expectedIndices, Comparator.comparing(EsIndex::name)); - - List actualIndices = separate(expectedIndices); - actualIndices.sort(Comparator.comparing(EsIndex::name)); - assertEquals(indicesCount, actualIndices.size()); - for (int i = 0; i < indicesCount; i++) { - assertEqualsMaps(expectedIndices[i].mapping(), actualIndices.get(i).mapping()); - assertEquals(Set.of(expectedIndices[i].name()), actualIndices.get(i).concreteIndices()); - } - } - - public void testMergeConcreteIndices() { - int indicesCount = randomIntBetween(2, 15); - EsIndex[] expectedIndices = new EsIndex[indicesCount]; - Set indexNames = new HashSet<>(); - - for (int i = 0; i < indicesCount; i++) { - Map mapping = Maps.newMapWithExpectedSize(1); - String fieldName = "field" + (i + 1); - mapping.put(fieldName, new KeywordEsField(fieldName)); - String indexName = "index" + (i + 1); - expectedIndices[i] = new EsIndex(indexName, mapping, Set.of(indexName)); - indexNames.add(indexName); - } - - IndexResolution resolution = merge(expectedIndices); - assertEquals(indicesCount, resolution.get().mapping().size()); - assertEquals(indexNames, resolution.get().concreteIndices()); - } - - public void testIndexWithNoMapping() { - Map> versionFC = singletonMap( - "_version", - singletonMap( - "_index", - new FieldCapabilities("_version", "_version", true, false, false, null, null, null, Collections.emptyMap()) - ) - ); - assertTrue(mergedMappings("*", new String[] { "empty" }, versionFC).isValid()); - } - - public static IndexResolution merge(EsIndex... indices) { - return mergedMappings("*", Stream.of(indices).map(EsIndex::name).toArray(String[]::new), fromMappings(indices)); - } - - public static List separate(EsIndex... indices) { - return separateMappings(null, Stream.of(indices).map(EsIndex::name).toArray(String[]::new), fromMappings(indices)); - } - - public static Map> fromMappings(EsIndex... indices) { - Map> merged = new HashMap<>(); - - // first pass: create the field caps - for (EsIndex index : indices) { - for (EsField field : index.mapping().values()) { - addFieldCaps(null, field, index.name(), merged); - } - } - - // second pass: update indices - for (Entry> entry : merged.entrySet()) { - String fieldName = entry.getKey(); - Map caps = entry.getValue(); - if (entry.getValue().size() > 1) { - for (EsIndex index : indices) { - EsField field = index.mapping().get(fieldName); - UpdateableFieldCapabilities fieldCaps = (UpdateableFieldCapabilities) caps.get(field.getDataType().esType()); - fieldCaps.indices.add(index.name()); - } - // TODO: what about nonAgg/SearchIndices? - } - } - - return merged; - } - - private static void addFieldCaps(String parent, EsField field, String indexName, Map> merged) { - String fieldName = parent != null ? parent + "." + field.getName() : field.getName(); - Map map = merged.get(fieldName); - if (map == null) { - map = new HashMap<>(); - merged.put(fieldName, map); - } - FieldCapabilities caps = map.computeIfAbsent( - field.getDataType().esType(), - esType -> new UpdateableFieldCapabilities( - fieldName, - esType, - isSearchable(field.getDataType()), - isAggregatable(field.getDataType()) - ) - ); - - if (field.isAggregatable() == false) { - ((UpdateableFieldCapabilities) caps).nonAggregatableIndices.add(indexName); - } - - for (EsField nested : field.getProperties().values()) { - addFieldCaps(fieldName, nested, indexName, merged); - } - } - - private static boolean isSearchable(DataType type) { - return isPrimitive(type); - } - - private static boolean isAggregatable(DataType type) { - return type.isNumeric() || type == KEYWORD || isDateTime(type); - } - - private static class UpdateableFieldCapabilities extends FieldCapabilities { - List indices = new ArrayList<>(); - List nonSearchableIndices = new ArrayList<>(); - List nonAggregatableIndices = new ArrayList<>(); - - UpdateableFieldCapabilities(String name, String type, boolean isSearchable, boolean isAggregatable) { - super(name, type, false, isSearchable, isAggregatable, null, null, null, Collections.emptyMap()); - } - - @Override - public String[] indices() { - return indices.isEmpty() ? null : indices.toArray(new String[indices.size()]); - } - - @Override - public String[] nonSearchableIndices() { - return nonSearchableIndices.isEmpty() ? null : nonSearchableIndices.toArray(new String[nonSearchableIndices.size()]); - } - - @Override - public String[] nonAggregatableIndices() { - return nonAggregatableIndices.isEmpty() ? null : nonAggregatableIndices.toArray(new String[nonAggregatableIndices.size()]); - } - - @Override - public String toString() { - return format("{},{}->{}", getName(), getType(), indices); - } - } - - private static void assertEqualsMaps(Map left, Map right) { - for (Entry entry : left.entrySet()) { - V rv = right.get(entry.getKey()); - assertEquals(format("Key [{}] has different values", entry.getKey()), entry.getValue(), rv); - } - } - - private void addFieldCaps( - Map> fieldCaps, - String name, - String type, - boolean isSearchable, - boolean isAggregatable - ) { - addFieldCaps(fieldCaps, name, type, false, isSearchable, isAggregatable); - } - - private void addFieldCaps( - Map> fieldCaps, - String name, - String type, - boolean isMetadataField, - boolean isSearchable, - boolean isAggregatable - ) { - Map cap = new HashMap<>(); - cap.put( - type, - new FieldCapabilities(name, type, isMetadataField, isSearchable, isAggregatable, null, null, null, Collections.emptyMap()) - ); - fieldCaps.put(name, cap); - } - - private static IndexResolution mergedMappings( - String indexPattern, - String[] indexNames, - Map> fieldCaps - ) { - return IndexResolver.mergedMappings( - EsqlDataTypeRegistry.INSTANCE, - indexPattern, - new FieldCapabilitiesResponse(indexNames, fieldCaps) - ); - } - - private static List separateMappings( - String javaRegex, - String[] indexNames, - Map> fieldCaps - ) { - return IndexResolver.separateMappings( - EsqlDataTypeRegistry.INSTANCE, - javaRegex, - new FieldCapabilitiesResponse(indexNames, fieldCaps), - null - ); - } + IndexResolution resolution = IndexResolver.mergedMappings(EsqlDataTypeRegistry.INSTANCE, "idx-*", caps); - public static Map loadMapping(String name) { - return TypesTests.loadMapping(EsqlDataTypeRegistry.INSTANCE, name, true); + EsField f = resolution.get().mapping().get("f"); + assertThat(f.getDataType(), equalTo(expected)); } } From 2f33d55328f0a8e6972d92d3cee8f8ad7a9c45f2 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Tue, 12 Sep 2023 13:10:15 -0400 Subject: [PATCH 4/5] test --- .../esql/type/EsqlDataTypeRegistryTests.java | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java index 38ba6057db912..63c1a4ac376cc 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java @@ -8,7 +8,6 @@ import org.elasticsearch.action.fieldcaps.FieldCapabilities; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse; -import org.elasticsearch.common.Strings; import org.elasticsearch.index.mapper.TimeSeriesParams; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.ql.index.IndexResolution; @@ -22,7 +21,7 @@ public class EsqlDataTypeRegistryTests extends ESTestCase { public void testCounter() { - resolve("long", TimeSeriesParams.MetricType.COUNTER, DataTypes.UNSIGNED_LONG); + resolve("long", TimeSeriesParams.MetricType.COUNTER, DataTypes.UNSUPPORTED); } public void testGauge() { @@ -35,33 +34,25 @@ public void testLong() { private void resolve(String esTypeName, TimeSeriesParams.MetricType metricType, DataType expected) { String[] indices = new String[] { "idx-" + randomAlphaOfLength(5) }; - FieldCapabilitiesResponse caps = new FieldCapabilitiesResponse( + FieldCapabilities fieldCap = new FieldCapabilities( + randomAlphaOfLength(3), + esTypeName, + false, + true, + true, + false, + metricType, indices, - Map.of( - "f", - Map.of( - esTypeName, - new FieldCapabilities( - "c", - esTypeName, - false, - true, - true, - false, - metricType, - indices, - Strings.EMPTY_ARRAY, - Strings.EMPTY_ARRAY, - Strings.EMPTY_ARRAY, - Strings.EMPTY_ARRAY, - Map.of() - ) - ) - ) + null, + null, + null, + null, + Map.of() ); + FieldCapabilitiesResponse caps = new FieldCapabilitiesResponse(indices, Map.of(fieldCap.getName(), Map.of(esTypeName, fieldCap))); IndexResolution resolution = IndexResolver.mergedMappings(EsqlDataTypeRegistry.INSTANCE, "idx-*", caps); - EsField f = resolution.get().mapping().get("f"); + EsField f = resolution.get().mapping().get(fieldCap.getName()); assertThat(f.getDataType(), equalTo(expected)); } } From cb26e0f0aa59f380862efd3d693fae08041814f4 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Wed, 13 Sep 2023 15:18:56 -0400 Subject: [PATCH 5/5] Format --- .../elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java index 63c1a4ac376cc..3620fcc8c5926 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeRegistryTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.xpack.ql.type.DataType; import org.elasticsearch.xpack.ql.type.DataTypes; import org.elasticsearch.xpack.ql.type.EsField; + import java.util.Map; import static org.hamcrest.Matchers.equalTo;