Skip to content

Commit 127b8d0

Browse files
authored
Add support for aliases in queries on _index. (#46640)
Previously, queries on the _index field were not able to specify index aliases. This was a regression in functionality compared to the 'indices' query that was deprecated and removed in 6.0. Now queries on _index can specify an alias, which is resolved to the concrete index names when we check whether an index matches. To match a remote shard target, the pattern needs to be of the form 'cluster:index' to match the fully-qualified index name. Index aliases can be specified in the following query types: term, terms, prefix, and wildcard.
1 parent ba9940b commit 127b8d0

File tree

32 files changed

+362
-139
lines changed

32 files changed

+362
-139
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
setup:
3+
- do:
4+
indices.create:
5+
index: single_doc_index
6+
body:
7+
settings:
8+
index:
9+
number_of_shards: 1
10+
number_of_replicas: 0
11+
---
12+
teardown:
13+
- do:
14+
indices.delete:
15+
index: single_doc_index
16+
ignore_unavailable: true
17+
18+
---
19+
"Test that queries on _index match against the correct indices.":
20+
21+
- do:
22+
bulk:
23+
refresh: true
24+
body:
25+
- '{"index": {"_index": "single_doc_index"}}'
26+
- '{"f1": "local_cluster", "sort_field": 0}'
27+
28+
- do:
29+
search:
30+
rest_total_hits_as_int: true
31+
index: "single_doc_index,my_remote_cluster:single_doc_index"
32+
body:
33+
query:
34+
term:
35+
"_index": "single_doc_index"
36+
37+
- match: { hits.total: 1 }
38+
- match: { hits.hits.0._index: "single_doc_index"}
39+
- match: { _shards.total: 2 }
40+
- match: { _shards.successful: 2 }
41+
- match: { _shards.skipped : 0}
42+
- match: { _shards.failed: 0 }
43+
44+
- do:
45+
search:
46+
rest_total_hits_as_int: true
47+
index: "single_doc_index,my_remote_cluster:single_doc_index"
48+
body:
49+
query:
50+
term:
51+
"_index": "my_remote_cluster:single_doc_index"
52+
53+
- match: { hits.total: 1 }
54+
- match: { hits.hits.0._index: "my_remote_cluster:single_doc_index"}
55+
- match: { _shards.total: 2 }
56+
- match: { _shards.successful: 2 }
57+
- match: { _shards.skipped : 0}
58+
- match: { _shards.failed: 0 }

server/src/main/java/org/elasticsearch/index/IndexModule.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.lucene.util.SetOnce;
3131
import org.elasticsearch.Version;
3232
import org.elasticsearch.client.Client;
33+
import org.elasticsearch.cluster.service.ClusterService;
3334
import org.elasticsearch.common.CheckedFunction;
3435
import org.elasticsearch.common.TriFunction;
3536
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
@@ -386,6 +387,7 @@ public IndexService newIndexService(
386387
BigArrays bigArrays,
387388
ThreadPool threadPool,
388389
ScriptService scriptService,
390+
ClusterService clusterService,
389391
Client client,
390392
IndicesQueryCache indicesQueryCache,
391393
MapperRegistry mapperRegistry,
@@ -411,7 +413,7 @@ public IndexService newIndexService(
411413
return new IndexService(indexSettings, indexCreationContext, environment, xContentRegistry,
412414
new SimilarityService(indexSettings, scriptService, similarities),
413415
shardStoreDeleter, analysisRegistry, engineFactory, circuitBreakerService, bigArrays, threadPool, scriptService,
414-
client, queryCache, directoryFactory, eventListener, readerWrapperFactory, mapperRegistry,
416+
clusterService, client, queryCache, directoryFactory, eventListener, readerWrapperFactory, mapperRegistry,
415417
indicesFieldDataCache, searchOperationListeners, indexOperationListeners, namedWriteableRegistry);
416418
}
417419

server/src/main/java/org/elasticsearch/index/IndexService.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.elasticsearch.client.Client;
3232
import org.elasticsearch.cluster.metadata.IndexMetaData;
3333
import org.elasticsearch.cluster.routing.ShardRouting;
34+
import org.elasticsearch.cluster.service.ClusterService;
3435
import org.elasticsearch.common.CheckedFunction;
3536
import org.elasticsearch.common.Nullable;
3637
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
@@ -57,6 +58,7 @@
5758
import org.elasticsearch.index.fielddata.IndexFieldDataCache;
5859
import org.elasticsearch.index.fielddata.IndexFieldDataService;
5960
import org.elasticsearch.index.mapper.MapperService;
61+
import org.elasticsearch.index.query.SearchIndexNameMatcher;
6062
import org.elasticsearch.index.query.QueryShardContext;
6163
import org.elasticsearch.index.seqno.RetentionLeaseSyncer;
6264
import org.elasticsearch.index.shard.IndexEventListener;
@@ -133,6 +135,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
133135
private final ThreadPool threadPool;
134136
private final BigArrays bigArrays;
135137
private final ScriptService scriptService;
138+
private final ClusterService clusterService;
136139
private final Client client;
137140
private final CircuitBreakerService circuitBreakerService;
138141
private Supplier<Sort> indexSortSupplier;
@@ -150,6 +153,7 @@ public IndexService(
150153
BigArrays bigArrays,
151154
ThreadPool threadPool,
152155
ScriptService scriptService,
156+
ClusterService clusterService,
153157
Client client,
154158
QueryCache queryCache,
155159
IndexStorePlugin.DirectoryFactory directoryFactory,
@@ -200,6 +204,7 @@ public IndexService(
200204
this.bigArrays = bigArrays;
201205
this.threadPool = threadPool;
202206
this.scriptService = scriptService;
207+
this.clusterService = clusterService;
203208
this.client = client;
204209
this.eventListener = eventListener;
205210
this.nodeEnv = nodeEnv;
@@ -528,9 +533,11 @@ public IndexSettings getIndexSettings() {
528533
* {@link IndexReader}-specific optimizations, such as rewriting containing range queries.
529534
*/
530535
public QueryShardContext newQueryShardContext(int shardId, IndexSearcher searcher, LongSupplier nowInMillis, String clusterAlias) {
536+
SearchIndexNameMatcher indexNameMatcher = new SearchIndexNameMatcher(index().getName(), clusterAlias, clusterService);
531537
return new QueryShardContext(
532538
shardId, indexSettings, bigArrays, indexCache.bitsetFilterCache(), indexFieldData::getForField, mapperService(),
533-
similarityService(), scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis, clusterAlias);
539+
similarityService(), scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis, clusterAlias,
540+
indexNameMatcher);
534541
}
535542

536543
/**

server/src/main/java/org/elasticsearch/index/mapper/IndexFieldMapper.java

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,16 @@ public Query existsQuery(QueryShardContext context) {
129129
*/
130130
@Override
131131
public Query termQuery(Object value, @Nullable QueryShardContext context) {
132-
if (isSameIndex(value, context.getFullyQualifiedIndex().getName())) {
132+
String pattern = value instanceof BytesRef
133+
? ((BytesRef) value).utf8ToString()
134+
: value.toString();
135+
if (context.indexMatches(pattern)) {
136+
// No need to OR these clauses - we can only logically be
137+
// running in the context of just one of these index names.
133138
return Queries.newMatchAllQuery();
134139
} else {
135-
return Queries.newMatchNoDocsQuery("Index didn't match. Index queried: " + context.index().getName()
136-
+ " vs. " + value);
140+
return Queries.newMatchNoDocsQuery("The index [" + context.getFullyQualifiedIndex().getName() +
141+
"] doesn't match the provided value [" + value + "].");
137142
}
138143
}
139144

@@ -143,26 +148,29 @@ public Query termsQuery(List values, QueryShardContext context) {
143148
return super.termsQuery(values, context);
144149
}
145150
for (Object value : values) {
146-
if (isSameIndex(value, context.getFullyQualifiedIndex().getName())) {
151+
String pattern = value instanceof BytesRef
152+
? ((BytesRef) value).utf8ToString()
153+
: value.toString();
154+
if (context.indexMatches(pattern)) {
147155
// No need to OR these clauses - we can only logically be
148156
// running in the context of just one of these index names.
149157
return Queries.newMatchAllQuery();
150158
}
151159
}
152160
// None of the listed index names are this one
153-
return Queries.newMatchNoDocsQuery("Index didn't match. Index queried: " + context.getFullyQualifiedIndex().getName()
154-
+ " vs. " + values);
161+
return Queries.newMatchNoDocsQuery("The index [" + context.getFullyQualifiedIndex().getName() +
162+
"] doesn't match the provided values [" + values + "].");
155163
}
156164

157165
@Override
158166
public Query prefixQuery(String value,
159167
@Nullable MultiTermQuery.RewriteMethod method,
160168
QueryShardContext context) {
161-
String indexName = context.getFullyQualifiedIndex().getName();
162-
if (indexName.startsWith(value)) {
169+
String pattern = value + "*";
170+
if (context.indexMatches(pattern)) {
163171
return Queries.newMatchAllQuery();
164172
} else {
165-
return Queries.newMatchNoDocsQuery("The index [" + indexName +
173+
return Queries.newMatchNoDocsQuery("The index [" + context.getFullyQualifiedIndex().getName() +
166174
"] doesn't match the provided prefix [" + value + "].");
167175
}
168176
}
@@ -176,29 +184,23 @@ public Query regexpQuery(String value, int flags, int maxDeterminizedStates,
176184
if (pattern.matcher(indexName).matches()) {
177185
return Queries.newMatchAllQuery();
178186
} else {
179-
return Queries.newMatchNoDocsQuery("The index [" + indexName +
180-
"] doesn't match the provided pattern [" + value + "].");
187+
return Queries.newMatchNoDocsQuery("The index [" + context.getFullyQualifiedIndex().getName()
188+
+ "] doesn't match the provided pattern [" + value + "].");
181189
}
182190
}
183191

184192
@Override
185193
public Query wildcardQuery(String value,
186194
@Nullable MultiTermQuery.RewriteMethod method,
187195
QueryShardContext context) {
188-
String indexName = context.getFullyQualifiedIndex().getName();
189-
if (isSameIndex(value, indexName)) {
196+
if (context.indexMatches(value)) {
190197
return Queries.newMatchAllQuery();
191198
} else {
192-
return Queries.newMatchNoDocsQuery("The index [" + indexName +
193-
"] doesn't match the provided pattern [" + value + "].");
199+
return Queries.newMatchNoDocsQuery("The index [" + context.getFullyQualifiedIndex().getName()
200+
+ "] doesn't match the provided pattern [" + value + "].");
194201
}
195202
}
196203

197-
private boolean isSameIndex(Object value, String indexName) {
198-
String pattern = value instanceof BytesRef ? ((BytesRef) value).utf8ToString() : value.toString();
199-
return Regex.simpleMatch(pattern, indexName);
200-
}
201-
202204
@Override
203205
public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName) {
204206
return new ConstantIndexFieldData.Builder(mapperService -> fullyQualifiedIndexName);

server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import java.util.function.BiConsumer;
6666
import java.util.function.BiFunction;
6767
import java.util.function.LongSupplier;
68+
import java.util.function.Predicate;
6869

6970
/**
7071
* Context object used to create lucene queries on the shard level.
@@ -86,53 +87,58 @@ public class QueryShardContext extends QueryRewriteContext {
8687
private final IndexSearcher searcher;
8788
private boolean cacheable = true;
8889
private final SetOnce<Boolean> frozen = new SetOnce<>();
90+
8991
private final Index fullyQualifiedIndex;
92+
private final Predicate<String> indexNameMatcher;
9093

9194
private final Map<String, Query> namedQueries = new HashMap<>();
9295
private boolean allowUnmappedFields;
9396
private boolean mapUnmappedFieldAsString;
9497
private NestedScope nestedScope;
9598

9699
public QueryShardContext(int shardId,
97-
IndexSettings indexSettings,
98-
BigArrays bigArrays,
99-
BitsetFilterCache bitsetFilterCache,
100-
BiFunction<MappedFieldType, String, IndexFieldData<?>> indexFieldDataLookup,
101-
MapperService mapperService,
102-
SimilarityService similarityService,
103-
ScriptService scriptService,
104-
NamedXContentRegistry xContentRegistry,
105-
NamedWriteableRegistry namedWriteableRegistry,
106-
Client client,
107-
IndexSearcher searcher,
108-
LongSupplier nowInMillis,
109-
String clusterAlias) {
100+
IndexSettings indexSettings,
101+
BigArrays bigArrays,
102+
BitsetFilterCache bitsetFilterCache,
103+
BiFunction<MappedFieldType, String, IndexFieldData<?>> indexFieldDataLookup,
104+
MapperService mapperService,
105+
SimilarityService similarityService,
106+
ScriptService scriptService,
107+
NamedXContentRegistry xContentRegistry,
108+
NamedWriteableRegistry namedWriteableRegistry,
109+
Client client,
110+
IndexSearcher searcher,
111+
LongSupplier nowInMillis,
112+
String clusterAlias,
113+
Predicate<String> indexNameMatcher) {
110114
this(shardId, indexSettings, bigArrays, bitsetFilterCache, indexFieldDataLookup, mapperService, similarityService,
111-
scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis,
115+
scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis, indexNameMatcher,
112116
new Index(RemoteClusterAware.buildRemoteIndexName(clusterAlias, indexSettings.getIndex().getName()),
113117
indexSettings.getIndex().getUUID()));
114118
}
115119

116120
public QueryShardContext(QueryShardContext source) {
117121
this(source.shardId, source.indexSettings, source.bigArrays, source.bitsetFilterCache, source.indexFieldDataService,
118-
source.mapperService, source.similarityService, source.scriptService, source.getXContentRegistry(),
119-
source.getWriteableRegistry(), source.client, source.searcher, source.nowInMillis, source.fullyQualifiedIndex);
122+
source.mapperService, source.similarityService, source.scriptService, source.getXContentRegistry(),
123+
source.getWriteableRegistry(), source.client, source.searcher, source.nowInMillis, source.indexNameMatcher,
124+
source.fullyQualifiedIndex);
120125
}
121126

122127
private QueryShardContext(int shardId,
123-
IndexSettings indexSettings,
124-
BigArrays bigArrays,
125-
BitsetFilterCache bitsetFilterCache,
126-
BiFunction<MappedFieldType, String, IndexFieldData<?>> indexFieldDataLookup,
127-
MapperService mapperService,
128-
SimilarityService similarityService,
129-
ScriptService scriptService,
130-
NamedXContentRegistry xContentRegistry,
131-
NamedWriteableRegistry namedWriteableRegistry,
132-
Client client,
133-
IndexSearcher searcher,
134-
LongSupplier nowInMillis,
135-
Index fullyQualifiedIndex) {
128+
IndexSettings indexSettings,
129+
BigArrays bigArrays,
130+
BitsetFilterCache bitsetFilterCache,
131+
BiFunction<MappedFieldType, String, IndexFieldData<?>> indexFieldDataLookup,
132+
MapperService mapperService,
133+
SimilarityService similarityService,
134+
ScriptService scriptService,
135+
NamedXContentRegistry xContentRegistry,
136+
NamedWriteableRegistry namedWriteableRegistry,
137+
Client client,
138+
IndexSearcher searcher,
139+
LongSupplier nowInMillis,
140+
Predicate<String> indexNameMatcher,
141+
Index fullyQualifiedIndex) {
136142
super(xContentRegistry, namedWriteableRegistry, client, nowInMillis);
137143
this.shardId = shardId;
138144
this.similarityService = similarityService;
@@ -145,6 +151,7 @@ private QueryShardContext(int shardId,
145151
this.scriptService = scriptService;
146152
this.indexSettings = indexSettings;
147153
this.searcher = searcher;
154+
this.indexNameMatcher = indexNameMatcher;
148155
this.fullyQualifiedIndex = fullyQualifiedIndex;
149156
}
150157

@@ -284,6 +291,14 @@ public Version indexVersionCreated() {
284291
return indexSettings.getIndexVersionCreated();
285292
}
286293

294+
/**
295+
* Given an index pattern, checks whether it matches against the current shard. The pattern
296+
* may represent a fully qualified index name if the search targets remote shards.
297+
*/
298+
public boolean indexMatches(String pattern) {
299+
return indexNameMatcher.test(pattern);
300+
}
301+
287302
public ParsedQuery toQuery(QueryBuilder queryBuilder) {
288303
return toQuery(queryBuilder, q -> {
289304
Query query = q.toQuery(this);

0 commit comments

Comments
 (0)