Skip to content

Commit a8a8c71

Browse files
authored
Fix searching a filtered and unfiltered data stream alias (#95865) (#96010)
This fixes the case when a `_search` request is targeting multiple aliases that point to the _same data stream_, some being filtered and some unfiltered. Before this fix, such a scenario would apply the encountered filters as `should` conditions on a `BoolQuery`, despite the request containing an unfiltered (match_all) alias for the same data stream. This fixes this scenario to disregard the filters when an unfiltered alias is encountered in the resolved expressions, targeting the same data stream. This also fixes the same scenario for a request that targets the data stream name and a filtered alias (the expected result is that the filter should be disregarded) Fixes #95786
1 parent bd3a68c commit a8a8c71

File tree

5 files changed

+137
-17
lines changed

5 files changed

+137
-17
lines changed

docs/changelog/95865.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 95865
2+
summary: Fix searching a filtered and unfiltered data stream alias
3+
area: Data streams
4+
type: bug
5+
issues:
6+
- 95786

modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamIT.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
import static org.hamcrest.Matchers.greaterThan;
134134
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
135135
import static org.hamcrest.Matchers.hasItemInArray;
136+
import static org.hamcrest.Matchers.hasKey;
136137
import static org.hamcrest.Matchers.hasSize;
137138
import static org.hamcrest.Matchers.instanceOf;
138139
import static org.hamcrest.Matchers.is;
@@ -801,6 +802,53 @@ public void testDataSteamAliasWithFilter() throws Exception {
801802
assertSearchHits(searchResponse, "1");
802803
}
803804

805+
public void testSearchFilteredAndUnfilteredAlias() throws Exception {
806+
putComposableIndexTemplate("id1", List.of("logs-*"));
807+
String dataStreamName = "logs-foobar";
808+
client().prepareIndex(dataStreamName)
809+
.setId("1")
810+
.setSource("{\"@timestamp\": \"2022-12-12\", \"type\": \"x\"}", XContentType.JSON)
811+
.setOpType(DocWriteRequest.OpType.CREATE)
812+
.get();
813+
client().prepareIndex(dataStreamName)
814+
.setId("2")
815+
.setSource("{\"@timestamp\": \"2022-12-12\", \"type\": \"y\"}", XContentType.JSON)
816+
.setOpType(DocWriteRequest.OpType.CREATE)
817+
.get();
818+
refresh(dataStreamName);
819+
820+
AliasActions addFilteredAliasAction = new AliasActions(AliasActions.Type.ADD).index(dataStreamName)
821+
.aliases("foo")
822+
.filter(Map.of("term", Map.of("type", Map.of("value", "y"))));
823+
AliasActions addUnfilteredAliasAction = new AliasActions(AliasActions.Type.ADD).index(dataStreamName).aliases("bar");
824+
825+
IndicesAliasesRequest aliasesAddRequest = new IndicesAliasesRequest();
826+
aliasesAddRequest.addAliasAction(addFilteredAliasAction);
827+
aliasesAddRequest.addAliasAction(addUnfilteredAliasAction);
828+
assertAcked(client().admin().indices().aliases(aliasesAddRequest).actionGet());
829+
GetAliasesResponse response = client().admin().indices().getAliases(new GetAliasesRequest()).actionGet();
830+
assertThat(response.getDataStreamAliases(), hasKey("logs-foobar"));
831+
assertThat(
832+
response.getDataStreamAliases().get("logs-foobar"),
833+
containsInAnyOrder(
834+
new DataStreamAlias("bar", List.of("logs-foobar"), null, null),
835+
new DataStreamAlias(
836+
"foo",
837+
List.of("logs-foobar"),
838+
null,
839+
Map.of("logs-foobar", Map.of("term", Map.of("type", Map.of("value", "y"))))
840+
)
841+
)
842+
);
843+
844+
// Searching the filtered and unfiltered aliases should return all results (unfiltered):
845+
SearchResponse searchResponse = client().prepareSearch("foo", "bar").get();
846+
assertSearchHits(searchResponse, "1", "2");
847+
// Searching the data stream name and the filtered alias should return all results (unfiltered):
848+
searchResponse = client().prepareSearch("foo", dataStreamName).get();
849+
assertSearchHits(searchResponse, "1", "2");
850+
}
851+
804852
public void testRandomDataSteamAliasesUpdate() throws Exception {
805853
putComposableIndexTemplate("id1", List.of("log-*"));
806854

server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -701,19 +701,41 @@ public String[] indexAliases(
701701
IndexAbstraction ia = state.metadata().getIndicesLookup().get(index);
702702
DataStream dataStream = ia.getParentDataStream();
703703
if (dataStream != null) {
704+
if (skipIdentity == false && resolvedExpressions.contains(dataStream.getName())) {
705+
// skip the filters when the request targets the data stream name
706+
return null;
707+
}
704708
Map<String, DataStreamAlias> dataStreamAliases = state.metadata().dataStreamAliases();
705-
Stream<DataStreamAlias> stream;
709+
List<DataStreamAlias> aliasesForDataStream;
706710
if (iterateIndexAliases(dataStreamAliases.size(), resolvedExpressions.size())) {
707-
stream = dataStreamAliases.values()
711+
aliasesForDataStream = dataStreamAliases.values()
708712
.stream()
709-
.filter(dataStreamAlias -> resolvedExpressions.contains(dataStreamAlias.getName()));
713+
.filter(dataStreamAlias -> resolvedExpressions.contains(dataStreamAlias.getName()))
714+
.filter(dataStreamAlias -> dataStreamAlias.getDataStreams().contains(dataStream.getName()))
715+
.toList();
710716
} else {
711-
stream = resolvedExpressions.stream().map(dataStreamAliases::get).filter(Objects::nonNull);
717+
aliasesForDataStream = resolvedExpressions.stream()
718+
.map(dataStreamAliases::get)
719+
.filter(dataStreamAlias -> dataStreamAlias != null && dataStreamAlias.getDataStreams().contains(dataStream.getName()))
720+
.toList();
721+
}
722+
723+
List<String> requiredAliases = null;
724+
for (DataStreamAlias dataStreamAlias : aliasesForDataStream) {
725+
if (requiredDataStreamAlias.test(dataStreamAlias)) {
726+
if (requiredAliases == null) {
727+
requiredAliases = new ArrayList<>(aliasesForDataStream.size());
728+
}
729+
requiredAliases.add(dataStreamAlias.getName());
730+
} else {
731+
// we have a non-required alias for this data stream so no need to check further
732+
return null;
733+
}
734+
}
735+
if (requiredAliases == null) {
736+
return null;
712737
}
713-
return stream.filter(dataStreamAlias -> dataStreamAlias.getDataStreams().contains(dataStream.getName()))
714-
.filter(requiredDataStreamAlias)
715-
.map(DataStreamAlias::getName)
716-
.toArray(String[]::new);
738+
return requiredAliases.toArray(Strings.EMPTY_ARRAY);
717739
} else {
718740
final Map<String, AliasMetadata> indexAliases = indexMetadata.getAliases();
719741
final AliasMetadata[] aliasCandidates;

server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,9 +1672,8 @@ public void testIndexAliasesDataStreamAliases() {
16721672
String[] result = indexNameExpressionResolver.indexAliases(state, index, x -> true, x -> true, false, resolvedExpressions);
16731673
assertThat(result, nullValue());
16741674
}
1675-
16761675
{
1677-
// Only resolve aliases that refer to dataStreamName1 where the filter is not null
1676+
// Null is returned, because the wildcard expands to a list of aliases containing an unfiltered alias for dataStreamName1
16781677
Set<String> resolvedExpressions = indexNameExpressionResolver.resolveExpressions(state, "l*");
16791678
String index = backingIndex1.getIndex().getName();
16801679
String[] result = indexNameExpressionResolver.indexAliases(
@@ -1685,7 +1684,49 @@ public void testIndexAliasesDataStreamAliases() {
16851684
true,
16861685
resolvedExpressions
16871686
);
1688-
assertThat(result, arrayContainingInAnyOrder("logs", "logs_foo"));
1687+
assertThat(result, nullValue());
1688+
}
1689+
{
1690+
// Null is returned, because an unfiltered alias is targeting the same data stream
1691+
Set<String> resolvedExpressions = indexNameExpressionResolver.resolveExpressions(state, "logs_bar", "logs");
1692+
String index = backingIndex1.getIndex().getName();
1693+
String[] result = indexNameExpressionResolver.indexAliases(
1694+
state,
1695+
index,
1696+
x -> true,
1697+
DataStreamAlias::filteringRequired,
1698+
true,
1699+
resolvedExpressions
1700+
);
1701+
assertThat(result, nullValue());
1702+
}
1703+
{
1704+
// The filtered alias is returned because although we target the data stream name, skipIdentity is true
1705+
Set<String> resolvedExpressions = indexNameExpressionResolver.resolveExpressions(state, dataStreamName1, "logs");
1706+
String index = backingIndex1.getIndex().getName();
1707+
String[] result = indexNameExpressionResolver.indexAliases(
1708+
state,
1709+
index,
1710+
x -> true,
1711+
DataStreamAlias::filteringRequired,
1712+
true,
1713+
resolvedExpressions
1714+
);
1715+
assertThat(result, arrayContainingInAnyOrder("logs"));
1716+
}
1717+
{
1718+
// Null is returned because we target the data stream name and skipIdentity is false
1719+
Set<String> resolvedExpressions = indexNameExpressionResolver.resolveExpressions(state, dataStreamName1, "logs");
1720+
String index = backingIndex1.getIndex().getName();
1721+
String[] result = indexNameExpressionResolver.indexAliases(
1722+
state,
1723+
index,
1724+
x -> true,
1725+
DataStreamAlias::filteringRequired,
1726+
false,
1727+
resolvedExpressions
1728+
);
1729+
assertThat(result, nullValue());
16891730
}
16901731
}
16911732

server/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
import static org.hamcrest.Matchers.equalTo;
8989
import static org.hamcrest.Matchers.hasToString;
9090
import static org.hamcrest.Matchers.instanceOf;
91+
import static org.hamcrest.Matchers.is;
9192
import static org.hamcrest.Matchers.not;
9293
import static org.hamcrest.Matchers.nullValue;
9394
import static org.mockito.Mockito.mock;
@@ -733,14 +734,16 @@ public void testBuildAliasFilterDataStreamAliases() {
733734
assertThat(filter.should(), containsInAnyOrder(QueryBuilders.termQuery("foo", "baz"), QueryBuilders.termQuery("foo", "bax")));
734735
}
735736
{
737+
// querying an unfiltered and a filtered alias for the same data stream should drop the filters
736738
String index = backingIndex1.getIndex().getName();
737739
AliasFilter result = indicesService.buildAliasFilter(state, index, Set.of("logs_foo", "logs", "logs_bar"));
738-
assertThat(result.getAliases(), arrayContainingInAnyOrder("logs_foo", "logs"));
739-
BoolQueryBuilder filter = (BoolQueryBuilder) result.getQueryBuilder();
740-
assertThat(filter.filter(), empty());
741-
assertThat(filter.must(), empty());
742-
assertThat(filter.mustNot(), empty());
743-
assertThat(filter.should(), containsInAnyOrder(QueryBuilders.termQuery("foo", "baz"), QueryBuilders.termQuery("foo", "bar")));
740+
assertThat(result, is(AliasFilter.EMPTY));
741+
}
742+
{
743+
// similarly, querying the data stream name and a filtered alias should drop the filter
744+
String index = backingIndex1.getIndex().getName();
745+
AliasFilter result = indicesService.buildAliasFilter(state, index, Set.of("logs", dataStreamName1));
746+
assertThat(result, is(AliasFilter.EMPTY));
744747
}
745748
}
746749
}

0 commit comments

Comments
 (0)