66package org .elasticsearch .xpack .ql .index ;
77
88import com .carrotsearch .hppc .cursors .ObjectCursor ;
9+ import com .carrotsearch .hppc .cursors .ObjectObjectCursor ;
910
1011import org .elasticsearch .ElasticsearchSecurityException ;
1112import org .elasticsearch .action .ActionListener ;
1617import org .elasticsearch .action .admin .indices .get .GetIndexResponse ;
1718import org .elasticsearch .action .fieldcaps .FieldCapabilities ;
1819import org .elasticsearch .action .fieldcaps .FieldCapabilitiesRequest ;
20+ import org .elasticsearch .action .fieldcaps .FieldCapabilitiesResponse ;
1921import org .elasticsearch .action .support .IndicesOptions ;
2022import org .elasticsearch .action .support .IndicesOptions .Option ;
2123import org .elasticsearch .action .support .IndicesOptions .WildcardStates ;
2224import org .elasticsearch .client .Client ;
2325import org .elasticsearch .cluster .metadata .AliasMetaData ;
2426import org .elasticsearch .common .Strings ;
27+ import org .elasticsearch .common .collect .ImmutableOpenMap ;
2528import org .elasticsearch .index .IndexNotFoundException ;
2629import org .elasticsearch .index .IndexSettings ;
2730import org .elasticsearch .xpack .ql .QlIllegalArgumentException ;
4245import java .util .Collections ;
4346import java .util .Comparator ;
4447import java .util .EnumSet ;
48+ import java .util .HashMap ;
4549import java .util .HashSet ;
4650import java .util .Iterator ;
4751import java .util .LinkedHashMap ;
52+ import java .util .LinkedHashSet ;
4853import java .util .List ;
4954import java .util .Map ;
5055import java .util .Map .Entry ;
@@ -164,7 +169,6 @@ public boolean equals(Object obj) {
164169 private final String clusterName ;
165170 private final DataTypeRegistry typeRegistry ;
166171
167-
168172 public IndexResolver (Client client , String clusterName , DataTypeRegistry typeRegistry ) {
169173 this .client = client ;
170174 this .clusterName = clusterName ;
@@ -296,7 +300,7 @@ public static IndexResolution mergedMappings(DataTypeRegistry typeRegistry, Stri
296300 }
297301
298302 // 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 ) -> {
300304 StringBuilder errorMessage = new StringBuilder ();
301305
302306 boolean hasUnmapped = types .containsKey (UNMAPPED );
@@ -473,17 +477,32 @@ private static FieldCapabilitiesRequest createFieldCapsRequest(String index, boo
473477 public void resolveAsSeparateMappings (String indexWildcard , String javaRegex , boolean includeFrozen ,
474478 ActionListener <List <EsIndex >> listener ) {
475479 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 ));
481492
482493 }
483494
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 );
487506 }
488507
489508 private static class Fields {
@@ -496,16 +515,27 @@ private static class Fields {
496515 * each field.
497516 */
498517 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 ,
500519 Function <String , String > indexNameProcessor ,
501520 BiFunction <String , Map <String , FieldCapabilities >, InvalidMappedField > validityVerifier ) {
502521
503- if (indexNames == null || indexNames .length == 0 ) {
522+ if (( indexNames == null || indexNames .length == 0 ) && ( aliases == null || aliases . isEmpty ()) ) {
504523 return emptyList ();
505524 }
506525
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 );
509539 Pattern pattern = javaRegex != null ? Pattern .compile (javaRegex ) : null ;
510540
511541 // sort fields in reverse order to build the field hierarchy
@@ -525,6 +555,8 @@ private static List<EsIndex> buildIndices(DataTypeRegistry typeRegistry, String[
525555 Map <String , FieldCapabilities > types = new LinkedHashMap <>(entry .getValue ());
526556 // apply verification and possibly remove the "duplicate" CONSTANT_KEYWORD field type
527557 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 );
528560
529561 // filter meta fields and unmapped
530562 FieldCapabilities unmapped = types .get (UNMAPPED );
@@ -545,7 +577,7 @@ private static List<EsIndex> buildIndices(DataTypeRegistry typeRegistry, String[
545577 List <String > concreteIndices = null ;
546578 if (capIndices != null ) {
547579 if (unmappedIndices .isEmpty ()) {
548- concreteIndices = asList (capIndices );
580+ concreteIndices = new ArrayList <>( asList (capIndices ) );
549581 } else {
550582 concreteIndices = new ArrayList <>(capIndices .length );
551583 for (String capIndex : capIndices ) {
@@ -559,38 +591,63 @@ private static List<EsIndex> buildIndices(DataTypeRegistry typeRegistry, String[
559591 concreteIndices = resolvedIndices ;
560592 }
561593
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+
562606 // put the field in their respective mappings
563607 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 );
566611 Fields indexFields = indices .get (indexName );
567612 if (indexFields == null ) {
568613 indexFields = new Fields ();
569614 indices .put (indexName , indexFields );
570615 }
571616 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 ) {
573630 int dot = fieldName .lastIndexOf ('.' );
574631 /*
575632 * Looking up the "tree" at the parent fields here to see if the field is an alias.
576633 * When the upper elements of the "tree" have no elements in fieldcaps, then this is an alias field. But not
577634 * always: if there are two aliases - a.b.c.alias1 and a.b.c.alias2 - only one of them will be considered alias.
578635 */
579- Holder <Boolean > isAlias = new Holder <>(false );
636+ Holder <Boolean > isAliasFieldType = new Holder <>(false );
580637 if (dot >= 0 ) {
581638 String parentName = fieldName .substring (0 , dot );
582639 if (indexFields .flattedMapping .get (parentName ) == null ) {
583640 // lack of parent implies the field is an alias
584641 if (fieldCaps .get (parentName ) == null ) {
585- isAlias .set (true );
642+ isAliasFieldType .set (true );
586643 }
587644 }
588645 }
589646
590647 createField (typeRegistry , fieldName , fieldCaps , indexFields .hierarchicalMapping , indexFields .flattedMapping ,
591648 s -> invalidField != null ? invalidField :
592649 createField (typeRegistry , s , typeCap .getType (), emptyMap (), typeCap .isAggregatable (),
593- isAlias .get ()));
650+ isAliasFieldType .get ()));
594651 }
595652 }
596653 }
@@ -605,4 +662,141 @@ private static List<EsIndex> buildIndices(DataTypeRegistry typeRegistry, String[
605662 foundIndices .sort (Comparator .comparing (EsIndex ::name ));
606663 return foundIndices ;
607664 }
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+ }
608802}
0 commit comments