2929import org .junit .rules .TestRule ;
3030
3131import java .io .IOException ;
32+ import java .util .ArrayList ;
3233import java .util .List ;
3334import java .util .Map ;
3435import java .util .Set ;
3738import java .util .stream .Stream ;
3839
3940import static org .elasticsearch .test .MapMatcher .assertMap ;
41+ import static org .elasticsearch .test .MapMatcher .matchesMap ;
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,51 @@ 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+
196+ private boolean capabilitiesSupportedNewAndOld (List <String > requiredCapabilities ) throws IOException {
197+ boolean isSupported = clusterHasCapability ("POST" , "/_query" , List .of (), requiredCapabilities ).orElse (false );
198+ try (RestClient remoteClient = remoteClusterClient ()) {
199+ isSupported = isSupported
200+ && clusterHasCapability (remoteClient , "POST" , "/_query" , List .of (), requiredCapabilities ).orElse (false );
201+ }
202+ return isSupported ;
203+ }
204+
161205 private <C , V > void assertResultMap (boolean includeCCSMetadata , Map <String , Object > result , C columns , V values , boolean remoteOnly ) {
162206 MapMatcher mapMatcher = getResultMatcher (
163207 ccsMetadataAvailable (),
164208 result .containsKey ("is_partial" ),
165209 result .containsKey ("documents_found" )
166- );
210+ ). extraOk () ;
167211 if (includeCCSMetadata ) {
168212 mapMatcher = mapMatcher .entry ("_clusters" , any (Map .class ));
169213 }
@@ -251,11 +295,13 @@ private void assertClusterDetailsMap(Map<String, Object> result, boolean remoteO
251295
252296 @ SuppressWarnings ("unchecked" )
253297 Map <String , Object > remoteClusterShards = (Map <String , Object >) remoteCluster .get ("_shards" );
254- assertThat (remoteClusterShards .keySet (), equalTo (Set .of ("total" , "successful" , "skipped" , "failed" )));
255- assertThat ((Integer ) remoteClusterShards .get ("total" ), greaterThanOrEqualTo (0 ));
256- assertThat ((Integer ) remoteClusterShards .get ("successful" ), equalTo ((Integer ) remoteClusterShards .get ("total" )));
257- assertThat ((Integer ) remoteClusterShards .get ("skipped" ), equalTo (0 ));
258- assertThat ((Integer ) remoteClusterShards .get ("failed" ), equalTo (0 ));
298+ assertThat (
299+ remoteClusterShards ,
300+ matchesMap ().entry ("total" , greaterThanOrEqualTo (0 ))
301+ .entry ("successful" , remoteClusterShards .get ("total" ))
302+ .entry ("skipped" , greaterThanOrEqualTo (0 ))
303+ .entry ("failed" , 0 )
304+ );
259305
260306 if (remoteOnly == false ) {
261307 @ SuppressWarnings ("unchecked" )
@@ -267,11 +313,13 @@ private void assertClusterDetailsMap(Map<String, Object> result, boolean remoteO
267313
268314 @ SuppressWarnings ("unchecked" )
269315 Map <String , Object > localClusterShards = (Map <String , Object >) localCluster .get ("_shards" );
270- assertThat (localClusterShards .keySet (), equalTo (Set .of ("total" , "successful" , "skipped" , "failed" )));
271- assertThat ((Integer ) localClusterShards .get ("total" ), greaterThanOrEqualTo (0 ));
272- assertThat ((Integer ) localClusterShards .get ("successful" ), equalTo ((Integer ) localClusterShards .get ("total" )));
273- assertThat ((Integer ) localClusterShards .get ("skipped" ), equalTo (0 ));
274- assertThat ((Integer ) localClusterShards .get ("failed" ), equalTo (0 ));
316+ assertThat (
317+ localClusterShards ,
318+ matchesMap ().entry ("total" , greaterThanOrEqualTo (0 ))
319+ .entry ("successful" , localClusterShards .get ("total" ))
320+ .entry ("skipped" , greaterThanOrEqualTo (0 ))
321+ .entry ("failed" , 0 )
322+ );
275323 }
276324 }
277325
@@ -371,6 +419,116 @@ public void testStats() throws IOException {
371419 assertThat (clusterData , hasKey ("took" ));
372420 }
373421
422+ public void testLikeIndex () throws Exception {
423+
424+ boolean includeCCSMetadata = includeCCSMetadata ();
425+ Map <String , Object > result = run ("""
426+ FROM test-local-index,*:test-remote-index METADATA _index
427+ | WHERE _index LIKE "*remote*"
428+ | STATS c = COUNT(*) BY _index
429+ | SORT _index ASC
430+ """ , includeCCSMetadata );
431+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
432+ var values = List .of (List .of (remoteDocs .size (), REMOTE_CLUSTER_NAME + ":" + remoteIndex ));
433+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , false );
434+ }
435+
436+ public void testNotLikeIndex () throws Exception {
437+ boolean includeCCSMetadata = includeCCSMetadata ();
438+ Map <String , Object > result = run ("""
439+ FROM test-local-index,*:test-remote-index METADATA _index
440+ | WHERE _index NOT LIKE "*remote*"
441+ | STATS c = COUNT(*) BY _index
442+ | SORT _index ASC
443+ """ , includeCCSMetadata );
444+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
445+ var values = List .of (List .of (localDocs .size (), localIndex ));
446+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , false );
447+ }
448+
449+ public void testLikeListIndex () throws Exception {
450+ List <String > requiredCapabilities = new ArrayList <>(List .of ("like_list_on_index_fields" ));
451+ // the feature is completely supported if both local and remote clusters support it
452+ if (capabilitiesSupportedNewAndOld (requiredCapabilities ) == false ) {
453+ logger .info ("--> skipping testNotLikeListIndex, due to missing capability" );
454+ return ;
455+ }
456+ boolean includeCCSMetadata = includeCCSMetadata ();
457+ Map <String , Object > result = run ("""
458+ FROM test-local-index,*:test-remote-index METADATA _index
459+ | WHERE _index 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 (remoteDocs .size (), REMOTE_CLUSTER_NAME + ":" + remoteIndex ));
465+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , true );
466+ }
467+
468+ public void testNotLikeListIndex () throws Exception {
469+ List <String > requiredCapabilities = new ArrayList <>(List .of ("like_list_on_index_fields" ));
470+ // the feature is completely supported if both local and remote clusters support it
471+ if (capabilitiesSupportedNewAndOld (requiredCapabilities ) == false ) {
472+ logger .info ("--> skipping testNotLikeListIndex, due to missing capability" );
473+ return ;
474+ }
475+ boolean includeCCSMetadata = includeCCSMetadata ();
476+ Map <String , Object > result = run ("""
477+ FROM test-local-index,*:test-remote-index METADATA _index
478+ | WHERE _index NOT LIKE ("*remote*", "not-exist*")
479+ | STATS c = COUNT(*) BY _index
480+ | SORT _index ASC
481+ """ , includeCCSMetadata );
482+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
483+ var values = List .of (List .of (localDocs .size (), localIndex ));
484+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , true );
485+ }
486+
487+ public void testNotLikeListKeyWord () throws Exception {
488+ List <String > requiredCapabilities = new ArrayList <>(List .of ("like_list_on_index_fields" ));
489+ // the feature is completely supported if both local and remote clusters support it
490+ if (capabilitiesSupportedNewAndOld (requiredCapabilities ) == false ) {
491+ logger .info ("--> skipping testNotLikeListIndex, due to missing capability" );
492+ return ;
493+ }
494+ boolean includeCCSMetadata = includeCCSMetadata ();
495+ Map <String , Object > result = run ("""
496+ FROM test-local-index,*:test-remote-index METADATA _index
497+ | WHERE color NOT LIKE ("*blue*", "*red*")
498+ | STATS c = COUNT(*) BY _index
499+ | SORT _index ASC
500+ """ , includeCCSMetadata );
501+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
502+ var values = List .of (List .of (localDocs .size (), localIndex ));
503+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , true );
504+ }
505+
506+ public void testRLikeIndex () throws Exception {
507+ boolean includeCCSMetadata = includeCCSMetadata ();
508+ Map <String , Object > result = run ("""
509+ FROM test-local-index,*:test-remote-index METADATA _index
510+ | WHERE _index RLIKE ".*remote.*"
511+ | STATS c = COUNT(*) BY _index
512+ | SORT _index ASC
513+ """ , includeCCSMetadata );
514+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
515+ var values = List .of (List .of (remoteDocs .size (), REMOTE_CLUSTER_NAME + ":" + remoteIndex ));
516+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , false );
517+ }
518+
519+ public void testNotRLikeIndex () throws Exception {
520+ boolean includeCCSMetadata = includeCCSMetadata ();
521+ Map <String , Object > result = run ("""
522+ FROM test-local-index,*:test-remote-index METADATA _index
523+ | WHERE _index NOT RLIKE ".*remote.*"
524+ | STATS c = COUNT(*) BY _index
525+ | SORT _index ASC
526+ """ , includeCCSMetadata );
527+ var columns = List .of (Map .of ("name" , "c" , "type" , "long" ), Map .of ("name" , "_index" , "type" , "keyword" ));
528+ var values = List .of (List .of (localDocs .size (), localIndex ));
529+ assertResultMapForLike (includeCCSMetadata , result , columns , values , false , false );
530+ }
531+
374532 private RestClient remoteClusterClient () throws IOException {
375533 var clusterHosts = parseClusterHosts (remoteCluster .getHttpAddresses ());
376534 return buildClient (restClientSettings (), clusterHosts .toArray (new HttpHost [0 ]));
0 commit comments