Skip to content
12 changes: 12 additions & 0 deletions docs/changelog/135601.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pr: 135601
summary: ES|QL Empty index resolution
area: ES|QL
type: breaking
issues: []
breaking:
title: ES|QL Empty index resolution
area: ES|QL
details: Changes index resolution so that `FROM empty*` resolves to empty set of indices (opposed to error today)
and becomes consistent with `GET /empty*/_search`.
impact: Makes a valid query check a bit more permissive in case when pattern is resolved to empty set of indices.
notable: false
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,6 @@ public void testIndicesDontExist() throws IOException {
assertThat(e.getMessage(), containsString("verification_exception"));
assertThat(e.getMessage(), anyOf(containsString("Unknown index [foo]"), containsString("Unknown index [remote_cluster:foo]")));

e = expectThrows(ResponseException.class, () -> runEsql(timestampFilter("gte", "2020-01-01").query(from("foo*"))));
assertEquals(400, e.getResponse().getStatusLine().getStatusCode());
assertThat(e.getMessage(), containsString("verification_exception"));
assertThat(e.getMessage(), anyOf(containsString("Unknown index [foo*]"), containsString("Unknown index [remote_cluster:foo*]")));

e = expectThrows(ResponseException.class, () -> runEsql(timestampFilter("gte", "2020-01-01").query("FROM foo, test1")));
assertEquals(404, e.getResponse().getStatusLine().getStatusCode());
assertThat(e.getMessage(), containsString("index_not_found_exception"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,23 +112,20 @@ public void testResolvesExclusionPattern() {
assertOk(response);
assertResultConcreteIndices(response, "index-1");// excludes pattern from pattern
}
expectThrows(
VerificationException.class,
containsString("Unknown index [index-*,-*]"),
() -> run(syncEsqlQueryRequest().query("FROM index-*,-* METADATA _index")) // exclude all resolves to empty
);
try (var response = run(syncEsqlQueryRequest().query("FROM index-*,-* METADATA _index"))) {
assertOk(response);
assertResultConcreteIndices(response);// exclude all resolves to empty
}
}

public void testDoesNotResolveEmptyPattern() {
public void testResolveEmptyPattern() {
assertAcked(client().admin().indices().prepareCreate("data"));
indexRandom(true, "data", 1);

expectThrows(
VerificationException.class,
containsString("Unknown index [index-*]"),
() -> run(syncEsqlQueryRequest().query("FROM index-* METADATA _index"))
);

try (var response = run(syncEsqlQueryRequest().query("FROM index-* METADATA _index"))) {
assertOk(response);
assertResultConcreteIndices(response);
}
try (var response = run(syncEsqlQueryRequest().query("FROM data,index-* METADATA _index"))) {
assertOk(response);
assertResultConcreteIndices(response, "data");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public static IndexResolution invalid(String invalid) {
return new IndexResolution(null, invalid, Set.of(), Map.of());
}

public static IndexResolution empty(String indexPattern) {
return IndexResolution.valid(new EsIndex(indexPattern, Map.of(), Map.of()));
}

public static IndexResolution notFound(String name) {
Objects.requireNonNull(name, "name must not be null");
return invalid("Unknown index [" + name + "]");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,10 @@ private PreAnalysisResult receiveLookupIndexResolution(
// If the index resolution is invalid, don't bother with the rest of the analysis
return result.addLookupIndexResolution(index, lookupIndexResolution);
}
if (lookupIndexResolution.get().concreteIndices().isEmpty()) {
// require non empty lookup index
return result.addLookupIndexResolution(index, IndexResolution.notFound(lookupIndexResolution.get().name()));
}
if (executionInfo.getClusters().isEmpty() || executionInfo.isCrossClusterSearch() == false) {
// Local only case, still do some checks, since we moved analysis checks here
if (lookupIndexResolution.get().indexNameWithModes().isEmpty()) {
Expand Down Expand Up @@ -696,9 +700,7 @@ private void preAnalyzeMainIndices(
String indexExpressionToResolve = EsqlCCSUtils.createIndexExpressionFromAvailableClusters(executionInfo);
if (indexExpressionToResolve.isEmpty()) {
// if this was a pure remote CCS request (no local indices) and all remotes are offline, return an empty IndexResolution
listener.onResponse(
result.withIndices(IndexResolution.valid(new EsIndex(preAnalysis.indexPattern().indexPattern(), Map.of(), Map.of())))
);
listener.onResponse(result.withIndices(IndexResolution.empty(preAnalysis.indexPattern().indexPattern())));
} else {
indexResolver.resolveAsMergedMapping(
indexExpressionToResolve,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public static IndexResolution mergedMappings(String indexPattern, FieldsInfo fie
assert ThreadPool.assertCurrentThreadPool(ThreadPool.Names.SEARCH_COORDINATION); // too expensive to run this on a transport worker
int numberOfIndices = fieldsInfo.caps.getIndexResponses().size();
if (numberOfIndices == 0) {
return IndexResolution.notFound(indexPattern);
return IndexResolution.empty(indexPattern);
}

// For each field name, store a list of the field caps responses from each index
Expand Down