Skip to content

Commit e1ceecb

Browse files
authored
Add unsupported error message for cross-clusters query (#102677) (#102719)
Return ES|QL does not yet support querying remote indices for cross-clusters queries until the feature is implemented. Closes #102650
1 parent 90b1f42 commit e1ceecb

File tree

2 files changed

+127
-0
lines changed

2 files changed

+127
-0
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.action;
9+
10+
import org.elasticsearch.client.internal.Client;
11+
import org.elasticsearch.common.settings.Setting;
12+
import org.elasticsearch.common.settings.Settings;
13+
import org.elasticsearch.common.util.CollectionUtils;
14+
import org.elasticsearch.compute.operator.exchange.ExchangeService;
15+
import org.elasticsearch.core.TimeValue;
16+
import org.elasticsearch.plugins.Plugin;
17+
import org.elasticsearch.test.AbstractMultiClustersTestCase;
18+
import org.elasticsearch.xpack.esql.plugin.EsqlPlugin;
19+
20+
import java.util.ArrayList;
21+
import java.util.Collection;
22+
import java.util.Iterator;
23+
import java.util.List;
24+
import java.util.concurrent.TimeUnit;
25+
26+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
27+
import static org.hamcrest.Matchers.equalTo;
28+
import static org.hamcrest.Matchers.hasSize;
29+
30+
public class CrossClustersQueryIT extends AbstractMultiClustersTestCase {
31+
private static final String REMOTE_CLUSTER = "cluster-a";
32+
33+
@Override
34+
protected Collection<String> remoteClusterAlias() {
35+
return List.of(REMOTE_CLUSTER);
36+
}
37+
38+
@Override
39+
protected Collection<Class<? extends Plugin>> nodePlugins(String clusterAlias) {
40+
List<Class<? extends Plugin>> plugins = new ArrayList<>();
41+
plugins.addAll(super.nodePlugins(clusterAlias));
42+
plugins.add(EsqlPlugin.class);
43+
plugins.add(InternalExchangePlugin.class);
44+
return CollectionUtils.appendToCopy(super.nodePlugins(clusterAlias), EsqlPlugin.class);
45+
}
46+
47+
public static class InternalExchangePlugin extends Plugin {
48+
@Override
49+
public List<Setting<?>> getSettings() {
50+
return List.of(
51+
Setting.timeSetting(
52+
ExchangeService.INACTIVE_SINKS_INTERVAL_SETTING,
53+
TimeValue.timeValueSeconds(30),
54+
Setting.Property.NodeScope
55+
)
56+
);
57+
}
58+
}
59+
60+
public void testUnsupported() {
61+
int numDocs = between(1, 10);
62+
for (String cluster : List.of(LOCAL_CLUSTER, REMOTE_CLUSTER)) {
63+
Client client = client(cluster);
64+
assertAcked(
65+
client.admin()
66+
.indices()
67+
.prepareCreate("events")
68+
.setSettings(Settings.builder().put("index.number_of_shards", randomIntBetween(1, 5)))
69+
.setMapping("tag", "type=keyword", "v", "type=long")
70+
);
71+
for (int i = 0; i < numDocs; i++) {
72+
client.prepareIndex("events").setSource("tag", cluster, "v", i).get();
73+
}
74+
client.admin().indices().prepareRefresh("events").get();
75+
}
76+
var emptyQueries = List.of(
77+
"from *:* | LIMIT 0",
78+
"from *,*:* | LIMIT 0",
79+
"from *:events* | LIMIT 0",
80+
"from events,*:events* | LIMIT 0"
81+
);
82+
for (String q : emptyQueries) {
83+
try (EsqlQueryResponse resp = runQuery(q)) {
84+
assertThat(resp.columns(), hasSize(2));
85+
assertFalse(resp.values().hasNext());
86+
}
87+
}
88+
var remotePatterns = List.of("*:*", "*, *:*", "*:events*", "events, *:events*");
89+
for (String pattern : remotePatterns) {
90+
var query = "FROM " + pattern + " | LIMIT " + between(1, 100);
91+
IllegalArgumentException error = expectThrows(IllegalArgumentException.class, () -> runQuery(query).close());
92+
assertThat(error.getMessage(), equalTo("ES|QL does not yet support querying remote indices [" + pattern + "]"));
93+
}
94+
int limit = between(1, numDocs);
95+
var localQueries = List.of("from events* | LIMIT " + limit, "from * | LIMIT " + limit);
96+
for (String q : localQueries) {
97+
try (EsqlQueryResponse resp = runQuery(q)) {
98+
assertThat(resp.columns(), hasSize(2));
99+
int rows = 0;
100+
Iterator<Iterator<Object>> values = resp.values();
101+
while (values.hasNext()) {
102+
values.next();
103+
++rows;
104+
}
105+
assertThat(rows, equalTo(limit));
106+
}
107+
}
108+
}
109+
110+
protected EsqlQueryResponse runQuery(String query) {
111+
logger.info("--> query [{}]", query);
112+
EsqlQueryRequest request = new EsqlQueryRequest();
113+
request.query(query);
114+
request.pragmas(AbstractEsqlIntegTestCase.randomPragmas());
115+
return client(LOCAL_CLUSTER).execute(EsqlQueryAction.INSTANCE, request).actionGet(30, TimeUnit.SECONDS);
116+
}
117+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ComputeService.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.elasticsearch.tasks.Task;
5353
import org.elasticsearch.tasks.TaskCancelledException;
5454
import org.elasticsearch.threadpool.ThreadPool;
55+
import org.elasticsearch.transport.RemoteClusterAware;
5556
import org.elasticsearch.transport.TransportChannel;
5657
import org.elasticsearch.transport.TransportRequestHandler;
5758
import org.elasticsearch.transport.TransportRequestOptions;
@@ -68,6 +69,7 @@
6869

6970
import java.io.IOException;
7071
import java.util.ArrayList;
72+
import java.util.Arrays;
7173
import java.util.Collections;
7274
import java.util.HashMap;
7375
import java.util.List;
@@ -341,6 +343,14 @@ private void computeTargetNodes(
341343
String[] originalIndices,
342344
ActionListener<List<TargetNode>> listener
343345
) {
346+
var remoteIndices = transportService.getRemoteClusterService().groupIndices(SearchRequest.DEFAULT_INDICES_OPTIONS, originalIndices);
347+
remoteIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY);
348+
if (remoteIndices.isEmpty() == false) {
349+
listener.onFailure(
350+
new IllegalArgumentException("ES|QL does not yet support querying remote indices " + Arrays.toString(originalIndices))
351+
);
352+
return;
353+
}
344354
// Ideally, the search_shards API should be called before the field-caps API; however, this can lead
345355
// to a situation where the column structure (i.e., matched data types) differs depending on the query.
346356
ThreadContext threadContext = transportService.getThreadPool().getThreadContext();

0 commit comments

Comments
 (0)