5656import  org .elasticsearch .xpack .esql .parser .QueryParams ;
5757import  org .elasticsearch .xpack .esql .plan .IndexPattern ;
5858import  org .elasticsearch .xpack .esql .plan .logical .Aggregate ;
59+ import  org .elasticsearch .xpack .esql .plan .logical .Drop ;
5960import  org .elasticsearch .xpack .esql .plan .logical .Enrich ;
61+ import  org .elasticsearch .xpack .esql .plan .logical .Eval ;
62+ import  org .elasticsearch .xpack .esql .plan .logical .Filter ;
63+ import  org .elasticsearch .xpack .esql .plan .logical .InlineStats ;
6064import  org .elasticsearch .xpack .esql .plan .logical .Keep ;
65+ import  org .elasticsearch .xpack .esql .plan .logical .Limit ;
6166import  org .elasticsearch .xpack .esql .plan .logical .LogicalPlan ;
67+ import  org .elasticsearch .xpack .esql .plan .logical .MvExpand ;
68+ import  org .elasticsearch .xpack .esql .plan .logical .OrderBy ;
6269import  org .elasticsearch .xpack .esql .plan .logical .Project ;
6370import  org .elasticsearch .xpack .esql .plan .logical .RegexExtract ;
71+ import  org .elasticsearch .xpack .esql .plan .logical .Rename ;
72+ import  org .elasticsearch .xpack .esql .plan .logical .TopN ;
6473import  org .elasticsearch .xpack .esql .plan .logical .UnresolvedRelation ;
6574import  org .elasticsearch .xpack .esql .plan .logical .join .InlineJoin ;
6675import  org .elasticsearch .xpack .esql .plan .logical .join .JoinTypes ;
@@ -443,6 +452,7 @@ private void preAnalyzeIndices(
443452
444453    /** 
445454     * Check if there are any clusters to search. 
455+      * 
446456     * @return true if there are no clusters to search, false otherwise 
447457     */ 
448458    private  boolean  allCCSClustersSkipped (
@@ -542,6 +552,8 @@ static PreAnalysisResult fieldNames(LogicalPlan parsed, Set<String> enrichPolicy
542552        var  keepJoinRefsBuilder  = AttributeSet .builder ();
543553        Set <String > wildcardJoinIndices  = new  java .util .HashSet <>();
544554
555+         boolean [] canRemoveAliases  = new  boolean [] { true  };
556+ 
545557        parsed .forEachDown (p  -> {// go over each plan top-down 
546558            if  (p  instanceof  RegexExtract  re ) { // for Grok and Dissect 
547559                // remove other down-the-tree references to the extracted fields 
@@ -587,20 +599,37 @@ static PreAnalysisResult fieldNames(LogicalPlan parsed, Set<String> enrichPolicy
587599                }
588600            }
589601
590-             // remove any already discovered UnresolvedAttributes that are in fact aliases defined later down in the tree 
591-             // for example "from test | eval x = salary | stats max = max(x) by gender" 
592-             // remove the UnresolvedAttribute "x", since that is an Alias defined in "eval" 
593-             AttributeSet  planRefs  = p .references ();
594-             Set <String > fieldNames  = planRefs .names ();
595-             p .forEachExpressionDown (Alias .class , alias  -> {
596-                 // do not remove the UnresolvedAttribute that has the same name as its alias, ie "rename id = id" 
597-                 // or the UnresolvedAttributes that are used in Functions that have aliases "STATS id = MAX(id)" 
598-                 if  (fieldNames .contains (alias .name ())) {
599-                     return ;
600-                 }
601-                 referencesBuilder .removeIf (attr  -> matchByName (attr , alias .name (), keepCommandRefsBuilder .contains (attr )));
602-             });
602+             // If the current node in the tree is of type JOIN (lookup join, inlinestats) or ENRICH or other type of 
603+             // command that we may add in the future which can override already defined Aliases with EVAL 
604+             // (for example 
605+             // 
606+             // from test 
607+             // | eval ip = 123 
608+             // | enrich ips_policy ON hostname 
609+             // | rename ip AS my_ip 
610+             // 
611+             // and ips_policy enriches the results with the same name ip field), 
612+             // these aliases should be kept in the list of fields. 
613+             if  (canRemoveAliases [0 ] && couldOverrideAliases (p )) {
614+                 canRemoveAliases [0 ] = false ;
615+             }
616+             if  (canRemoveAliases [0 ]) {
617+                 // remove any already discovered UnresolvedAttributes that are in fact aliases defined later down in the tree 
618+                 // for example "from test | eval x = salary | stats max = max(x) by gender" 
619+                 // remove the UnresolvedAttribute "x", since that is an Alias defined in "eval" 
620+                 AttributeSet  planRefs  = p .references ();
621+                 Set <String > fieldNames  = planRefs .names ();
622+                 p .forEachExpressionDown (Alias .class , alias  -> {
623+                     // do not remove the UnresolvedAttribute that has the same name as its alias, ie "rename id AS id" 
624+                     // or the UnresolvedAttributes that are used in Functions that have aliases "STATS id = MAX(id)" 
625+                     if  (fieldNames .contains (alias .name ())) {
626+                         return ;
627+                     }
628+                     referencesBuilder .removeIf (attr  -> matchByName (attr , alias .name (), keepCommandRefsBuilder .contains (attr )));
629+                 });
630+             }
603631        });
632+ 
604633        // Add JOIN ON column references afterward to avoid Alias removal 
605634        referencesBuilder .addAll (keepJoinRefsBuilder );
606635        // If any JOIN commands need wildcard field-caps calls, persist the index names 
@@ -624,6 +653,29 @@ static PreAnalysisResult fieldNames(LogicalPlan parsed, Set<String> enrichPolicy
624653        }
625654    }
626655
656+     /** 
657+      * Could a plan "accidentally" override aliases? 
658+      * Examples are JOIN and ENRICH, that _could_ produce fields with the same 
659+      * name of an existing alias, based on their index mapping. 
660+      * Here we just have to consider commands where this information is not available before index resolution, 
661+      * eg. EVAL, GROK, DISSECT can override an alias, but we know it in advance, ie. we don't need to resolve indices to know. 
662+      */ 
663+     private  static  boolean  couldOverrideAliases (LogicalPlan  p ) {
664+         return  (p  instanceof  Aggregate 
665+             || p  instanceof  Drop 
666+             || p  instanceof  Eval 
667+             || p  instanceof  Filter 
668+             || p  instanceof  InlineStats 
669+             || p  instanceof  Keep 
670+             || p  instanceof  Limit 
671+             || p  instanceof  MvExpand 
672+             || p  instanceof  OrderBy 
673+             || p  instanceof  Project 
674+             || p  instanceof  RegexExtract 
675+             || p  instanceof  Rename 
676+             || p  instanceof  TopN ) == false ;
677+     }
678+ 
627679    private  static  boolean  matchByName (Attribute  attr , String  other , boolean  skipIfPattern ) {
628680        boolean  isPattern  = Regex .isSimpleMatchPattern (attr .name ());
629681        if  (skipIfPattern  && isPattern ) {
0 commit comments