@@ -26,15 +26,24 @@ public final class PushDownMvExpandPastProject extends OptimizerRules.OptimizerR
2626 protected LogicalPlan rule (MvExpand mvExpand ) {
2727 if (mvExpand .child () instanceof Project pj ) {
2828 List <NamedExpression > projections = new ArrayList <>(pj .projections ());
29+ List <Attribute > output = mvExpand .output ();
2930
30- // Skip if any projection alias shadows an input field name, as injecting an Eval would cause
31- // duplicate output attributes. This can happen with aliases generated by ResolveUnionTypesInUnionAll.
31+ // Skip if the expanded field has the same name as a field in the projection's input set, and
32+ // the projection shadows that specific field from the projection input set.
33+ // Pushing down the MvExpand in such cases would cause duplicate output attributes.
34+ // This can happen with aliases generated by ResolveUnionTypesInUnionAll.
3235 // Example:
3336 // MvExpand[salary{r}#168,salary{r}#175]
3437 // \_Project[[$$salary$converted_to$keyword{r$}#178 AS salary#168]]
3538 // \_UnionAll[[salary{r}#174, $$salary$converted_to$keyword{r$}#178]]
39+ String expandedFieldName = mvExpand .expanded ().name ();
3640 Set <String > inputNames = pj .inputSet ().stream ().map (NamedExpression ::name ).collect (Collectors .toSet ());
37- if (projections .stream ().anyMatch (e -> e instanceof Alias alias && inputNames .contains (alias .toAttribute ().name ()))) {
41+ if (projections .stream ()
42+ .anyMatch (
43+ e -> e instanceof Alias alias
44+ && inputNames .contains (expandedFieldName )
45+ && inputNames .contains (alias .toAttribute ().name ())
46+ )) {
3847 return mvExpand ;
3948 }
4049
@@ -56,7 +65,7 @@ protected LogicalPlan rule(MvExpand mvExpand) {
5665 break ;
5766 }
5867
59- // for query like: row a = 2 | eval b = a | keep * | mv_expand b
68+ // for query like: row a = 2 | eval b = a | keep * | mv_expand b
6069 Alias aliasAlias = new Alias (
6170 alias .source (),
6271 TemporaryNameUtils .temporaryName (alias .child (), alias .toAttribute (), 0 ),
@@ -67,7 +76,7 @@ protected LogicalPlan rule(MvExpand mvExpand) {
6776 mvExpand = new MvExpand (mvExpand .source (), pj , aliasAlias .toAttribute (), mvExpand .expanded ());
6877 break ;
6978 } else if (alias .child ().semanticEquals (mvExpand .target ().toAttribute ())) {
70- // for query like: row a = 2 | eval b = a | keep * | mv_expand a
79+ // for query like: row a = 2 | eval b = a | keep * | mv_expand a
7180 Alias aliasAlias = new Alias (
7281 alias .source (),
7382 TemporaryNameUtils .temporaryName (alias .child (), alias .toAttribute (), 0 ),
@@ -83,16 +92,20 @@ protected LogicalPlan rule(MvExpand mvExpand) {
8392
8493 // Build a map of aliases from the original projection
8594 AttributeMap .Builder <Alias > aliasBuilder = AttributeMap .builder ();
86- pj .forEachExpression (Alias .class , alias -> aliasBuilder .put (alias .toAttribute (), alias ));
95+ projections .forEach (ne -> {
96+ if (ne instanceof Alias alias ) {
97+ aliasBuilder .put (alias .toAttribute (), alias );
98+ }
99+ });
87100 AttributeMap <Alias > aliases = aliasBuilder .build ();
88101
89102 // Push down the MvExpand past the Project
90103 MvExpand pushedDownMvExpand = mvExpand .replaceChild (pj .child ());
91104
92105 // Create a new projection at the top based on mvExpand.output(), plugging back in the aliases
93106 List <NamedExpression > newProjections = new ArrayList <>();
94- for (Attribute outputExpr : mvExpand . output () ) {
95- Alias alias = aliases .resolve (outputExpr , null );
107+ for (Attribute outputExpr : output ) {
108+ Alias alias = aliases .get (outputExpr );
96109 if (alias == null ) {
97110 // No alias, use the output expression directly
98111 newProjections .add (outputExpr );
0 commit comments