From 86b80b655197505e56c5b2ec5c036a019bc3bf66 Mon Sep 17 00:00:00 2001 From: Nhat Nguyen Date: Tue, 18 Jun 2024 07:24:27 -0700 Subject: [PATCH] Remove legacy IndexResolver (#109821) Currently, we are using the legacy IndexResolver alongside the updated version to compare their outputs. While this approach helps in bug detection, it adds to the maintenance burden, particularly when adding new features to the field-caps API or the index resolver. This change removes the legacy IndexResolver from the ESQL codebase. --- .../esql/core/index/IndexCompatibility.java | 49 - .../xpack/esql/core/index/IndexResolver.java | 1046 ----------------- .../core/index/RemoteClusterResolver.java | 40 - .../index/VersionCompatibilityChecks.java | 63 - .../esql/enrich/EnrichPolicyResolver.java | 42 +- .../xpack/esql/execution/PlanExecutor.java | 8 +- .../xpack/esql/plugin/EsqlPlugin.java | 14 +- .../xpack/esql/session/EsqlSession.java | 150 +-- ...lIndexResolver.java => IndexResolver.java} | 27 +- .../xpack/esql/analysis/AnalyzerTests.java | 4 +- .../enrich/EnrichPolicyResolverTests.java | 35 +- .../session/IndexResolverFieldNamesTests.java | 4 +- .../esql/stats/PlanExecutorMetricsTests.java | 10 +- .../esql/type/EsqlDataTypeRegistryTests.java | 4 +- 14 files changed, 70 insertions(+), 1426 deletions(-) delete mode 100644 x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/IndexCompatibility.java delete mode 100644 x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/IndexResolver.java delete mode 100644 x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/RemoteClusterResolver.java delete mode 100644 x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/VersionCompatibilityChecks.java rename x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/{EsqlIndexResolver.java => IndexResolver.java} (91%) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/IndexCompatibility.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/IndexCompatibility.java deleted file mode 100644 index 6cc0816661f01..0000000000000 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/IndexCompatibility.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.esql.core.index; - -import org.elasticsearch.Version; -import org.elasticsearch.xpack.esql.core.type.DataType; -import org.elasticsearch.xpack.esql.core.type.EsField; -import org.elasticsearch.xpack.esql.core.type.UnsupportedEsField; - -import java.util.Map; - -import static org.elasticsearch.xpack.esql.core.index.VersionCompatibilityChecks.isTypeSupportedInVersion; -import static org.elasticsearch.xpack.esql.core.type.DataType.isPrimitive; -import static org.elasticsearch.xpack.esql.core.type.Types.propagateUnsupportedType; - -public final class IndexCompatibility { - - public static Map compatible(Map mapping, Version version) { - for (Map.Entry entry : mapping.entrySet()) { - EsField esField = entry.getValue(); - DataType dataType = esField.getDataType(); - if (isPrimitive(dataType) == false) { - compatible(esField.getProperties(), version); - } else if (isTypeSupportedInVersion(dataType, version) == false) { - EsField field = new UnsupportedEsField(entry.getKey(), dataType.nameUpper(), null, esField.getProperties()); - entry.setValue(field); - propagateUnsupportedType(entry.getKey(), dataType.nameUpper(), esField.getProperties()); - } - } - return mapping; - } - - public static EsIndex compatible(EsIndex esIndex, Version version) { - compatible(esIndex.mapping(), version); - return esIndex; - } - - public static IndexResolution compatible(IndexResolution indexResolution, Version version) { - if (indexResolution.isValid()) { - compatible(indexResolution.get(), version); - } - return indexResolution; - } -} diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/IndexResolver.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/IndexResolver.java deleted file mode 100644 index 63467eaadd8df..0000000000000 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/IndexResolver.java +++ /dev/null @@ -1,1046 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -package org.elasticsearch.xpack.esql.core.index; - -import org.elasticsearch.ElasticsearchSecurityException; -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; -import org.elasticsearch.action.admin.indices.get.GetIndexRequest; -import org.elasticsearch.action.admin.indices.get.GetIndexRequest.Feature; -import org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction; -import org.elasticsearch.action.fieldcaps.FieldCapabilities; -import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest; -import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse; -import org.elasticsearch.action.support.IndicesOptions; -import org.elasticsearch.client.internal.Client; -import org.elasticsearch.cluster.metadata.AliasMetadata; -import org.elasticsearch.common.Strings; -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.esql.core.QlIllegalArgumentException; -import org.elasticsearch.xpack.esql.core.type.DataType; -import org.elasticsearch.xpack.esql.core.type.DataTypeRegistry; -import org.elasticsearch.xpack.esql.core.type.DateEsField; -import org.elasticsearch.xpack.esql.core.type.EsField; -import org.elasticsearch.xpack.esql.core.type.InvalidMappedField; -import org.elasticsearch.xpack.esql.core.type.KeywordEsField; -import org.elasticsearch.xpack.esql.core.type.TextEsField; -import org.elasticsearch.xpack.esql.core.type.UnsupportedEsField; -import org.elasticsearch.xpack.esql.core.util.CollectionUtils; -import org.elasticsearch.xpack.esql.core.util.Holder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.regex.Pattern; - -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static org.elasticsearch.action.ActionListener.wrap; -import static org.elasticsearch.common.Strings.hasText; -import static org.elasticsearch.common.regex.Regex.simpleMatch; -import static org.elasticsearch.transport.RemoteClusterAware.buildRemoteIndexName; -import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME; -import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; -import static org.elasticsearch.xpack.esql.core.type.DataType.OBJECT; -import static org.elasticsearch.xpack.esql.core.type.DataType.TEXT; -import static org.elasticsearch.xpack.esql.core.type.DataType.UNSUPPORTED; -import static org.elasticsearch.xpack.esql.core.util.StringUtils.qualifyAndJoinIndices; -import static org.elasticsearch.xpack.esql.core.util.StringUtils.splitQualifiedIndex; - -public class IndexResolver { - - public enum IndexType { - STANDARD_INDEX(SQL_TABLE, "INDEX"), - ALIAS(SQL_VIEW, "ALIAS"), - FROZEN_INDEX(SQL_TABLE, "FROZEN INDEX"), - // value for user types unrecognized - UNKNOWN("UNKNOWN", "UNKNOWN"); - - public static final EnumSet VALID_INCLUDE_FROZEN = EnumSet.of(STANDARD_INDEX, ALIAS, FROZEN_INDEX); - public static final EnumSet VALID_REGULAR = EnumSet.of(STANDARD_INDEX, ALIAS); - - private final String toSql; - private final String toNative; - - IndexType(String sql, String toNative) { - this.toSql = sql; - this.toNative = toNative; - } - - public String toSql() { - return toSql; - } - - public String toNative() { - return toNative; - } - } - - public record IndexInfo(String cluster, String name, IndexType type) { - - @Override - public String toString() { - return buildRemoteIndexName(cluster, name); - } - - } - - public static final String SQL_TABLE = "TABLE"; - public static final String SQL_VIEW = "VIEW"; - - private static final IndicesOptions INDICES_ONLY_OPTIONS = IndicesOptions.builder() - .concreteTargetOptions(IndicesOptions.ConcreteTargetOptions.ALLOW_UNAVAILABLE_TARGETS) - .wildcardOptions( - IndicesOptions.WildcardOptions.builder() - .matchOpen(true) - .matchClosed(false) - .includeHidden(false) - .allowEmptyExpressions(true) - .resolveAliases(false) - ) - .gatekeeperOptions( - IndicesOptions.GatekeeperOptions.builder().ignoreThrottled(true).allowClosedIndices(true).allowAliasToMultipleIndices(true) - ) - .build(); - private static final IndicesOptions FROZEN_INDICES_OPTIONS = IndicesOptions.builder() - .concreteTargetOptions(IndicesOptions.ConcreteTargetOptions.ALLOW_UNAVAILABLE_TARGETS) - .wildcardOptions( - IndicesOptions.WildcardOptions.builder() - .matchOpen(true) - .matchClosed(false) - .includeHidden(false) - .allowEmptyExpressions(true) - .resolveAliases(false) - ) - .gatekeeperOptions( - IndicesOptions.GatekeeperOptions.builder().ignoreThrottled(false).allowClosedIndices(true).allowAliasToMultipleIndices(true) - ) - .build(); - - public static final IndicesOptions FIELD_CAPS_INDICES_OPTIONS = IndicesOptions.builder() - .concreteTargetOptions(IndicesOptions.ConcreteTargetOptions.ALLOW_UNAVAILABLE_TARGETS) - .wildcardOptions( - IndicesOptions.WildcardOptions.builder() - .matchOpen(true) - .matchClosed(false) - .includeHidden(false) - .allowEmptyExpressions(true) - .resolveAliases(true) - ) - .gatekeeperOptions( - IndicesOptions.GatekeeperOptions.builder().ignoreThrottled(true).allowClosedIndices(true).allowAliasToMultipleIndices(true) - ) - .build(); - public static final IndicesOptions FIELD_CAPS_FROZEN_INDICES_OPTIONS = IndicesOptions.builder() - .concreteTargetOptions(IndicesOptions.ConcreteTargetOptions.ALLOW_UNAVAILABLE_TARGETS) - .wildcardOptions( - IndicesOptions.WildcardOptions.builder() - .matchOpen(true) - .matchClosed(false) - .includeHidden(false) - .allowEmptyExpressions(true) - .resolveAliases(true) - ) - .gatekeeperOptions( - IndicesOptions.GatekeeperOptions.builder().ignoreThrottled(false).allowClosedIndices(true).allowAliasToMultipleIndices(true) - ) - .build(); - - public static final Set ALL_FIELDS = Set.of("*"); - public static final Set INDEX_METADATA_FIELD = Set.of("_index"); - public static final String UNMAPPED = "unmapped"; - - private final Client client; - private final String clusterName; - private final DataTypeRegistry typeRegistry; - - private final Supplier> remoteClusters; - - public IndexResolver(Client client, String clusterName, DataTypeRegistry typeRegistry, Supplier> remoteClusters) { - this.client = client; - this.clusterName = clusterName; - this.typeRegistry = typeRegistry; - this.remoteClusters = remoteClusters; - } - - public String clusterName() { - return clusterName; - } - - public Set remoteClusters() { - return remoteClusters.get(); - } - - /** - * Resolves only the names, differentiating between indices and aliases. - * This method is required since the other methods rely on mapping which is tied to an index (not an alias). - */ - public void resolveNames( - String clusterWildcard, - String indexWildcard, - String javaRegex, - EnumSet types, - ActionListener> listener - ) { - - // first get aliases (if specified) - boolean retrieveAliases = CollectionUtils.isEmpty(types) || types.contains(IndexType.ALIAS); - boolean retrieveIndices = CollectionUtils.isEmpty(types) || types.contains(IndexType.STANDARD_INDEX); - boolean retrieveFrozenIndices = CollectionUtils.isEmpty(types) || types.contains(IndexType.FROZEN_INDEX); - - String[] indexWildcards = Strings.commaDelimitedListToStringArray(indexWildcard); - Set indexInfos = new HashSet<>(); - if (retrieveAliases && clusterIsLocal(clusterWildcard)) { - ResolveIndexAction.Request resolveRequest = new ResolveIndexAction.Request(indexWildcards, IndicesOptions.lenientExpandOpen()); - client.admin().indices().resolveIndex(resolveRequest, wrap(response -> { - for (ResolveIndexAction.ResolvedAlias alias : response.getAliases()) { - indexInfos.add(new IndexInfo(clusterName, alias.getName(), IndexType.ALIAS)); - } - for (ResolveIndexAction.ResolvedDataStream dataStream : response.getDataStreams()) { - indexInfos.add(new IndexInfo(clusterName, dataStream.getName(), IndexType.ALIAS)); - } - resolveIndices(clusterWildcard, indexWildcards, javaRegex, retrieveIndices, retrieveFrozenIndices, indexInfos, listener); - }, ex -> { - // with security, two exception can be thrown: - // INFE - if no alias matches - // security exception is the user cannot access aliases - - // in both cases, that is allowed and we continue with the indices request - if (ex instanceof IndexNotFoundException || ex instanceof ElasticsearchSecurityException) { - resolveIndices( - clusterWildcard, - indexWildcards, - javaRegex, - retrieveIndices, - retrieveFrozenIndices, - indexInfos, - listener - ); - } else { - listener.onFailure(ex); - } - })); - } else { - resolveIndices(clusterWildcard, indexWildcards, javaRegex, retrieveIndices, retrieveFrozenIndices, indexInfos, listener); - } - } - - private void resolveIndices( - String clusterWildcard, - String[] indexWildcards, - String javaRegex, - boolean retrieveIndices, - boolean retrieveFrozenIndices, - Set indexInfos, - ActionListener> listener - ) { - if (retrieveIndices || retrieveFrozenIndices) { - if (clusterIsLocal(clusterWildcard)) { // resolve local indices - GetIndexRequest indexRequest = new GetIndexRequest().local(true) - .indices(indexWildcards) - .features(Feature.SETTINGS) - .includeDefaults(false) - .indicesOptions(INDICES_ONLY_OPTIONS); - - // if frozen indices are requested, make sure to update the request accordingly - if (retrieveFrozenIndices) { - indexRequest.indicesOptions(FROZEN_INDICES_OPTIONS); - } - - client.admin().indices().getIndex(indexRequest, listener.delegateFailureAndWrap((delegate, indices) -> { - if (indices != null) { - for (String indexName : indices.getIndices()) { - boolean isFrozen = retrieveFrozenIndices - && indices.getSettings().get(indexName).getAsBoolean("index.frozen", false); - indexInfos.add( - new IndexInfo(clusterName, indexName, isFrozen ? IndexType.FROZEN_INDEX : IndexType.STANDARD_INDEX) - ); - } - } - resolveRemoteIndices(clusterWildcard, indexWildcards, javaRegex, retrieveFrozenIndices, indexInfos, delegate); - })); - } else { - resolveRemoteIndices(clusterWildcard, indexWildcards, javaRegex, retrieveFrozenIndices, indexInfos, listener); - } - } else { - filterResults(javaRegex, indexInfos, listener); - } - } - - private void resolveRemoteIndices( - String clusterWildcard, - String[] indexWildcards, - String javaRegex, - boolean retrieveFrozenIndices, - Set indexInfos, - ActionListener> listener - ) { - if (hasText(clusterWildcard)) { - IndicesOptions indicesOptions = retrieveFrozenIndices ? FIELD_CAPS_FROZEN_INDICES_OPTIONS : FIELD_CAPS_INDICES_OPTIONS; - FieldCapabilitiesRequest fieldRequest = createFieldCapsRequest( - qualifyAndJoinIndices(clusterWildcard, indexWildcards), - ALL_FIELDS, - indicesOptions, - emptyMap() - ); - client.fieldCaps(fieldRequest, wrap(response -> { - String[] indices = response.getIndices(); - if (indices != null) { - for (String indexName : indices) { - // TODO: perform two requests w/ & w/o frozen option to retrieve (by diff) the throttling status? - Tuple splitRef = splitQualifiedIndex(indexName); - // Field caps on "remote:foo" should always return either empty or remote indices. But in case cluster's - // detail is missing, it's going to be a local index. TODO: why would this happen? - String cluster = splitRef.v1() == null ? clusterName : splitRef.v1(); - indexInfos.add(new IndexInfo(cluster, splitRef.v2(), IndexType.STANDARD_INDEX)); - } - } - filterResults(javaRegex, indexInfos, listener); - }, ex -> { - // see comment in resolveNames() - if (ex instanceof NoSuchRemoteClusterException || ex instanceof ElasticsearchSecurityException) { - filterResults(javaRegex, indexInfos, listener); - } else { - listener.onFailure(ex); - } - })); - } else { - filterResults(javaRegex, indexInfos, listener); - } - } - - private static void filterResults(String javaRegex, Set indexInfos, ActionListener> listener) { - - // since the index name does not support ?, filter the results manually - Pattern pattern = javaRegex != null ? Pattern.compile(javaRegex) : null; - - Set result = new TreeSet<>(Comparator.comparing(IndexInfo::cluster).thenComparing(IndexInfo::name)); - for (IndexInfo indexInfo : indexInfos) { - if (pattern == null || pattern.matcher(indexInfo.name()).matches()) { - result.add(indexInfo); - } - } - listener.onResponse(result); - } - - private boolean clusterIsLocal(String clusterWildcard) { - return clusterWildcard == null || simpleMatch(clusterWildcard, clusterName); - } - - /** - * Resolves a pattern to one (potentially compound meaning that spawns multiple indices) mapping. - */ - public void resolveAsMergedMapping( - String indexWildcard, - Set fieldNames, - IndicesOptions indicesOptions, - Map runtimeMappings, - ActionListener listener - ) { - FieldCapabilitiesRequest fieldRequest = createFieldCapsRequest(indexWildcard, fieldNames, indicesOptions, runtimeMappings); - client.fieldCaps( - fieldRequest, - listener.delegateFailureAndWrap((l, response) -> l.onResponse(mergedMappings(typeRegistry, indexWildcard, response))) - ); - } - - /** - * Resolves a pattern to one (potentially compound meaning that spawns multiple indices) mapping. - */ - public void resolveAsMergedMapping( - String indexWildcard, - Set fieldNames, - boolean includeFrozen, - Map runtimeMappings, - ActionListener listener - ) { - resolveAsMergedMapping(indexWildcard, fieldNames, includeFrozen, runtimeMappings, listener, (fieldName, types) -> null); - } - - /** - * Resolves a pattern to one (potentially compound meaning that spawns multiple indices) mapping. - */ - public void resolveAsMergedMapping( - String indexWildcard, - Set fieldNames, - boolean includeFrozen, - Map runtimeMappings, - ActionListener listener, - BiFunction, InvalidMappedField> specificValidityVerifier - ) { - FieldCapabilitiesRequest fieldRequest = createFieldCapsRequest(indexWildcard, fieldNames, includeFrozen, runtimeMappings); - client.fieldCaps( - fieldRequest, - listener.delegateFailureAndWrap( - (l, response) -> l.onResponse(mergedMappings(typeRegistry, indexWildcard, response, specificValidityVerifier, null, null)) - ) - ); - } - - public void resolveAsMergedMapping( - String indexWildcard, - Set fieldNames, - boolean includeFrozen, - Map runtimeMappings, - ActionListener listener, - BiFunction, InvalidMappedField> specificValidityVerifier, - BiConsumer fieldUpdater, - Set allowedMetadataFields - ) { - FieldCapabilitiesRequest fieldRequest = createFieldCapsRequest(indexWildcard, fieldNames, includeFrozen, runtimeMappings); - client.fieldCaps( - fieldRequest, - listener.delegateFailureAndWrap( - (l, response) -> l.onResponse( - mergedMappings(typeRegistry, indexWildcard, response, specificValidityVerifier, fieldUpdater, allowedMetadataFields) - ) - ) - ); - } - - public static IndexResolution mergedMappings( - DataTypeRegistry typeRegistry, - String indexPattern, - FieldCapabilitiesResponse fieldCapsResponse, - BiFunction, InvalidMappedField> specificValidityVerifier - ) { - return mergedMappings(typeRegistry, indexPattern, fieldCapsResponse, specificValidityVerifier, null, null); - } - - public static IndexResolution mergedMappings( - DataTypeRegistry typeRegistry, - String indexPattern, - FieldCapabilitiesResponse fieldCapsResponse, - BiFunction, InvalidMappedField> specificValidityVerifier, - BiConsumer fieldUpdater, - Set allowedMetadataFields - ) { - - if (fieldCapsResponse.getIndices().length == 0) { - return IndexResolution.notFound(indexPattern); - } - - BiFunction, InvalidMappedField> validityVerifier = (fieldName, types) -> { - InvalidMappedField f = specificValidityVerifier.apply(fieldName, types); - if (f != null) { - return f; - } - - StringBuilder errorMessage = new StringBuilder(); - boolean hasUnmapped = types.containsKey(UNMAPPED); - - if (types.size() > (hasUnmapped ? 2 : 1)) { - // build the error message - // and create a MultiTypeField - - for (Entry type : types.entrySet()) { - // skip unmapped - if (UNMAPPED.equals(type.getKey())) { - continue; - } - - if (errorMessage.length() > 0) { - errorMessage.append(", "); - } - errorMessage.append("["); - errorMessage.append(type.getKey()); - errorMessage.append("] in "); - errorMessage.append(Arrays.toString(type.getValue().indices())); - } - - errorMessage.insert(0, "mapped as [" + (types.size() - (hasUnmapped ? 1 : 0)) + "] incompatible types: "); - - return new InvalidMappedField(fieldName, errorMessage.toString()); - } - // type is okay, check aggregation - else { - FieldCapabilities fieldCap = types.values().iterator().next(); - - // validate search/agg-able - if (fieldCap.isAggregatable() && fieldCap.nonAggregatableIndices() != null) { - errorMessage.append("mapped as aggregatable except in "); - errorMessage.append(Arrays.toString(fieldCap.nonAggregatableIndices())); - } - if (fieldCap.isSearchable() && fieldCap.nonSearchableIndices() != null) { - if (errorMessage.length() > 0) { - errorMessage.append(","); - } - errorMessage.append("mapped as searchable except in "); - errorMessage.append(Arrays.toString(fieldCap.nonSearchableIndices())); - } - - if (errorMessage.length() > 0) { - return new InvalidMappedField(fieldName, errorMessage.toString()); - } - } - - // everything checks - return null; - }; - - // merge all indices onto the same one - List indices = buildIndices( - typeRegistry, - null, - fieldCapsResponse, - null, - i -> indexPattern, - validityVerifier, - fieldUpdater, - allowedMetadataFields - ); - - if (indices.size() > 1) { - throw new QlIllegalArgumentException( - "Incorrect merging of mappings (likely due to a bug) - expect at most one but found [{}]", - indices.size() - ); - } - - String[] indexNames = fieldCapsResponse.getIndices(); - if (indices.isEmpty()) { - return IndexResolution.valid(new EsIndex(indexNames[0], emptyMap(), Set.of())); - } else { - EsIndex idx = indices.get(0); - return IndexResolution.valid(new EsIndex(idx.name(), idx.mapping(), Set.of(indexNames))); - } - } - - public static IndexResolution mergedMappings( - DataTypeRegistry typeRegistry, - String indexPattern, - FieldCapabilitiesResponse fieldCapsResponse - ) { - return mergedMappings(typeRegistry, indexPattern, fieldCapsResponse, (fieldName, types) -> null, null, null); - } - - private static EsField createField( - DataTypeRegistry typeRegistry, - String fieldName, - Map> globalCaps, - Map hierarchicalMapping, - Map flattedMapping, - Function field - ) { - - Map parentProps = hierarchicalMapping; - - int dot = fieldName.lastIndexOf('.'); - String fullFieldName = fieldName; - EsField parent = null; - - if (dot >= 0) { - String parentName = fieldName.substring(0, dot); - fieldName = fieldName.substring(dot + 1); - parent = flattedMapping.get(parentName); - if (parent == null) { - Map map = globalCaps.get(parentName); - Function fieldFunction; - - // 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(), null, new TreeMap<>(), false, true); - } else { - Iterator iterator = map.values().iterator(); - FieldCapabilities parentCap = iterator.next(); - if (iterator.hasNext() && UNMAPPED.equals(parentCap.getType())) { - parentCap = iterator.next(); - } - final FieldCapabilities parentC = parentCap; - fieldFunction = s -> createField( - typeRegistry, - s, - parentC.getType(), - parentC.getMetricType(), - new TreeMap<>(), - parentC.isAggregatable(), - false - ); - } - - parent = createField(typeRegistry, parentName, globalCaps, hierarchicalMapping, flattedMapping, fieldFunction); - } - parentProps = parent.getProperties(); - } - - EsField esField = field.apply(fieldName); - - if (parent instanceof UnsupportedEsField unsupportedParent) { - String inherited = unsupportedParent.getInherited(); - String type = unsupportedParent.getOriginalType(); - - if (inherited == null) { - // mark the sub-field as unsupported, just like its parent, setting the first unsupported parent as the current one - esField = new UnsupportedEsField(esField.getName(), type, unsupportedParent.getName(), esField.getProperties()); - } else { - // mark the sub-field as unsupported, just like its parent, but setting the first unsupported parent - // as the parent's first unsupported grandparent - esField = new UnsupportedEsField(esField.getName(), type, inherited, esField.getProperties()); - } - } - - parentProps.put(fieldName, esField); - flattedMapping.put(fullFieldName, esField); - - return esField; - } - - private static EsField createField( - DataTypeRegistry typeRegistry, - String fieldName, - String typeName, - TimeSeriesParams.MetricType metricType, - Map props, - boolean isAggregateable, - boolean isAlias - ) { - DataType esType = typeRegistry.fromEs(typeName, metricType); - - if (esType == TEXT) { - return new TextEsField(fieldName, props, false, isAlias); - } - if (esType == KEYWORD) { - 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(fieldName, props, isAggregateable, length, normalized, isAlias); - } - if (esType == DATETIME) { - return DateEsField.dateEsField(fieldName, props, isAggregateable); - } - if (esType == UNSUPPORTED) { - String originalType = metricType == TimeSeriesParams.MetricType.COUNTER ? "counter" : typeName; - return new UnsupportedEsField(fieldName, originalType, null, props); - } - - return new EsField(fieldName, esType, props, isAggregateable, isAlias); - } - - private static FieldCapabilitiesRequest createFieldCapsRequest( - String index, - Set fieldNames, - IndicesOptions indicesOptions, - Map runtimeMappings - ) { - return new FieldCapabilitiesRequest().indices(Strings.commaDelimitedListToStringArray(index)) - .fields(fieldNames.toArray(String[]::new)) - .includeUnmapped(true) - .runtimeFields(runtimeMappings) - // lenient because we throw our own errors looking at the response e.g. if something was not resolved - // also because this way security doesn't throw authorization exceptions but rather honors ignore_unavailable - .indicesOptions(indicesOptions); - } - - private static FieldCapabilitiesRequest createFieldCapsRequest( - String index, - Set fieldNames, - boolean includeFrozen, - Map runtimeMappings - ) { - IndicesOptions indicesOptions = includeFrozen ? FIELD_CAPS_FROZEN_INDICES_OPTIONS : FIELD_CAPS_INDICES_OPTIONS; - return createFieldCapsRequest(index, fieldNames, indicesOptions, runtimeMappings); - } - - /** - * Resolves a pattern to multiple, separate indices. Doesn't perform validation. - */ - public void resolveAsSeparateMappings( - String indexWildcard, - String javaRegex, - boolean includeFrozen, - Map runtimeMappings, - ActionListener> listener - ) { - FieldCapabilitiesRequest fieldRequest = createFieldCapsRequest(indexWildcard, ALL_FIELDS, includeFrozen, runtimeMappings); - client.fieldCaps(fieldRequest, listener.delegateFailureAndWrap((delegate, response) -> { - client.admin().indices().getAliases(createGetAliasesRequest(response, includeFrozen), wrap(aliases -> { - delegate.onResponse(separateMappings(typeRegistry, javaRegex, response, aliases.getAliases())); - }, ex -> { - if (ex instanceof IndexNotFoundException || ex instanceof ElasticsearchSecurityException) { - delegate.onResponse(separateMappings(typeRegistry, javaRegex, response, null)); - } else { - delegate.onFailure(ex); - } - })); - })); - - } - - private static GetAliasesRequest createGetAliasesRequest(FieldCapabilitiesResponse response, boolean includeFrozen) { - return new GetAliasesRequest().aliases("*") - .indices(response.getIndices()) - .indicesOptions(includeFrozen ? FIELD_CAPS_FROZEN_INDICES_OPTIONS : FIELD_CAPS_INDICES_OPTIONS); - } - - public static List separateMappings( - DataTypeRegistry typeRegistry, - String javaRegex, - FieldCapabilitiesResponse fieldCaps, - Map> aliases - ) { - return buildIndices(typeRegistry, javaRegex, fieldCaps, aliases, Function.identity(), (s, cap) -> null, null, null); - } - - private static class Fields { - final Map hierarchicalMapping = new TreeMap<>(); - final Map flattedMapping = new LinkedHashMap<>(); - } - - /** - * Assemble an index-based mapping from the field caps (which is field based) by looking at the indices associated with - * each field. - */ - private static List buildIndices( - DataTypeRegistry typeRegistry, - String javaRegex, - FieldCapabilitiesResponse fieldCapsResponse, - Map> aliases, - Function indexNameProcessor, - BiFunction, InvalidMappedField> validityVerifier, - BiConsumer fieldUpdater, - Set allowedMetadataFields - ) { - - if ((fieldCapsResponse.getIndices() == null || fieldCapsResponse.getIndices().length == 0) - && (aliases == null || aliases.isEmpty())) { - return emptyList(); - } - - Set resolvedAliases = new HashSet<>(); - if (aliases != null) { - for (var aliasList : aliases.values()) { - for (AliasMetadata alias : aliasList) { - resolvedAliases.add(alias.getAlias()); - } - } - } - - Map indices = Maps.newLinkedHashMapWithExpectedSize(fieldCapsResponse.getIndices().length + resolvedAliases.size()); - Pattern pattern = javaRegex != null ? Pattern.compile(javaRegex) : null; - - // sort fields in reverse order to build the field hierarchy - TreeMap> sortedFields = new TreeMap<>(Collections.reverseOrder()); - final Map> fieldCaps = fieldCapsResponse.get(); - for (Entry> entry : fieldCaps.entrySet()) { - String fieldName = entry.getKey(); - // skip specific metadata fields - if ((allowedMetadataFields != null && allowedMetadataFields.contains(fieldName)) - || fieldCapsResponse.isMetadataField(fieldName) == false) { - sortedFields.put(fieldName, entry.getValue()); - } - } - - for (Entry> entry : sortedFields.entrySet()) { - String fieldName = entry.getKey(); - Map types = entry.getValue(); - final InvalidMappedField invalidField = validityVerifier.apply(fieldName, types); - // apply verification for fields belonging to index aliases - Map invalidFieldsForAliases = getInvalidFieldsForAliases(fieldName, types, aliases); - // For ESQL there are scenarios where there is no field asked from field_caps and the field_caps response only contains - // the list of indices. To be able to still have an "indices" list properly built (even if empty), the metadata fields are - // accepted but not actually added to each index hierarchy. - boolean isMetadataField = allowedMetadataFields != null && allowedMetadataFields.contains(fieldName); - - // check each type - for (Entry typeEntry : types.entrySet()) { - if (UNMAPPED.equals(typeEntry.getKey())) { - continue; - } - FieldCapabilities typeCap = typeEntry.getValue(); - String[] capIndices = typeCap.indices(); - - // compute the actual indices - if any are specified, take into account the unmapped indices - final String[] concreteIndices; - if (capIndices != null) { - concreteIndices = capIndices; - } else { - concreteIndices = fieldCapsResponse.getIndices(); - } - - Set uniqueAliases = new LinkedHashSet<>(); - // put the field in their respective mappings and collect the aliases names - for (String index : concreteIndices) { - List concreteIndexAliases = aliases != null ? aliases.get(index) : null; - if (concreteIndexAliases != null) { - for (AliasMetadata e : concreteIndexAliases) { - uniqueAliases.add(e.alias()); - } - } - // TODO is split still needed? - if (pattern == null || pattern.matcher(splitQualifiedIndex(index).v2()).matches()) { - String indexName = indexNameProcessor.apply(index); - Fields indexFields = indices.computeIfAbsent(indexName, k -> new Fields()); - EsField field = indexFields.flattedMapping.get(fieldName); - // create field hierarchy or update it in case of an invalid field - if (isMetadataField == false - && (field == null || (invalidField != null && (field instanceof InvalidMappedField) == false))) { - createField(typeRegistry, fieldName, indexFields, fieldCaps, invalidField, typeCap); - - // In evolving mappings, it is possible for a field to be promoted to an object in new indices - // meaning there are subfields associated with this *invalid* field. - // index_A: file -> keyword - // index_B: file -> object, file.name = keyword - // - // In the scenario above file is problematic but file.name is not. This scenario is addressed - // below through the dedicated callback - copy the existing properties or drop them all together. - // Note this applies for *invalid* fields (that have conflicts), not *unsupported* (those that cannot be read) - // See https://github.com/elastic/elasticsearch/pull/100875 - - // Postpone the call until is really needed - if (fieldUpdater != null && field != null) { - EsField newField = indexFields.flattedMapping.get(fieldName); - if (newField != field && newField instanceof InvalidMappedField newInvalidField) { - fieldUpdater.accept(field, newInvalidField); - } - } - } - } - } - // put the field in their respective mappings by alias name - for (String index : uniqueAliases) { - Fields indexFields = indices.computeIfAbsent(index, k -> new Fields()); - EsField field = indexFields.flattedMapping.get(fieldName); - if (isMetadataField == false && field == null && invalidFieldsForAliases.get(index) == null) { - createField(typeRegistry, fieldName, indexFields, fieldCaps, invalidField, typeCap); - } - } - } - } - - // return indices in ascending order - List foundIndices = new ArrayList<>(indices.size()); - for (Entry entry : indices.entrySet()) { - foundIndices.add(new EsIndex(entry.getKey(), entry.getValue().hierarchicalMapping, Set.of(entry.getKey()))); - } - foundIndices.sort(Comparator.comparing(EsIndex::name)); - return foundIndices; - } - - private static void createField( - DataTypeRegistry typeRegistry, - String fieldName, - Fields indexFields, - Map> fieldCaps, - InvalidMappedField invalidField, - FieldCapabilities typeCap - ) { - int dot = fieldName.lastIndexOf('.'); - /* - * Looking up the "tree" at the parent fields here to see if the field is an alias. - * When the upper elements of the "tree" have no elements in fieldcaps, then this is an alias field. But not - * always: if there are two aliases - a.b.c.alias1 and a.b.c.alias2 - only one of them will be considered alias. - */ - Holder isAliasFieldType = new Holder<>(false); - if (dot >= 0) { - String parentName = fieldName.substring(0, dot); - if (indexFields.flattedMapping.get(parentName) == null) { - // lack of parent implies the field is an alias - if (fieldCaps.get(parentName) == null) { - isAliasFieldType.set(true); - } - } - } - - createField( - typeRegistry, - fieldName, - fieldCaps, - indexFields.hierarchicalMapping, - indexFields.flattedMapping, - s -> invalidField != null - ? invalidField - : createField( - typeRegistry, - s, - typeCap.getType(), - typeCap.getMetricType(), - new TreeMap<>(), - typeCap.isAggregatable(), - isAliasFieldType.get() - ) - ); - } - - /* - * Checks if the field is valid (same type and same capabilities - searchable/aggregatable) across indices belonging to a list - * of aliases. - * A field can look like the example below (generated by field_caps API). - * "name": { - * "text": { - * "type": "text", - * "searchable": false, - * "aggregatable": false, - * "indices": [ - * "bar", - * "foo" - * ], - * "non_searchable_indices": [ - * "foo" - * ] - * }, - * "keyword": { - * "type": "keyword", - * "searchable": false, - * "aggregatable": true, - * "non_aggregatable_indices": [ - * "bar", "baz" - * ] - * } - * } - */ - private static Map getInvalidFieldsForAliases( - String fieldName, - Map types, - Map> aliases - ) { - if (aliases == null || aliases.isEmpty()) { - return emptyMap(); - } - Map invalidFields = new HashMap<>(); - Map> typesErrors = new HashMap<>(); // map holding aliases and a list of unique field types across its indices - Map> aliasToIndices = new HashMap<>(); // map with aliases and their list of indices - - for (var entry : aliases.entrySet()) { - for (AliasMetadata aliasMetadata : entry.getValue()) { - String aliasName = aliasMetadata.alias(); - aliasToIndices.putIfAbsent(aliasName, new HashSet<>()); - aliasToIndices.get(aliasName).add(entry.getKey()); - } - } - - // iterate over each type - for (Entry type : types.entrySet()) { - String esFieldType = type.getKey(); - if (Objects.equals(esFieldType, UNMAPPED)) { - continue; - } - String[] indices = type.getValue().indices(); - // if there is a list of indices where this field type is defined - if (indices != null) { - // Look at all these indices' aliases and add the type of the field to a list (Set) with unique elements. - // A valid mapping for a field in an index alias should contain only one type. If it doesn't, this means that field - // is mapped as different types across the indices in this index alias. - for (String index : indices) { - List indexAliases = aliases.get(index); - if (indexAliases == null) { - continue; - } - for (AliasMetadata aliasMetadata : indexAliases) { - String aliasName = aliasMetadata.alias(); - if (typesErrors.containsKey(aliasName)) { - typesErrors.get(aliasName).add(esFieldType); - } else { - Set fieldTypes = new HashSet<>(); - fieldTypes.add(esFieldType); - typesErrors.put(aliasName, fieldTypes); - } - } - } - } - } - - for (String aliasName : aliasToIndices.keySet()) { - // if, for the same index alias, there are multiple field types for this fieldName ie the index alias has indices where the same - // field name is of different types - Set esFieldTypes = typesErrors.get(aliasName); - if (esFieldTypes != null && esFieldTypes.size() > 1) { - // consider the field as invalid, for the currently checked index alias - // the error message doesn't actually matter - invalidFields.put(aliasName, new InvalidMappedField(fieldName)); - } else { - // if the field type is the same across all this alias' indices, check the field's capabilities (searchable/aggregatable) - for (Entry type : types.entrySet()) { - if (Objects.equals(type.getKey(), UNMAPPED)) { - continue; - } - FieldCapabilities f = type.getValue(); - - // the existence of a list of non_aggregatable_indices is an indication that not all indices have the same capabilities - // but this list can contain indices belonging to other aliases, so we need to check only for this alias - if (f.nonAggregatableIndices() != null) { - Set aliasIndices = aliasToIndices.get(aliasName); - int nonAggregatableCount = 0; - // either all or none of the non-aggregatable indices belonging to a certain alias should be in this list - for (String nonAggIndex : f.nonAggregatableIndices()) { - if (aliasIndices.contains(nonAggIndex)) { - nonAggregatableCount++; - } - } - if (nonAggregatableCount > 0 && nonAggregatableCount != aliasIndices.size()) { - invalidFields.put(aliasName, new InvalidMappedField(fieldName)); - break; - } - } - - // perform the same check for non_searchable_indices list - if (f.nonSearchableIndices() != null) { - Set aliasIndices = aliasToIndices.get(aliasName); - int nonSearchableCount = 0; - // either all or none of the non-searchable indices belonging to a certain alias should be in this list - for (String nonSearchIndex : f.nonSearchableIndices()) { - if (aliasIndices.contains(nonSearchIndex)) { - nonSearchableCount++; - } - } - if (nonSearchableCount > 0 && nonSearchableCount != aliasIndices.size()) { - invalidFields.put(aliasName, new InvalidMappedField(fieldName)); - break; - } - } - } - } - } - - if (invalidFields.size() > 0) { - return invalidFields; - } - // everything checks - return emptyMap(); - } - - /** - * Callback interface used when transitioning an already discovered EsField to an InvalidMapped one. - * By default, this interface is not used, meaning when a field is marked as invalid all its subfields - * are removed (are dropped). - * For cases where this is not desired, a different strategy can be employed such as keeping the properties: - * @see IndexResolver#PRESERVE_PROPERTIES - */ - public interface ExistingFieldInvalidCallback extends BiConsumer {}; - - /** - * Preserve the properties (sub fields) of an existing field even when marking it as invalid. - */ - public static ExistingFieldInvalidCallback PRESERVE_PROPERTIES = (oldField, newField) -> { - var oldProps = oldField.getProperties(); - if (oldProps.size() > 0) { - newField.getProperties().putAll(oldProps); - } - }; -} diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/RemoteClusterResolver.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/RemoteClusterResolver.java deleted file mode 100644 index e83eddc71000b..0000000000000 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/RemoteClusterResolver.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.esql.core.index; - -import org.elasticsearch.common.settings.ClusterSettings; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.transport.RemoteClusterAware; -import org.elasticsearch.transport.RemoteConnectionStrategy; - -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.CopyOnWriteArraySet; - -public final class RemoteClusterResolver extends RemoteClusterAware { - private final CopyOnWriteArraySet clusters; - - public RemoteClusterResolver(Settings settings, ClusterSettings clusterSettings) { - super(settings); - clusters = new CopyOnWriteArraySet<>(getEnabledRemoteClusters(settings)); - listenForUpdates(clusterSettings); - } - - @Override - protected void updateRemoteCluster(String clusterAlias, Settings settings) { - if (RemoteConnectionStrategy.isConnectionEnabled(clusterAlias, settings)) { - clusters.add(clusterAlias); - } else { - clusters.remove(clusterAlias); - } - } - - public Set remoteClusters() { - return new TreeSet<>(clusters); - } -} diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/VersionCompatibilityChecks.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/VersionCompatibilityChecks.java deleted file mode 100644 index e4ae4f8f0d51f..0000000000000 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/index/VersionCompatibilityChecks.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.esql.core.index; - -import org.elasticsearch.TransportVersion; -import org.elasticsearch.TransportVersions; -import org.elasticsearch.Version; -import org.elasticsearch.core.Nullable; -import org.elasticsearch.xpack.esql.core.type.DataType; - -import static org.elasticsearch.Version.V_8_2_0; -import static org.elasticsearch.Version.V_8_4_0; -import static org.elasticsearch.xpack.esql.core.type.DataType.UNSIGNED_LONG; -import static org.elasticsearch.xpack.esql.core.type.DataType.VERSION; - -public final class VersionCompatibilityChecks { - - public static final Version INTRODUCING_UNSIGNED_LONG = V_8_2_0; - public static final TransportVersion INTRODUCING_UNSIGNED_LONG_TRANSPORT = TransportVersions.V_8_2_0; - public static final Version INTRODUCING_VERSION_FIELD_TYPE = V_8_4_0; - - private VersionCompatibilityChecks() {} - - public static boolean isTypeSupportedInVersion(DataType dataType, Version version) { - if (dataType == UNSIGNED_LONG) { - return supportsUnsignedLong(version); - } - if (dataType == VERSION) { - return supportsVersionType(version); - } - return true; - } - - /** - * Does the provided {@code version} support the unsigned_long type (PR#60050)? - */ - public static boolean supportsUnsignedLong(Version version) { - return INTRODUCING_UNSIGNED_LONG.compareTo(version) <= 0; - } - - /** - * Does the provided {@code version} support the version type (PR#85502)? - */ - public static boolean supportsVersionType(Version version) { - return INTRODUCING_VERSION_FIELD_TYPE.compareTo(version) <= 0; - } - - public static @Nullable Version versionIntroducingType(DataType dataType) { - if (dataType == UNSIGNED_LONG) { - return INTRODUCING_UNSIGNED_LONG; - } - if (dataType == VERSION) { - return INTRODUCING_VERSION_FIELD_TYPE; - } - - return null; - } -} 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 2b29d36cdfa1d..82eda9679074d 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 @@ -37,11 +37,10 @@ import org.elasticsearch.xpack.core.enrich.EnrichPolicy; import org.elasticsearch.xpack.esql.analysis.EnrichResolution; import org.elasticsearch.xpack.esql.core.index.EsIndex; -import org.elasticsearch.xpack.esql.core.index.IndexResolver; import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.core.util.StringUtils; import org.elasticsearch.xpack.esql.plan.logical.Enrich; -import org.elasticsearch.xpack.esql.session.EsqlSession; +import org.elasticsearch.xpack.esql.session.IndexResolver; import org.elasticsearch.xpack.esql.type.EsqlDataTypes; import java.io.IOException; @@ -359,29 +358,22 @@ public void messageReceived(LookupRequest request, TransportChannel channel, Tas } try (ThreadContext.StoredContext ignored = threadContext.stashWithOrigin(ClientHelper.ENRICH_ORIGIN)) { String indexName = EnrichPolicy.getBaseName(policyName); - indexResolver.resolveAsMergedMapping( - indexName, - IndexResolver.ALL_FIELDS, - false, - Map.of(), - refs.acquire(indexResult -> { - if (indexResult.isValid() && indexResult.get().concreteIndices().size() == 1) { - EsIndex esIndex = indexResult.get(); - var concreteIndices = Map.of(request.clusterAlias, Iterables.get(esIndex.concreteIndices(), 0)); - var resolved = new ResolvedEnrichPolicy( - p.getMatchField(), - p.getType(), - p.getEnrichFields(), - concreteIndices, - esIndex.mapping() - ); - resolvedPolices.put(policyName, resolved); - } else { - failures.put(policyName, indexResult.toString()); - } - }), - EsqlSession::specificValidity - ); + indexResolver.resolveAsMergedMapping(indexName, IndexResolver.ALL_FIELDS, refs.acquire(indexResult -> { + if (indexResult.isValid() && indexResult.get().concreteIndices().size() == 1) { + EsIndex esIndex = indexResult.get(); + var concreteIndices = Map.of(request.clusterAlias, Iterables.get(esIndex.concreteIndices(), 0)); + var resolved = new ResolvedEnrichPolicy( + p.getMatchField(), + p.getType(), + p.getEnrichFields(), + concreteIndices, + esIndex.mapping() + ); + resolvedPolices.put(policyName, resolved); + } else { + failures.put(policyName, indexResult.toString()); + } + })); } } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java index 7af2668e9d74b..f4979fa9928db 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java @@ -12,7 +12,6 @@ import org.elasticsearch.xpack.esql.analysis.PreAnalyzer; import org.elasticsearch.xpack.esql.analysis.Verifier; import org.elasticsearch.xpack.esql.core.expression.function.FunctionRegistry; -import org.elasticsearch.xpack.esql.core.index.IndexResolver; import org.elasticsearch.xpack.esql.enrich.EnrichPolicyResolver; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext; @@ -20,8 +19,8 @@ import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan; import org.elasticsearch.xpack.esql.planner.Mapper; import org.elasticsearch.xpack.esql.session.EsqlConfiguration; -import org.elasticsearch.xpack.esql.session.EsqlIndexResolver; import org.elasticsearch.xpack.esql.session.EsqlSession; +import org.elasticsearch.xpack.esql.session.IndexResolver; import org.elasticsearch.xpack.esql.stats.Metrics; import org.elasticsearch.xpack.esql.stats.QueryMetric; @@ -30,16 +29,14 @@ public class PlanExecutor { private final IndexResolver indexResolver; - private final EsqlIndexResolver esqlIndexResolver; private final PreAnalyzer preAnalyzer; private final FunctionRegistry functionRegistry; private final Mapper mapper; private final Metrics metrics; private final Verifier verifier; - public PlanExecutor(IndexResolver indexResolver, EsqlIndexResolver esqlIndexResolver) { + public PlanExecutor(IndexResolver indexResolver) { this.indexResolver = indexResolver; - this.esqlIndexResolver = esqlIndexResolver; this.preAnalyzer = new PreAnalyzer(); this.functionRegistry = new EsqlFunctionRegistry(); this.mapper = new Mapper(functionRegistry); @@ -58,7 +55,6 @@ public void esql( sessionId, cfg, indexResolver, - esqlIndexResolver, enrichPolicyResolver, preAnalyzer, functionRegistry, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java index 228ed6c5b4b32..d3b2d5c6e7646 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java @@ -57,13 +57,12 @@ import org.elasticsearch.xpack.esql.action.RestEsqlQueryAction; import org.elasticsearch.xpack.esql.core.expression.Attribute; import org.elasticsearch.xpack.esql.core.expression.NamedExpression; -import org.elasticsearch.xpack.esql.core.index.IndexResolver; import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.enrich.EnrichLookupOperator; import org.elasticsearch.xpack.esql.execution.PlanExecutor; import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute; import org.elasticsearch.xpack.esql.querydsl.query.SingleValueQuery; -import org.elasticsearch.xpack.esql.session.EsqlIndexResolver; +import org.elasticsearch.xpack.esql.session.IndexResolver; import org.elasticsearch.xpack.esql.type.EsqlDataTypeRegistry; import java.lang.invoke.MethodHandles; @@ -71,7 +70,6 @@ import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.function.Predicate; import java.util.function.Supplier; @@ -110,15 +108,7 @@ public Collection createComponents(PluginServices services) { BlockFactory blockFactory = new BlockFactory(circuitBreaker, bigArrays, maxPrimitiveArrayBlockSize); setupSharedSecrets(); return List.of( - new PlanExecutor( - new IndexResolver( - services.client(), - services.clusterService().getClusterName().value(), - EsqlDataTypeRegistry.INSTANCE, - Set::of - ), - new EsqlIndexResolver(services.client(), EsqlDataTypeRegistry.INSTANCE) - ), + new PlanExecutor(new IndexResolver(services.client(), EsqlDataTypeRegistry.INSTANCE)), new ExchangeService(services.clusterService().getSettings(), services.threadPool(), ThreadPool.Names.SEARCH, blockFactory), blockFactory ); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java index 1f5374b73466e..0589424b37d1e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java @@ -11,7 +11,6 @@ import org.elasticsearch.action.fieldcaps.FieldCapabilities; import org.elasticsearch.common.Strings; import org.elasticsearch.common.regex.Regex; -import org.elasticsearch.core.Assertions; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; @@ -31,12 +30,9 @@ import org.elasticsearch.xpack.esql.core.expression.UnresolvedStar; import org.elasticsearch.xpack.esql.core.expression.function.FunctionRegistry; import org.elasticsearch.xpack.esql.core.index.IndexResolution; -import org.elasticsearch.xpack.esql.core.index.IndexResolver; import org.elasticsearch.xpack.esql.core.index.MappingException; import org.elasticsearch.xpack.esql.core.plan.TableIdentifier; import org.elasticsearch.xpack.esql.core.plan.logical.LogicalPlan; -import org.elasticsearch.xpack.esql.core.type.DataType; -import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.core.type.InvalidMappedField; import org.elasticsearch.xpack.esql.core.util.Holder; import org.elasticsearch.xpack.esql.enrich.EnrichPolicyResolver; @@ -59,7 +55,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -68,7 +63,6 @@ import java.util.stream.Collectors; import static org.elasticsearch.index.query.QueryBuilders.boolQuery; -import static org.elasticsearch.xpack.esql.core.index.IndexResolver.UNMAPPED; import static org.elasticsearch.xpack.esql.core.util.ActionListeners.map; import static org.elasticsearch.xpack.esql.core.util.StringUtils.WILDCARD; @@ -79,7 +73,6 @@ public class EsqlSession { private final String sessionId; private final EsqlConfiguration configuration; private final IndexResolver indexResolver; - private final EsqlIndexResolver esqlIndexResolver; private final EnrichPolicyResolver enrichPolicyResolver; private final PreAnalyzer preAnalyzer; @@ -94,7 +87,6 @@ public EsqlSession( String sessionId, EsqlConfiguration configuration, IndexResolver indexResolver, - EsqlIndexResolver esqlIndexResolver, EnrichPolicyResolver enrichPolicyResolver, PreAnalyzer preAnalyzer, FunctionRegistry functionRegistry, @@ -105,7 +97,6 @@ public EsqlSession( this.sessionId = sessionId; this.configuration = configuration; this.indexResolver = indexResolver; - this.esqlIndexResolver = esqlIndexResolver; this.enrichPolicyResolver = enrichPolicyResolver; this.preAnalyzer = preAnalyzer; this.verifier = verifier; @@ -207,12 +198,7 @@ private void preAnalyzeIndices(LogicalPlan parsed, ActionListener void preAnalyzeIndices(LogicalPlan parsed, ActionListener fieldNames, - ActionListener listener - ) { - indexResolver.resolveAsMergedMapping(indexWildcard, fieldNames, false, Map.of(), new ActionListener<>() { - @Override - public void onResponse(IndexResolution fromQl) { - esqlIndexResolver.resolveAsMergedMapping(indexWildcard, fieldNames, new ActionListener<>() { - @Override - public void onResponse(IndexResolution fromEsql) { - if (fromQl.isValid() == false) { - if (fromEsql.isValid()) { - throw new IllegalArgumentException( - "ql and esql didn't make the same resolution: validity differs " + fromQl + " != " + fromEsql - ); - } - } else { - assertSameMappings("", fromQl.get().mapping(), fromEsql.get().mapping()); - if (fromQl.get().concreteIndices().equals(fromEsql.get().concreteIndices()) == false) { - throw new IllegalArgumentException( - "ql and esql didn't make the same resolution: concrete indices differ " - + fromQl.get().concreteIndices() - + " != " - + fromEsql.get().concreteIndices() - ); - } - } - listener.onResponse(fromEsql); - } - - private void assertSameMappings(String prefix, Map fromQl, Map fromEsql) { - List qlFields = new ArrayList<>(); - qlFields.addAll(fromQl.keySet()); - Collections.sort(qlFields); - - List esqlFields = new ArrayList<>(); - esqlFields.addAll(fromEsql.keySet()); - Collections.sort(esqlFields); - if (qlFields.equals(esqlFields) == false) { - throw new IllegalArgumentException( - prefix + ": ql and esql didn't make the same resolution: fields differ \n" + qlFields + " !=\n" + esqlFields - ); - } - - for (int f = 0; f < qlFields.size(); f++) { - String name = qlFields.get(f); - EsField qlField = fromQl.get(name); - EsField esqlField = fromEsql.get(name); - - if (qlField.getProperties().isEmpty() == false || esqlField.getProperties().isEmpty() == false) { - assertSameMappings( - prefix.equals("") ? name : prefix + "." + name, - qlField.getProperties(), - esqlField.getProperties() - ); - } - - /* - * Check that the field itself is the same, skipping isAlias because - * we don't actually use it in ESQL and the EsqlIndexResolver doesn't - * produce exactly the same result. - */ - if (qlField.getDataType().equals(DataType.UNSUPPORTED) == false - && qlField.getName().equals(esqlField.getName()) == false - // QL uses full paths for unsupported fields. ESQL does not. This particular difference is fine. - ) { - throw new IllegalArgumentException( - prefix - + "." - + name - + ": ql and esql didn't make the same resolution: names differ [" - + qlField.getName() - + "] != [" - + esqlField.getName() - + "]" - ); - } - if (qlField.getDataType() != esqlField.getDataType()) { - throw new IllegalArgumentException( - prefix - + "." - + name - + ": ql and esql didn't make the same resolution: types differ [" - + qlField.getDataType() - + "] != [" - + esqlField.getDataType() - + "]" - ); - } - if (qlField.isAggregatable() != esqlField.isAggregatable()) { - throw new IllegalArgumentException( - prefix - + "." - + name - + ": ql and esql didn't make the same resolution: aggregability differ [" - + qlField.isAggregatable() - + "] != [" - + esqlField.isAggregatable() - + "]" - ); - } - } - } - - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } - }); - } - - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } - }, - EsqlSession::specificValidity, - IndexResolver.PRESERVE_PROPERTIES, - // TODO no matter what metadata fields are asked in a query, the "allowedMetadataFields" is always _index, does it make - // sense to reflect the actual list of metadata fields instead? - IndexResolver.INDEX_METADATA_FIELD - ); - } - static Set fieldNames(LogicalPlan parsed, Set enrichPolicyMatchFields) { if (false == parsed.anyMatch(plan -> plan instanceof Aggregate || plan instanceof Project)) { // no explicit columns selection, for example "from employees" @@ -476,14 +332,14 @@ public void optimizedPhysicalPlan(LogicalPlan logicalPlan, ActionListener types) { - boolean hasUnmapped = types.containsKey(UNMAPPED); + boolean hasUnmapped = types.containsKey(IndexResolver.UNMAPPED); boolean hasTypeConflicts = types.size() > (hasUnmapped ? 2 : 1); String metricConflictsTypeName = null; boolean hasMetricConflicts = false; if (hasTypeConflicts == false) { for (Map.Entry type : types.entrySet()) { - if (UNMAPPED.equals(type.getKey())) { + if (IndexResolver.UNMAPPED.equals(type.getKey())) { continue; } if (type.getValue().metricConflictsIndices() != null && type.getValue().metricConflictsIndices().length > 0) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlIndexResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java similarity index 91% rename from x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlIndexResolver.java rename to x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java index f973983e47f39..983a45f36169e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlIndexResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java @@ -11,13 +11,13 @@ import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse; import org.elasticsearch.action.fieldcaps.IndexFieldCapabilities; +import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.internal.Client; import org.elasticsearch.common.Strings; import org.elasticsearch.index.mapper.TimeSeriesParams; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.esql.core.index.EsIndex; import org.elasticsearch.xpack.esql.core.index.IndexResolution; -import org.elasticsearch.xpack.esql.core.index.IndexResolver; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.DataTypeRegistry; import org.elasticsearch.xpack.esql.core.type.DateEsField; @@ -43,11 +43,30 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.TEXT; import static org.elasticsearch.xpack.esql.core.type.DataType.UNSUPPORTED; -public class EsqlIndexResolver { +public class IndexResolver { + public static final Set ALL_FIELDS = Set.of("*"); + public static final Set INDEX_METADATA_FIELD = Set.of("_index"); + public static final String UNMAPPED = "unmapped"; + + public static final IndicesOptions FIELD_CAPS_INDICES_OPTIONS = IndicesOptions.builder() + .concreteTargetOptions(IndicesOptions.ConcreteTargetOptions.ALLOW_UNAVAILABLE_TARGETS) + .wildcardOptions( + IndicesOptions.WildcardOptions.builder() + .matchOpen(true) + .matchClosed(false) + .includeHidden(false) + .allowEmptyExpressions(true) + .resolveAliases(true) + ) + .gatekeeperOptions( + IndicesOptions.GatekeeperOptions.builder().ignoreThrottled(true).allowClosedIndices(true).allowAliasToMultipleIndices(true) + ) + .build(); + private final Client client; private final DataTypeRegistry typeRegistry; - public EsqlIndexResolver(Client client, DataTypeRegistry typeRegistry) { + public IndexResolver(Client client, DataTypeRegistry typeRegistry) { this.client = client; this.typeRegistry = typeRegistry; } @@ -245,7 +264,7 @@ private static FieldCapabilitiesRequest createFieldCapsRequest(String index, Set req.includeUnmapped(true); // lenient because we throw our own errors looking at the response e.g. if something was not resolved // also because this way security doesn't throw authorization exceptions but rather honors ignore_unavailable - req.indicesOptions(IndexResolver.FIELD_CAPS_INDICES_OPTIONS); + req.indicesOptions(FIELD_CAPS_INDICES_OPTIONS); req.setMergeResults(false); return req; } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index 975d8e1c7d7b8..794bdc23f08c5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -53,7 +53,7 @@ import org.elasticsearch.xpack.esql.plan.logical.Row; import org.elasticsearch.xpack.esql.plan.logical.local.EsqlProject; import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; -import org.elasticsearch.xpack.esql.session.EsqlIndexResolver; +import org.elasticsearch.xpack.esql.session.IndexResolver; import org.elasticsearch.xpack.esql.type.EsqlDataTypeRegistry; import java.io.IOException; @@ -2106,7 +2106,7 @@ protected List filteredWarnings() { private static LogicalPlan analyzeWithEmptyFieldCapsResponse(String query) throws IOException { List idxResponses = List.of(new FieldCapabilitiesIndexResponse("idx", "idx", Map.of(), true)); FieldCapabilitiesResponse caps = new FieldCapabilitiesResponse(idxResponses, List.of()); - IndexResolution resolution = new EsqlIndexResolver(null, EsqlDataTypeRegistry.INSTANCE).mergedMappings("test*", caps); + IndexResolution resolution = new IndexResolver(null, EsqlDataTypeRegistry.INSTANCE).mergedMappings("test*", caps); var analyzer = analyzer(resolution, TEST_VERIFIER, configuration(query)); return analyze(query, analyzer); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolverTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolverTests.java index 90fca14b7b06d..9f81437bd1b77 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolverTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolverTests.java @@ -11,10 +11,12 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.ActionRunnable; import org.elasticsearch.action.ActionType; -import org.elasticsearch.action.fieldcaps.FieldCapabilities; +import org.elasticsearch.action.fieldcaps.FieldCapabilitiesIndexResponse; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse; +import org.elasticsearch.action.fieldcaps.IndexFieldCapabilities; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.client.internal.FilterClient; import org.elasticsearch.cluster.ClusterName; @@ -36,8 +38,8 @@ import org.elasticsearch.xpack.core.enrich.EnrichMetadata; import org.elasticsearch.xpack.core.enrich.EnrichPolicy; import org.elasticsearch.xpack.esql.analysis.EnrichResolution; -import org.elasticsearch.xpack.esql.core.index.IndexResolver; import org.elasticsearch.xpack.esql.plan.logical.Enrich; +import org.elasticsearch.xpack.esql.session.IndexResolver; import org.elasticsearch.xpack.esql.type.EsqlDataTypeRegistry; import org.junit.After; import org.junit.Before; @@ -416,7 +418,7 @@ class TestEnrichPolicyResolver extends EnrichPolicyResolver { super( mockClusterService(policies), transports.get(cluster), - new IndexResolver(new FieldCapsClient(threadPool, aliases, mappings), cluster, EsqlDataTypeRegistry.INSTANCE, Set::of) + new IndexResolver(new FieldCapsClient(threadPool, aliases, mappings), EsqlDataTypeRegistry.INSTANCE) ); this.policies = policies; this.cluster = cluster; @@ -483,30 +485,19 @@ protected void String alias = aliases.get(r.indices()[0]); assertNotNull(alias); Map mapping = mappings.get(alias); + final FieldCapabilitiesResponse response; if (mapping != null) { - Map> fieldCaps = new HashMap<>(); + Map fieldCaps = new HashMap<>(); for (Map.Entry e : mapping.entrySet()) { - var f = new FieldCapabilities( - e.getKey(), - e.getValue(), - false, - false, - false, - true, - null, - new String[] { alias }, - null, - null, - null, - null, - Map.of() - ); - fieldCaps.put(e.getKey(), Map.of(e.getValue(), f)); + var f = new IndexFieldCapabilities(e.getKey(), e.getValue(), false, false, false, false, null, Map.of()); + fieldCaps.put(e.getKey(), f); } - listener.onResponse((Response) new FieldCapabilitiesResponse(new String[] { alias }, fieldCaps)); + var indexResponse = new FieldCapabilitiesIndexResponse(alias, null, fieldCaps, true); + response = new FieldCapabilitiesResponse(List.of(indexResponse), List.of()); } else { - listener.onResponse((Response) new FieldCapabilitiesResponse(new String[0], Map.of())); + response = new FieldCapabilitiesResponse(List.of(), List.of()); } + threadPool().executor(ThreadPool.Names.SEARCH_COORDINATION).execute(ActionRunnable.supply(listener, () -> (Response) response)); } } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java index 8d1353cbddd42..17dca8096de0f 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java @@ -13,8 +13,8 @@ import java.util.Collections; import java.util.Set; -import static org.elasticsearch.xpack.esql.core.index.IndexResolver.ALL_FIELDS; -import static org.elasticsearch.xpack.esql.core.index.IndexResolver.INDEX_METADATA_FIELD; +import static org.elasticsearch.xpack.esql.session.IndexResolver.ALL_FIELDS; +import static org.elasticsearch.xpack.esql.session.IndexResolver.INDEX_METADATA_FIELD; import static org.hamcrest.Matchers.equalTo; public class IndexResolverFieldNamesTests extends ESTestCase { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/PlanExecutorMetricsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/PlanExecutorMetricsTests.java index d3011506bb5ef..5883d41f32125 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/PlanExecutorMetricsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/PlanExecutorMetricsTests.java @@ -20,11 +20,10 @@ import org.elasticsearch.xpack.esql.VerificationException; import org.elasticsearch.xpack.esql.action.EsqlQueryRequest; import org.elasticsearch.xpack.esql.analysis.EnrichResolution; -import org.elasticsearch.xpack.esql.core.index.IndexResolver; import org.elasticsearch.xpack.esql.enrich.EnrichPolicyResolver; import org.elasticsearch.xpack.esql.execution.PlanExecutor; import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan; -import org.elasticsearch.xpack.esql.session.EsqlIndexResolver; +import org.elasticsearch.xpack.esql.session.IndexResolver; import org.elasticsearch.xpack.esql.type.EsqlDataTypeRegistry; import org.junit.After; import org.junit.Before; @@ -34,7 +33,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.hamcrest.Matchers.instanceOf; @@ -73,7 +71,7 @@ public void testFailedMetric() { String[] indices = new String[] { "test" }; Client qlClient = mock(Client.class); - IndexResolver idxResolver = new IndexResolver(qlClient, randomAlphaOfLength(10), EsqlDataTypeRegistry.INSTANCE, Set::of); + IndexResolver idxResolver = new IndexResolver(qlClient, EsqlDataTypeRegistry.INSTANCE); // simulate a valid field_caps response so we can parse and correctly analyze de query FieldCapabilitiesResponse fieldCapabilitiesResponse = mock(FieldCapabilitiesResponse.class); when(fieldCapabilitiesResponse.getIndices()).thenReturn(indices); @@ -87,7 +85,7 @@ public void testFailedMetric() { }).when(qlClient).fieldCaps(any(), any()); Client esqlClient = mock(Client.class); - EsqlIndexResolver esqlIndexResolver = new EsqlIndexResolver(esqlClient, EsqlDataTypeRegistry.INSTANCE); + IndexResolver indexResolver = new IndexResolver(esqlClient, EsqlDataTypeRegistry.INSTANCE); doAnswer((Answer) invocation -> { @SuppressWarnings("unchecked") ActionListener listener = (ActionListener) invocation.getArguments()[1]; @@ -96,7 +94,7 @@ public void testFailedMetric() { return null; }).when(esqlClient).fieldCaps(any(), any()); - var planExecutor = new PlanExecutor(idxResolver, esqlIndexResolver); + var planExecutor = new PlanExecutor(indexResolver); var enrichResolver = mockEnrichResolver(); var request = new EsqlQueryRequest(); 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 7dca73219d6a1..ad7be1e38681f 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 @@ -14,7 +14,7 @@ import org.elasticsearch.xpack.esql.core.index.IndexResolution; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.EsField; -import org.elasticsearch.xpack.esql.session.EsqlIndexResolver; +import org.elasticsearch.xpack.esql.session.IndexResolver; import java.util.List; import java.util.Map; @@ -51,7 +51,7 @@ private void resolve(String esTypeName, TimeSeriesParams.MetricType metricType, ); FieldCapabilitiesResponse caps = new FieldCapabilitiesResponse(idxResponses, List.of()); - IndexResolution resolution = new EsqlIndexResolver(null, EsqlDataTypeRegistry.INSTANCE).mergedMappings("idx-*", caps); + IndexResolution resolution = new IndexResolver(null, EsqlDataTypeRegistry.INSTANCE).mergedMappings("idx-*", caps); EsField f = resolution.get().mapping().get(field); assertThat(f.getDataType(), equalTo(expected)); }