6
6
package org .elasticsearch .xpack .ql .index ;
7
7
8
8
import com .carrotsearch .hppc .cursors .ObjectCursor ;
9
+ import com .carrotsearch .hppc .cursors .ObjectObjectCursor ;
9
10
10
11
import org .elasticsearch .ElasticsearchSecurityException ;
11
12
import org .elasticsearch .action .ActionListener ;
16
17
import org .elasticsearch .action .admin .indices .get .GetIndexResponse ;
17
18
import org .elasticsearch .action .fieldcaps .FieldCapabilities ;
18
19
import org .elasticsearch .action .fieldcaps .FieldCapabilitiesRequest ;
20
+ import org .elasticsearch .action .fieldcaps .FieldCapabilitiesResponse ;
19
21
import org .elasticsearch .action .support .IndicesOptions ;
20
22
import org .elasticsearch .action .support .IndicesOptions .Option ;
21
23
import org .elasticsearch .action .support .IndicesOptions .WildcardStates ;
22
24
import org .elasticsearch .client .Client ;
23
25
import org .elasticsearch .cluster .metadata .AliasMetaData ;
24
26
import org .elasticsearch .common .Strings ;
27
+ import org .elasticsearch .common .collect .ImmutableOpenMap ;
25
28
import org .elasticsearch .index .IndexNotFoundException ;
26
29
import org .elasticsearch .index .IndexSettings ;
27
30
import org .elasticsearch .xpack .ql .QlIllegalArgumentException ;
42
45
import java .util .Collections ;
43
46
import java .util .Comparator ;
44
47
import java .util .EnumSet ;
48
+ import java .util .HashMap ;
45
49
import java .util .HashSet ;
46
50
import java .util .Iterator ;
47
51
import java .util .LinkedHashMap ;
52
+ import java .util .LinkedHashSet ;
48
53
import java .util .List ;
49
54
import java .util .Map ;
50
55
import java .util .Map .Entry ;
@@ -164,7 +169,6 @@ public boolean equals(Object obj) {
164
169
private final String clusterName ;
165
170
private final DataTypeRegistry typeRegistry ;
166
171
167
-
168
172
public IndexResolver (Client client , String clusterName , DataTypeRegistry typeRegistry ) {
169
173
this .client = client ;
170
174
this .clusterName = clusterName ;
@@ -296,7 +300,7 @@ public static IndexResolution mergedMappings(DataTypeRegistry typeRegistry, Stri
296
300
}
297
301
298
302
// merge all indices onto the same one
299
- List <EsIndex > indices = buildIndices (typeRegistry , indexNames , null , fieldCaps , i -> indexPattern , (n , types ) -> {
303
+ List <EsIndex > indices = buildIndices (typeRegistry , indexNames , null , fieldCaps , null , i -> indexPattern , (n , types ) -> {
300
304
StringBuilder errorMessage = new StringBuilder ();
301
305
302
306
boolean hasUnmapped = types .containsKey (UNMAPPED );
@@ -473,17 +477,32 @@ private static FieldCapabilitiesRequest createFieldCapsRequest(String index, boo
473
477
public void resolveAsSeparateMappings (String indexWildcard , String javaRegex , boolean includeFrozen ,
474
478
ActionListener <List <EsIndex >> listener ) {
475
479
FieldCapabilitiesRequest fieldRequest = createFieldCapsRequest (indexWildcard , includeFrozen );
476
- client .fieldCaps (fieldRequest ,
477
- ActionListener .wrap (
478
- response -> listener .onResponse (
479
- separateMappings (typeRegistry , indexWildcard , javaRegex , response .getIndices (), response .get ())),
480
- listener ::onFailure ));
480
+ client .fieldCaps (fieldRequest , wrap (response -> {
481
+ client .admin ().indices ().getAliases (createGetAliasesRequest (response , includeFrozen ), wrap (aliases ->
482
+ listener .onResponse (separateMappings (typeRegistry , javaRegex , response .getIndices (), response .get (), aliases .getAliases ())),
483
+ ex -> {
484
+ if (ex instanceof IndexNotFoundException || ex instanceof ElasticsearchSecurityException ) {
485
+ listener .onResponse (separateMappings (typeRegistry , javaRegex , response .getIndices (), response .get (), null ));
486
+ } else {
487
+ listener .onFailure (ex );
488
+ }
489
+ }));
490
+ },
491
+ listener ::onFailure ));
481
492
482
493
}
483
494
484
- public static List <EsIndex > separateMappings (DataTypeRegistry typeRegistry , String indexPattern , String javaRegex , String [] indexNames ,
485
- Map <String , Map <String , FieldCapabilities >> fieldCaps ) {
486
- return buildIndices (typeRegistry , indexNames , javaRegex , fieldCaps , Function .identity (), (s , cap ) -> null );
495
+ private GetAliasesRequest createGetAliasesRequest (FieldCapabilitiesResponse response , boolean includeFrozen ) {
496
+ return new GetAliasesRequest ()
497
+ .local (true )
498
+ .aliases ("*" )
499
+ .indices (response .getIndices ())
500
+ .indicesOptions (includeFrozen ? FIELD_CAPS_FROZEN_INDICES_OPTIONS : FIELD_CAPS_INDICES_OPTIONS );
501
+ }
502
+
503
+ public static List <EsIndex > separateMappings (DataTypeRegistry typeRegistry , String javaRegex , String [] indexNames ,
504
+ Map <String , Map <String , FieldCapabilities >> fieldCaps , ImmutableOpenMap <String , List <AliasMetaData >> aliases ) {
505
+ return buildIndices (typeRegistry , indexNames , javaRegex , fieldCaps , aliases , Function .identity (), (s , cap ) -> null );
487
506
}
488
507
489
508
private static class Fields {
@@ -496,16 +515,27 @@ private static class Fields {
496
515
* each field.
497
516
*/
498
517
private static List <EsIndex > buildIndices (DataTypeRegistry typeRegistry , String [] indexNames , String javaRegex ,
499
- Map <String , Map <String , FieldCapabilities >> fieldCaps ,
518
+ Map <String , Map <String , FieldCapabilities >> fieldCaps , ImmutableOpenMap < String , List < AliasMetaData >> aliases ,
500
519
Function <String , String > indexNameProcessor ,
501
520
BiFunction <String , Map <String , FieldCapabilities >, InvalidMappedField > validityVerifier ) {
502
521
503
- if (indexNames == null || indexNames .length == 0 ) {
522
+ if (( indexNames == null || indexNames .length == 0 ) && ( aliases == null || aliases . isEmpty ()) ) {
504
523
return emptyList ();
505
524
}
506
525
507
- final List <String > resolvedIndices = asList (indexNames );
508
- Map <String , Fields > indices = new LinkedHashMap <>(resolvedIndices .size ());
526
+ Set <String > resolvedAliases = new HashSet <>();
527
+ if (aliases != null ) {
528
+ Iterator <ObjectObjectCursor <String , List <AliasMetaData >>> iterator = aliases .iterator ();
529
+ while (iterator .hasNext ()) {
530
+ for (AliasMetaData alias : iterator .next ().value ) {
531
+ resolvedAliases .add (alias .getAlias ());
532
+ }
533
+ }
534
+ }
535
+
536
+ List <String > resolvedIndices = new ArrayList <>(asList (indexNames ));
537
+ int mapSize = CollectionUtils .mapSize (resolvedIndices .size () + resolvedAliases .size ());
538
+ Map <String , Fields > indices = new LinkedHashMap <>(mapSize );
509
539
Pattern pattern = javaRegex != null ? Pattern .compile (javaRegex ) : null ;
510
540
511
541
// sort fields in reverse order to build the field hierarchy
@@ -525,6 +555,8 @@ private static List<EsIndex> buildIndices(DataTypeRegistry typeRegistry, String[
525
555
Map <String , FieldCapabilities > types = new LinkedHashMap <>(entry .getValue ());
526
556
// apply verification and possibly remove the "duplicate" CONSTANT_KEYWORD field type
527
557
final InvalidMappedField invalidField = validityVerifier .apply (fieldName , types );
558
+ // apply verification for fields belonging to index aliases
559
+ Map <String , InvalidMappedField > invalidFieldsForAliases = getInvalidFieldsForAliases (fieldName , types , aliases );
528
560
529
561
// filter meta fields and unmapped
530
562
FieldCapabilities unmapped = types .get (UNMAPPED );
@@ -545,7 +577,7 @@ private static List<EsIndex> buildIndices(DataTypeRegistry typeRegistry, String[
545
577
List <String > concreteIndices = null ;
546
578
if (capIndices != null ) {
547
579
if (unmappedIndices .isEmpty ()) {
548
- concreteIndices = asList (capIndices );
580
+ concreteIndices = new ArrayList <>( asList (capIndices ) );
549
581
} else {
550
582
concreteIndices = new ArrayList <>(capIndices .length );
551
583
for (String capIndex : capIndices ) {
@@ -559,38 +591,63 @@ private static List<EsIndex> buildIndices(DataTypeRegistry typeRegistry, String[
559
591
concreteIndices = resolvedIndices ;
560
592
}
561
593
594
+ // add to the list of concrete indices the aliases associated with these indices
595
+ Set <String > uniqueAliases = new LinkedHashSet <>();
596
+ if (aliases != null ) {
597
+ for (String concreteIndex : concreteIndices ) {
598
+ if (aliases .containsKey (concreteIndex )) {
599
+ List <AliasMetaData > concreteIndexAliases = aliases .get (concreteIndex );
600
+ concreteIndexAliases .stream ().forEach (e -> uniqueAliases .add (e .alias ()));
601
+ }
602
+ }
603
+ concreteIndices .addAll (uniqueAliases );
604
+ }
605
+
562
606
// put the field in their respective mappings
563
607
for (String index : concreteIndices ) {
564
- if (pattern == null || pattern .matcher (index ).matches ()) {
565
- String indexName = indexNameProcessor .apply (index );
608
+ boolean isIndexAlias = uniqueAliases .contains (index );
609
+ if (pattern == null || pattern .matcher (index ).matches () || isIndexAlias ) {
610
+ String indexName = isIndexAlias ? index : indexNameProcessor .apply (index );
566
611
Fields indexFields = indices .get (indexName );
567
612
if (indexFields == null ) {
568
613
indexFields = new Fields ();
569
614
indices .put (indexName , indexFields );
570
615
}
571
616
EsField field = indexFields .flattedMapping .get (fieldName );
572
- if (field == null || (invalidField != null && (field instanceof InvalidMappedField ) == false )) {
617
+ boolean createField = false ;
618
+ if (isIndexAlias == false ) {
619
+ if (field == null || (invalidField != null && (field instanceof InvalidMappedField ) == false )) {
620
+ createField = true ;
621
+ }
622
+ }
623
+ else {
624
+ if (field == null && invalidFieldsForAliases .get (index ) == null ) {
625
+ createField = true ;
626
+ }
627
+ }
628
+
629
+ if (createField ) {
573
630
int dot = fieldName .lastIndexOf ('.' );
574
631
/*
575
632
* Looking up the "tree" at the parent fields here to see if the field is an alias.
576
633
* When the upper elements of the "tree" have no elements in fieldcaps, then this is an alias field. But not
577
634
* always: if there are two aliases - a.b.c.alias1 and a.b.c.alias2 - only one of them will be considered alias.
578
635
*/
579
- Holder <Boolean > isAlias = new Holder <>(false );
636
+ Holder <Boolean > isAliasFieldType = new Holder <>(false );
580
637
if (dot >= 0 ) {
581
638
String parentName = fieldName .substring (0 , dot );
582
639
if (indexFields .flattedMapping .get (parentName ) == null ) {
583
640
// lack of parent implies the field is an alias
584
641
if (fieldCaps .get (parentName ) == null ) {
585
- isAlias .set (true );
642
+ isAliasFieldType .set (true );
586
643
}
587
644
}
588
645
}
589
646
590
647
createField (typeRegistry , fieldName , fieldCaps , indexFields .hierarchicalMapping , indexFields .flattedMapping ,
591
648
s -> invalidField != null ? invalidField :
592
649
createField (typeRegistry , s , typeCap .getType (), emptyMap (), typeCap .isAggregatable (),
593
- isAlias .get ()));
650
+ isAliasFieldType .get ()));
594
651
}
595
652
}
596
653
}
@@ -605,4 +662,141 @@ private static List<EsIndex> buildIndices(DataTypeRegistry typeRegistry, String[
605
662
foundIndices .sort (Comparator .comparing (EsIndex ::name ));
606
663
return foundIndices ;
607
664
}
665
+
666
+
667
+ /*
668
+ * Checks if the field is valid (same type and same capabilities - searchable/aggregatable) across indices belonging to a list
669
+ * of aliases.
670
+ * A field can look like the example below (generated by field_caps API).
671
+ * "name": {
672
+ * "text": {
673
+ * "type": "text",
674
+ * "searchable": false,
675
+ * "aggregatable": false,
676
+ * "indices": [
677
+ * "bar",
678
+ * "foo"
679
+ * ],
680
+ * "non_searchable_indices": [
681
+ * "foo"
682
+ * ]
683
+ * },
684
+ * "keyword": {
685
+ * "type": "keyword",
686
+ * "searchable": false,
687
+ * "aggregatable": true,
688
+ * "non_aggregatable_indices": [
689
+ * "bar", "baz"
690
+ * ]
691
+ * }
692
+ * }
693
+ */
694
+ private static Map <String , InvalidMappedField > getInvalidFieldsForAliases (String fieldName , Map <String , FieldCapabilities > types ,
695
+ ImmutableOpenMap <String , List <AliasMetaData >> aliases ) {
696
+ if (aliases == null || aliases .isEmpty ()) {
697
+ return emptyMap ();
698
+ }
699
+ Map <String , InvalidMappedField > invalidFields = new HashMap <>();
700
+ Map <String , Set <String >> typesErrors = new HashMap <>(); // map holding aliases and a list of unique field types across its indices
701
+ Map <String , Set <String >> aliasToIndices = new HashMap <>(); // map with aliases and their list of indices
702
+
703
+ Iterator <ObjectObjectCursor <String , List <AliasMetaData >>> iter = aliases .iterator ();
704
+ while (iter .hasNext ()) {
705
+ ObjectObjectCursor <String , List <AliasMetaData >> index = iter .next ();
706
+ for (AliasMetaData aliasMetaData : index .value ) {
707
+ String aliasName = aliasMetaData .alias ();
708
+ aliasToIndices .putIfAbsent (aliasName , new HashSet <>());
709
+ aliasToIndices .get (aliasName ).add (index .key );
710
+ }
711
+ }
712
+
713
+ // iterate over each type
714
+ for (Entry <String , FieldCapabilities > type : types .entrySet ()) {
715
+ String esFieldType = type .getKey ();
716
+ if (esFieldType == UNMAPPED ) {
717
+ continue ;
718
+ }
719
+ String [] indices = type .getValue ().indices ();
720
+ // if there is a list of indices where this field type is defined
721
+ if (indices != null ) {
722
+ // Look at all these indices' aliases and add the type of the field to a list (Set) with unique elements.
723
+ // A valid mapping for a field in an index alias should contain only one type. If it doesn't, this means that field
724
+ // is mapped as different types across the indices in this index alias.
725
+ for (String index : indices ) {
726
+ List <AliasMetaData > indexAliases = aliases .get (index );
727
+ if (indexAliases == null ) {
728
+ continue ;
729
+ }
730
+ for (AliasMetaData aliasMetaData : indexAliases ) {
731
+ String aliasName = aliasMetaData .alias ();
732
+ if (typesErrors .containsKey (aliasName )) {
733
+ typesErrors .get (aliasName ).add (esFieldType );
734
+ } else {
735
+ Set <String > fieldTypes = new HashSet <>();
736
+ fieldTypes .add (esFieldType );
737
+ typesErrors .put (aliasName , fieldTypes );
738
+ }
739
+ }
740
+ }
741
+ }
742
+ }
743
+
744
+ for (String aliasName : aliasToIndices .keySet ()) {
745
+ // if, for the same index alias, there are multiple field types for this fieldName ie the index alias has indices where the same
746
+ // field name is of different types
747
+ Set <String > esFieldTypes = typesErrors .get (aliasName );
748
+ if (esFieldTypes != null && esFieldTypes .size () > 1 ) {
749
+ // consider the field as invalid, for the currently checked index alias
750
+ // the error message doesn't actually matter
751
+ invalidFields .put (aliasName , new InvalidMappedField (fieldName ));
752
+ } else {
753
+ // if the field type is the same across all this alias' indices, check the field's capabilities (searchable/aggregatable)
754
+ for (Entry <String , FieldCapabilities > type : types .entrySet ()) {
755
+ if (type .getKey () == UNMAPPED ) {
756
+ continue ;
757
+ }
758
+ FieldCapabilities f = type .getValue ();
759
+
760
+ // the existence of a list of non_aggregatable_indices is an indication that not all indices have the same capabilities
761
+ // but this list can contain indices belonging to other aliases, so we need to check only for this alias
762
+ if (f .nonAggregatableIndices () != null ) {
763
+ Set <String > aliasIndices = aliasToIndices .get (aliasName );
764
+ int nonAggregatableCount = 0 ;
765
+ // either all or none of the non-aggregatable indices belonging to a certain alias should be in this list
766
+ for (String nonAggIndex : f .nonAggregatableIndices ()) {
767
+ if (aliasIndices .contains (nonAggIndex )) {
768
+ nonAggregatableCount ++;
769
+ }
770
+ }
771
+ if (nonAggregatableCount > 0 && nonAggregatableCount != aliasIndices .size ()) {
772
+ invalidFields .put (aliasName , new InvalidMappedField (fieldName ));
773
+ break ;
774
+ }
775
+ }
776
+
777
+ // perform the same check for non_searchable_indices list
778
+ if (f .nonSearchableIndices () != null ) {
779
+ Set <String > aliasIndices = aliasToIndices .get (aliasName );
780
+ int nonSearchableCount = 0 ;
781
+ // either all or none of the non-searchable indices belonging to a certain alias should be in this list
782
+ for (String nonSearchIndex : f .nonSearchableIndices ()) {
783
+ if (aliasIndices .contains (nonSearchIndex )) {
784
+ nonSearchableCount ++;
785
+ }
786
+ }
787
+ if (nonSearchableCount > 0 && nonSearchableCount != aliasIndices .size ()) {
788
+ invalidFields .put (aliasName , new InvalidMappedField (fieldName ));
789
+ break ;
790
+ }
791
+ }
792
+ }
793
+ }
794
+ }
795
+
796
+ if (invalidFields .size () > 0 ) {
797
+ return invalidFields ;
798
+ }
799
+ // everything checks
800
+ return emptyMap ();
801
+ }
608
802
}
0 commit comments