diff --git a/docs/changelog/135601.yaml b/docs/changelog/135601.yaml new file mode 100644 index 0000000000000..7088a8bfc874a --- /dev/null +++ b/docs/changelog/135601.yaml @@ -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 diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java index 712f1d874e663..b4eaaf6d25983 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java @@ -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")); diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/IndexResolutionIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/IndexResolutionIT.java index f50091e164086..bb93c4b2b59f5 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/IndexResolutionIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/IndexResolutionIT.java @@ -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"); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/index/IndexResolution.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/index/IndexResolution.java index 4d31f48da77de..b74ef000ee8e8 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/index/IndexResolution.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/index/IndexResolution.java @@ -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 + "]"); 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 a4bfa329d8ac5..99cbb6bf008ef 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 @@ -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()) { @@ -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, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java index ff042c5e7d870..c1d3b94fe1083 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java @@ -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