@@ -2881,7 +2881,7 @@ def _list_of_calls_to_exp(value: t.List[t.Tuple[str, t.Dict[str, t.Any]]]) -> ex
28812881 )
28822882
28832883
2884- def _has_ordinal_references (query : exp .Select ) -> bool :
2884+ def _has_ordinal_references (query : exp .Query ) -> bool :
28852885 order = query .args .get ("order" )
28862886 if order and any (
28872887 isinstance (ob .this , exp .Literal ) and ob .this .is_number for ob in order .expressions
@@ -2893,7 +2893,28 @@ def _has_ordinal_references(query: exp.Select) -> bool:
28932893 )
28942894
28952895
2896- def _is_valid_added_projection (projection : exp .Expr ) -> bool :
2896+ def _has_ordinal_references_in_scope (query : exp .Select ) -> bool :
2897+ """Return whether the SELECT or any set operation it is a branch of uses ordinal references.
2898+
2899+ An ORDER BY on a UNION is attached to the set operation rather than to its branches, but its
2900+ ordinals address the branch projections positionally, so a mid-list addition shifts them too.
2901+ Ascending only while the direct parent is a set operation keeps the walk within the projection
2902+ list's own output scope: it covers chained set operations but stops at a subquery or CTE
2903+ boundary, whose ordinals refer to that enclosing scope's projections instead.
2904+ """
2905+ if _has_ordinal_references (query ):
2906+ return True
2907+
2908+ parent = query .parent
2909+ while isinstance (parent , exp .SetOperation ):
2910+ if _has_ordinal_references (parent ):
2911+ return True
2912+ parent = parent .parent
2913+
2914+ return False
2915+
2916+
2917+ def _added_projection_preserves_cardinality (projection : exp .Expr ) -> bool :
28972918 """Return whether an added projection preserves the query's row cardinality.
28982919
28992920 A directly projected UDTF can emit multiple rows. SQLMesh treats it as safe when its nearest
@@ -2912,7 +2933,7 @@ def _is_valid_added_projection(projection: exp.Expr) -> bool:
29122933 )
29132934
29142935
2915- def _projection_lists_match (previous_query : exp .Select , this_query : exp .Select ) -> bool :
2936+ def _projections_only_safely_added (previous_query : exp .Select , this_query : exp .Select ) -> bool :
29162937 """Return whether a SELECT's projections differ only through safe additions.
29172938
29182939 Every previous projection must occur unchanged and in the same order in the current list.
@@ -2923,45 +2944,36 @@ def _projection_lists_match(previous_query: exp.Select, this_query: exp.Select)
29232944 previous_projections = previous_query .expressions
29242945 this_projections = this_query .expressions
29252946 this_index = 0
2926- added_at : list [int ] = []
2927- matched_at : list [int ] = []
2947+ added_before_existing = False
29282948
29292949 # Match each previous projection to the earliest identical current projection. Any current
2930- # projections skipped along the way are additions.
2950+ # projections skipped along the way are additions placed before an existing projection .
29312951 for previous_projection in previous_projections :
29322952 while (
29332953 this_index < len (this_projections )
29342954 and previous_projection != this_projections [this_index ]
29352955 ):
2936- if not _is_valid_added_projection (this_projections [this_index ]):
2956+ if not _added_projection_preserves_cardinality (this_projections [this_index ]):
29372957 return False
29382958
2939- added_at . append ( this_index )
2959+ added_before_existing = True
29402960 this_index += 1
29412961
29422962 if this_index == len (this_projections ):
29432963 return False
29442964
2945- matched_at .append (this_index )
29462965 this_index += 1
29472966
2948- # Once all previous projections are matched, every remaining projection was appended.
2967+ # Once all previous projections are matched, every remaining projection was appended, which
2968+ # leaves the positions of the existing projections untouched.
29492969 for index in range (this_index , len (this_projections )):
2950- if not _is_valid_added_projection (this_projections [index ]):
2970+ if not _added_projection_preserves_cardinality (this_projections [index ]):
29512971 return False
2952- added_at .append (index )
2953-
2954- # Be conservative about every mid-list addition when ordinals are present. Determining whether
2955- # a particular ordinal was shifted would couple this comparison to dialect-specific semantics.
2956- if (
2957- added_at
2958- and matched_at
2959- and _has_ordinal_references (this_query )
2960- and any (index < matched_at [- 1 ] for index in added_at )
2961- ):
2962- return False
29632972
2964- return True
2973+ # Be conservative about every addition placed before an existing projection when ordinals are
2974+ # present. Determining whether a particular ordinal was shifted would couple this comparison
2975+ # to dialect-specific semantics.
2976+ return not (added_before_existing and _has_ordinal_references_in_scope (this_query ))
29652977
29662978
29672979def _is_only_projection_additions (
@@ -3004,7 +3016,7 @@ def _is_only_projection_additions(
30043016 and isinstance (this_expression , exp .Select )
30053017 and arg_key == "expressions"
30063018 ):
3007- if not _projection_lists_match (previous_expression , this_expression ):
3019+ if not _projections_only_safely_added (previous_expression , this_expression ):
30083020 return False
30093021 elif len (previous_value ) != len (this_value ):
30103022 return False
0 commit comments