29
29
import org .junit .rules .TestRule ;
30
30
31
31
import java .io .IOException ;
32
+ import java .util .ArrayList ;
32
33
import java .util .List ;
33
34
import java .util .Map ;
34
35
import java .util .Set ;
37
38
import java .util .stream .Stream ;
38
39
39
40
import static org .elasticsearch .test .MapMatcher .assertMap ;
41
+ import static org .elasticsearch .test .MapMatcher .matchesMap ;
40
42
import static org .elasticsearch .xpack .esql .ccq .Clusters .REMOTE_CLUSTER_NAME ;
41
43
import static org .hamcrest .Matchers .any ;
44
+ import static org .hamcrest .Matchers .anyOf ;
42
45
import static org .hamcrest .Matchers .equalTo ;
43
46
import static org .hamcrest .Matchers .greaterThanOrEqualTo ;
44
47
import static org .hamcrest .Matchers .hasKey ;
48
+ import static org .hamcrest .Matchers .is ;
49
+ import static org .hamcrest .Matchers .nullValue ;
45
50
46
51
@ ThreadLeakFilters (filters = TestClustersThreadFilter .class )
47
52
public class MultiClustersIT extends ESRestTestCase {
@@ -129,7 +134,7 @@ void indexDocs(RestClient client, String index, List<Doc> docs) throws IOExcepti
129
134
}
130
135
131
136
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 ) ;
133
138
if (includeCCSMetadata ) {
134
139
queryBuilder .includeCCSMetadata (true );
135
140
}
@@ -158,12 +163,51 @@ private Map<String, Object> runEsql(RestEsqlTestCase.RequestObjectBuilder reques
158
163
}
159
164
}
160
165
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
+
161
205
private <C , V > void assertResultMap (boolean includeCCSMetadata , Map <String , Object > result , C columns , V values , boolean remoteOnly ) {
162
206
MapMatcher mapMatcher = getResultMatcher (
163
207
ccsMetadataAvailable (),
164
208
result .containsKey ("is_partial" ),
165
209
result .containsKey ("documents_found" )
166
- );
210
+ ). extraOk () ;
167
211
if (includeCCSMetadata ) {
168
212
mapMatcher = mapMatcher .entry ("_clusters" , any (Map .class ));
169
213
}
@@ -251,11 +295,13 @@ private void assertClusterDetailsMap(Map<String, Object> result, boolean remoteO
251
295
252
296
@ SuppressWarnings ("unchecked" )
253
297
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
+ );
259
305
260
306
if (remoteOnly == false ) {
261
307
@ SuppressWarnings ("unchecked" )
@@ -267,11 +313,13 @@ private void assertClusterDetailsMap(Map<String, Object> result, boolean remoteO
267
313
268
314
@ SuppressWarnings ("unchecked" )
269
315
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
+ );
275
323
}
276
324
}
277
325
@@ -371,6 +419,116 @@ public void testStats() throws IOException {
371
419
assertThat (clusterData , hasKey ("took" ));
372
420
}
373
421
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
+
374
532
private RestClient remoteClusterClient () throws IOException {
375
533
var clusterHosts = parseClusterHosts (remoteCluster .getHttpAddresses ());
376
534
return buildClient (restClientSettings (), clusterHosts .toArray (new HttpHost [0 ]));
0 commit comments