2121import org .elasticsearch .test .cluster .ElasticsearchCluster ;
2222import org .elasticsearch .test .rest .ESRestTestCase ;
2323import org .elasticsearch .test .rest .TestFeatureService ;
24+ import org .elasticsearch .xcontent .json .JsonXContent ;
2425import org .elasticsearch .xpack .esql .qa .rest .RestEsqlTestCase ;
2526import org .junit .After ;
2627import org .junit .Before ;
2930import org .junit .rules .TestRule ;
3031
3132import java .io .IOException ;
33+ import java .util .ArrayList ;
3234import java .util .List ;
3335import java .util .Map ;
3436import java .util .Set ;
3941import static org .elasticsearch .test .MapMatcher .assertMap ;
4042import static org .elasticsearch .xpack .esql .ccq .Clusters .REMOTE_CLUSTER_NAME ;
4143import static org .hamcrest .Matchers .any ;
44+ import static org .hamcrest .Matchers .anyOf ;
4245import static org .hamcrest .Matchers .equalTo ;
4346import static org .hamcrest .Matchers .greaterThanOrEqualTo ;
4447import static org .hamcrest .Matchers .hasKey ;
48+ import static org .hamcrest .Matchers .is ;
49+ import static org .hamcrest .Matchers .nullValue ;
4550
4651@ ThreadLeakFilters (filters = TestClustersThreadFilter .class )
4752public class MultiClustersIT extends ESRestTestCase {
@@ -129,7 +134,7 @@ void indexDocs(RestClient client, String index, List<Doc> docs) throws IOExcepti
129134 }
130135
131136 private Map <String , Object > run (String query , boolean includeCCSMetadata ) throws IOException {
132- var queryBuilder = new RestEsqlTestCase .RequestObjectBuilder ().query (query );
137+ var queryBuilder = new RestEsqlTestCase .RequestObjectBuilder ().query (query ). profile ( true ) ;
133138 if (includeCCSMetadata ) {
134139 queryBuilder .includeCCSMetadata (true );
135140 }
@@ -158,12 +163,42 @@ private Map<String, Object> runEsql(RestEsqlTestCase.RequestObjectBuilder reques
158163 }
159164 }
160165
166+ private <C , V > void assertResultMapForLike (
167+ boolean includeCCSMetadata ,
168+ Map <String , Object > result ,
169+ C columns ,
170+ V values ,
171+ boolean remoteOnly ,
172+ boolean requireLikeListCapability
173+ ) throws IOException {
174+ List <String > requiredCapabilities = new ArrayList <>(List .of ("like_on_index_fields" ));
175+ if (requireLikeListCapability ) {
176+ requiredCapabilities .add ("like_list_on_index_fields" );
177+ }
178+ // the feature is completely supported if both local and remote clusters support it
179+ boolean isSupported = clusterHasCapability ("POST" , "/_query" , List .of (), requiredCapabilities ).orElse (false );
180+ try (RestClient remoteClient = remoteClusterClient ()) {
181+ isSupported = isSupported
182+ && clusterHasCapability (remoteClient , "POST" , "/_query" , List .of (), requiredCapabilities ).orElse (false );
183+ }
184+
185+ if (isSupported ) {
186+ assertResultMap (includeCCSMetadata , result , columns , values , remoteOnly );
187+ } else {
188+ logger .info ("--> skipping data check for like index test, cluster does not support like index feature" );
189+ // just verify that we did not get a partial result
190+ var clusters = result .get ("_clusters" );
191+ var reason = "unexpected partial results" + (clusters != null ? ": _clusters=" + clusters : "" );
192+ assertThat (reason , result .get ("is_partial" ), anyOf (nullValue (), is (false )));
193+ }
194+ }
195+
161196 private <C , V > void assertResultMap (boolean includeCCSMetadata , Map <String , Object > result , C columns , V values , boolean remoteOnly ) {
162197 MapMatcher mapMatcher = getResultMatcher (
163198 ccsMetadataAvailable (),
164199 result .containsKey ("is_partial" ),
165200 result .containsKey ("documents_found" )
166- );
201+ ). extraOk () ;
167202 if (includeCCSMetadata ) {
168203 mapMatcher = mapMatcher .entry ("_clusters" , any (Map .class ));
169204 }
@@ -254,7 +289,7 @@ private void assertClusterDetailsMap(Map<String, Object> result, boolean remoteO
254289 assertThat (remoteClusterShards .keySet (), equalTo (Set .of ("total" , "successful" , "skipped" , "failed" )));
255290 assertThat ((Integer ) remoteClusterShards .get ("total" ), greaterThanOrEqualTo (0 ));
256291 assertThat ((Integer ) remoteClusterShards .get ("successful" ), equalTo ((Integer ) remoteClusterShards .get ("total" )));
257- assertThat ((Integer ) remoteClusterShards .get ("skipped" ), equalTo (0 ));
292+ // assertThat((Integer) remoteClusterShards.get("skipped"), equalTo(0));
258293 assertThat ((Integer ) remoteClusterShards .get ("failed" ), equalTo (0 ));
259294
260295 if (remoteOnly == false ) {
@@ -270,7 +305,7 @@ private void assertClusterDetailsMap(Map<String, Object> result, boolean remoteO
270305 assertThat (localClusterShards .keySet (), equalTo (Set .of ("total" , "successful" , "skipped" , "failed" )));
271306 assertThat ((Integer ) localClusterShards .get ("total" ), greaterThanOrEqualTo (0 ));
272307 assertThat ((Integer ) localClusterShards .get ("successful" ), equalTo ((Integer ) localClusterShards .get ("total" )));
273- assertThat ((Integer ) localClusterShards .get ("skipped" ), equalTo (0 ));
308+ // assertThat((Integer) localClusterShards.get("skipped"), equalTo(0));
274309 assertThat ((Integer ) localClusterShards .get ("failed" ), equalTo (0 ));
275310 }
276311 }
@@ -371,6 +406,93 @@ public void testStats() throws IOException {
371406 assertThat (clusterData , hasKey ("took" ));
372407 }
373408
409+ public void testLikeIndex () throws Exception {
410+
411+ boolean includeCCSMetadata = includeCCSMetadata ();
412+ Map <String , Object > result = run ("""
413+ FROM test-local-index,*:test-remote-index METADATA _index
414+ | WHERE _index LIKE "*remote*"
415+ | STATS c = COUNT(*) BY _index
416+ | SORT _index ASC
417+ """ , includeCCSMetadata );
418+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
419+ var values = List .of (List .of (remoteDocs .size (), REMOTE_CLUSTER_NAME + ":" + remoteIndex ));
420+ String resultString = Strings .toString (JsonXContent .contentBuilder ().prettyPrint ().map (result ));
421+ System .out .println (resultString );
422+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , false );
423+ }
424+
425+ public void testNotLikeIndex () throws Exception {
426+ boolean includeCCSMetadata = includeCCSMetadata ();
427+ Map <String , Object > result = run ("""
428+ FROM test-local-index,*:test-remote-index METADATA _index
429+ | WHERE _index NOT LIKE "*remote*"
430+ | STATS c = COUNT(*) BY _index
431+ | SORT _index ASC
432+ """ , includeCCSMetadata );
433+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
434+ var values = List .of (List .of (localDocs .size (), localIndex ));
435+ String resultString = Strings .toString (JsonXContent .contentBuilder ().prettyPrint ().map (result ));
436+ System .out .println (resultString );
437+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , false );
438+ }
439+
440+ public void testLikeListIndex () throws Exception {
441+ boolean includeCCSMetadata = includeCCSMetadata ();
442+ Map <String , Object > result = run ("""
443+ FROM test-local-index,*:test-remote-index METADATA _index
444+ | WHERE _index LIKE ("*remote*", "not-exist*")
445+ | STATS c = COUNT(*) BY _index
446+ | SORT _index ASC
447+ """ , includeCCSMetadata );
448+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
449+ var values = List .of (List .of (remoteDocs .size (), REMOTE_CLUSTER_NAME + ":" + remoteIndex ));
450+ String resultString = Strings .toString (JsonXContent .contentBuilder ().prettyPrint ().map (result ));
451+ System .out .println (resultString );
452+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , true );
453+ }
454+
455+ public void testNotLikeListIndex () throws Exception {
456+ boolean includeCCSMetadata = includeCCSMetadata ();
457+ Map <String , Object > result = run ("""
458+ FROM test-local-index,*:test-remote-index METADATA _index
459+ | WHERE _index NOT LIKE ("*remote*", "not-exist*")
460+ | STATS c = COUNT(*) BY _index
461+ | SORT _index ASC
462+ """ , includeCCSMetadata );
463+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
464+ var values = List .of (List .of (localDocs .size (), localIndex ));
465+ String resultString = Strings .toString (JsonXContent .contentBuilder ().prettyPrint ().map (result ));
466+ System .out .println (resultString );
467+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , true );
468+ }
469+
470+ public void testRLikeIndex () throws Exception {
471+ boolean includeCCSMetadata = includeCCSMetadata ();
472+ Map <String , Object > result = run ("""
473+ FROM test-local-index,*:test-remote-index METADATA _index
474+ | WHERE _index RLIKE ".*remote.*"
475+ | STATS c = COUNT(*) BY _index
476+ | SORT _index ASC
477+ """ , includeCCSMetadata );
478+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
479+ var values = List .of (List .of (remoteDocs .size (), REMOTE_CLUSTER_NAME + ":" + remoteIndex ));
480+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , false );
481+ }
482+
483+ public void testNotRLikeIndex () throws Exception {
484+ boolean includeCCSMetadata = includeCCSMetadata ();
485+ Map <String , Object > result = run ("""
486+ FROM test-local-index,*:test-remote-index METADATA _index
487+ | WHERE _index NOT RLIKE ".*remote.*"
488+ | STATS c = COUNT(*) BY _index
489+ | SORT _index ASC
490+ """ , includeCCSMetadata );
491+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
492+ var values = List .of (List .of (localDocs .size (), localIndex ));
493+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , false );
494+ }
495+
374496 private RestClient remoteClusterClient () throws IOException {
375497 var clusterHosts = parseClusterHosts (remoteCluster .getHttpAddresses ());
376498 return buildClient (restClientSettings (), clusterHosts .toArray (new HttpHost [0 ]));
0 commit comments