Skip to content

Commit 9124c94

Browse files
authored
Add support for aliases in queries on _index. (elastic#46944)
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 08f28e6 commit 9124c94

File tree

33 files changed

+392
-139
lines changed

33 files changed

+392
-139
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[[breaking-changes-7.5]]
2+
== Breaking changes in 7.5
3+
++++
4+
<titleabbrev>7.5</titleabbrev>
5+
++++
6+
7+
This section discusses the changes that you need to be aware of when migrating
8+
your application to Elasticsearch 7.5.
9+
10+
See also <<release-highlights>> and <<es-release-notes>>.
11+
12+
coming[7.5.0]
13+
14+
//NOTE: The notable-breaking-changes tagged regions are re-used in the
15+
//Installation and Upgrade Guide
16+
17+
//tag::notable-breaking-changes[]
18+
19+
//end::notable-breaking-changes[]
20+
21+
[discrete]
22+
[[breaking_75_search_changes]]
23+
=== Search Changes
24+
25+
[discrete]
26+
==== Stricter checking for wildcard queries on _index
27+
Previously, a wildcard query on the `_index` field matched directly against the
28+
fully-qualified index name. Now, in order to match against remote indices like
29+
i`cluster:index`, the query must contain a colon, as in `cl*ster:inde*`. This
30+
behavior aligns with the way indices are matched in the search endpoint.
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
@@ -32,6 +32,7 @@
3232
import org.elasticsearch.client.Client;
3333
import org.elasticsearch.cluster.metadata.IndexMetaData;
3434
import org.elasticsearch.cluster.routing.ShardRouting;
35+
import org.elasticsearch.cluster.service.ClusterService;
3536
import org.elasticsearch.common.CheckedFunction;
3637
import org.elasticsearch.common.Nullable;
3738
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;
@@ -134,6 +136,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
134136
private final ThreadPool threadPool;
135137
private final BigArrays bigArrays;
136138
private final ScriptService scriptService;
139+
private final ClusterService clusterService;
137140
private final Client client;
138141
private final CircuitBreakerService circuitBreakerService;
139142
private Supplier<Sort> indexSortSupplier;
@@ -151,6 +154,7 @@ public IndexService(
151154
BigArrays bigArrays,
152155
ThreadPool threadPool,
153156
ScriptService scriptService,
157+
ClusterService clusterService,
154158
Client client,
155159
QueryCache queryCache,
156160
IndexStorePlugin.DirectoryFactory directoryFactory,
@@ -201,6 +205,7 @@ public IndexService(
201205
this.bigArrays = bigArrays;
202206
this.threadPool = threadPool;
203207
this.scriptService = scriptService;
208+
this.clusterService = clusterService;
204209
this.client = client;
205210
this.eventListener = eventListener;
206211
this.nodeEnv = nodeEnv;
@@ -530,9 +535,11 @@ public IndexSettings getIndexSettings() {
530535
* {@link IndexReader}-specific optimizations, such as rewriting containing range queries.
531536
*/
532537
public QueryShardContext newQueryShardContext(int shardId, IndexSearcher searcher, LongSupplier nowInMillis, String clusterAlias) {
538+
SearchIndexNameMatcher indexNameMatcher = new SearchIndexNameMatcher(index().getName(), clusterAlias, clusterService);
533539
return new QueryShardContext(
534540
shardId, indexSettings, bigArrays, indexCache.bitsetFilterCache(), indexFieldData::getForField, mapperService(),
535-
similarityService(), scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis, clusterAlias);
541+
similarityService(), scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis, clusterAlias,
542+
indexNameMatcher);
536543
}
537544

538545
/**

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
@@ -69,6 +69,7 @@
6969
import java.util.function.BiConsumer;
7070
import java.util.function.BiFunction;
7171
import java.util.function.LongSupplier;
72+
import java.util.function.Predicate;
7273

7374
import static java.util.Collections.unmodifiableMap;
7475

@@ -93,7 +94,9 @@ public class QueryShardContext extends QueryRewriteContext {
9394
private String[] types = Strings.EMPTY_ARRAY;
9495
private boolean cacheable = true;
9596
private final SetOnce<Boolean> frozen = new SetOnce<>();
97+
9698
private final Index fullyQualifiedIndex;
99+
private final Predicate<String> indexNameMatcher;
97100

98101
public void setTypes(String... types) {
99102
this.types = types;
@@ -109,45 +112,48 @@ public String[] getTypes() {
109112
private NestedScope nestedScope;
110113

111114
public QueryShardContext(int shardId,
112-
IndexSettings indexSettings,
113-
BigArrays bigArrays,
114-
BitsetFilterCache bitsetFilterCache,
115-
BiFunction<MappedFieldType, String, IndexFieldData<?>> indexFieldDataLookup,
116-
MapperService mapperService,
117-
SimilarityService similarityService,
118-
ScriptService scriptService,
119-
NamedXContentRegistry xContentRegistry,
120-
NamedWriteableRegistry namedWriteableRegistry,
121-
Client client,
122-
IndexSearcher searcher,
123-
LongSupplier nowInMillis,
124-
String clusterAlias) {
115+
IndexSettings indexSettings,
116+
BigArrays bigArrays,
117+
BitsetFilterCache bitsetFilterCache,
118+
BiFunction<MappedFieldType, String, IndexFieldData<?>> indexFieldDataLookup,
119+
MapperService mapperService,
120+
SimilarityService similarityService,
121+
ScriptService scriptService,
122+
NamedXContentRegistry xContentRegistry,
123+
NamedWriteableRegistry namedWriteableRegistry,
124+
Client client,
125+
IndexSearcher searcher,
126+
LongSupplier nowInMillis,
127+
String clusterAlias,
128+
Predicate<String> indexNameMatcher) {
125129
this(shardId, indexSettings, bigArrays, bitsetFilterCache, indexFieldDataLookup, mapperService, similarityService,
126-
scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis,
130+
scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis, indexNameMatcher,
127131
new Index(RemoteClusterAware.buildRemoteIndexName(clusterAlias, indexSettings.getIndex().getName()),
128132
indexSettings.getIndex().getUUID()));
129133
}
130134

131135
public QueryShardContext(QueryShardContext source) {
132136
this(source.shardId, source.indexSettings, source.bigArrays, source.bitsetFilterCache, source.indexFieldDataService,
133-
source.mapperService, source.similarityService, source.scriptService, source.getXContentRegistry(),
134-
source.getWriteableRegistry(), source.client, source.searcher, source.nowInMillis, source.fullyQualifiedIndex);
137+
source.mapperService, source.similarityService, source.scriptService, source.getXContentRegistry(),
138+
source.getWriteableRegistry(), source.client, source.searcher, source.nowInMillis, source.indexNameMatcher,
139+
source.fullyQualifiedIndex);
135140
}
136141

137142
private QueryShardContext(int shardId,
138-
IndexSettings indexSettings,
139-
BigArrays bigArrays,
140-
BitsetFilterCache bitsetFilterCache,
141-
BiFunction<MappedFieldType, String, IndexFieldData<?>> indexFieldDataLookup,
142-
MapperService mapperService,
143-
SimilarityService similarityService,
144-
ScriptService scriptService,
145-
NamedXContentRegistry xContentRegistry,
146-
NamedWriteableRegistry namedWriteableRegistry,
147-
Client client,
148-
IndexSearcher searcher,
149-
LongSupplier nowInMillis,
150-
Index fullyQualifiedIndex) {
143+
IndexSettings indexSettings,
144+
BigArrays bigArrays,
145+
BitsetFilterCache bitsetFilterCache,
146+
BiFunction<MappedFieldType, String, IndexFieldData<?>> indexFieldDataLookup,
147+
MapperService mapperService,
148+
SimilarityService similarityService,
149+
ScriptService scriptService,
150+
NamedXContentRegistry xContentRegistry,
151+
NamedWriteableRegistry namedWriteableRegistry,
152+
Client client,
153+
IndexSearcher searcher,
154+
LongSupplier nowInMillis,
155+
Predicate<String> indexNameMatcher,
156+
Index fullyQualifiedIndex) {
151157
super(xContentRegistry, namedWriteableRegistry, client, nowInMillis);
152158
this.shardId = shardId;
153159
this.similarityService = similarityService;
@@ -160,6 +166,7 @@ private QueryShardContext(int shardId,
160166
this.scriptService = scriptService;
161167
this.indexSettings = indexSettings;
162168
this.searcher = searcher;
169+
this.indexNameMatcher = indexNameMatcher;
163170
this.fullyQualifiedIndex = fullyQualifiedIndex;
164171
}
165172

@@ -311,6 +318,14 @@ public Version indexVersionCreated() {
311318
return indexSettings.getIndexVersionCreated();
312319
}
313320

321+
/**
322+
* Given an index pattern, checks whether it matches against the current shard. The pattern
323+
* may represent a fully qualified index name if the search targets remote shards.
324+
*/
325+
public boolean indexMatches(String pattern) {
326+
return indexNameMatcher.test(pattern);
327+
}
328+
314329
public ParsedQuery toQuery(QueryBuilder queryBuilder) {
315330
return toQuery(queryBuilder, q -> {
316331
Query query = q.toQuery(this);

0 commit comments

Comments
 (0)