|
85 | 85 | has_invalid_projection_merge_functions, |
86 | 86 | ) |
87 | 87 | from snowflake.snowpark._internal.utils import ( |
88 | | - is_sql_select_statement, |
89 | 88 | ExprAliasUpdateDict, |
| 89 | + is_sql_select_statement, |
90 | 90 | ) |
91 | 91 | import snowflake.snowpark.context as context |
92 | 92 |
|
@@ -1592,6 +1592,70 @@ def select(self, cols: List[Expression]) -> "SelectStatement": |
1592 | 1592 | ) |
1593 | 1593 | ) |
1594 | 1594 |
|
| 1595 | + # When describe reduction is on and the inner select already has resolved |
| 1596 | + # attributes, infer new.attributes for this outer select by reusing datatype and |
| 1597 | + # nullable from the subquery: (0) skip if parent column names collide, (1) index |
| 1598 | + # attributes by exact parent Attribute.name, (2) walk new.projection, (3) only |
| 1599 | + # handle plain columns or Alias(column), (4) resolve source by exact string match |
| 1600 | + # of the projection source name to that name (no quote_name / normalization), |
| 1601 | + # (5) assign only if every output column was inferred (length matches projection). |
| 1602 | + if self._session.reduce_describe_query_enabled and self.attributes is not None: |
| 1603 | + parent_attributes = self.attributes |
| 1604 | + projection = new.projection |
| 1605 | + inferred_attributes: Optional[List[Attribute]] = None |
| 1606 | + # Skip: no projection to walk (do not assert; leave new.attributes unchanged). |
| 1607 | + if projection is not None: |
| 1608 | + # Skip: duplicate output names on the parent — dict/lookup would be ambiguous. |
| 1609 | + attributes_by_column_name: Dict[str, Attribute] = {} |
| 1610 | + collision = False |
| 1611 | + for attr in parent_attributes: |
| 1612 | + key = attr.name |
| 1613 | + existing = attributes_by_column_name.get(key) |
| 1614 | + # Skip: two parent columns share the same name string. |
| 1615 | + if existing is not None and existing is not attr: |
| 1616 | + collision = True |
| 1617 | + break |
| 1618 | + attributes_by_column_name[key] = attr |
| 1619 | + if not collision: |
| 1620 | + inferred_attributes = [] |
| 1621 | + for expr in projection: |
| 1622 | + source_column_name: Optional[str] = None |
| 1623 | + projected_column_name: Optional[str] = None |
| 1624 | + if isinstance(expr, (Attribute, UnresolvedAttribute)): |
| 1625 | + source_column_name = expr.name |
| 1626 | + projected_column_name = expr.name |
| 1627 | + elif isinstance(expr, Alias) and isinstance( |
| 1628 | + expr.child, (Attribute, UnresolvedAttribute) |
| 1629 | + ): |
| 1630 | + source_column_name = expr.child.name |
| 1631 | + projected_column_name = expr.name |
| 1632 | + else: |
| 1633 | + # Skip: not a plain column or Alias(Attribute|UnresolvedAttribute). |
| 1634 | + inferred_attributes = [] |
| 1635 | + break |
| 1636 | + |
| 1637 | + if source_column_name is None or projected_column_name is None: |
| 1638 | + # Skip: missing projected output name. |
| 1639 | + inferred_attributes = [] |
| 1640 | + break |
| 1641 | + source_attr = attributes_by_column_name.get(source_column_name) |
| 1642 | + # Skip: no parent column for this source name. |
| 1643 | + if source_attr is None: |
| 1644 | + inferred_attributes = [] |
| 1645 | + break |
| 1646 | + inferred_attributes.append( |
| 1647 | + Attribute( |
| 1648 | + projected_column_name, |
| 1649 | + source_attr.datatype, |
| 1650 | + source_attr.nullable, |
| 1651 | + ) |
| 1652 | + ) |
| 1653 | + if len(inferred_attributes) != len(projection): |
| 1654 | + # Skip: incomplete inference (includes defensive mismatch). |
| 1655 | + inferred_attributes = None |
| 1656 | + if inferred_attributes is not None: |
| 1657 | + new.attributes = inferred_attributes |
| 1658 | + |
1595 | 1659 | new.flatten_disabled = disable_next_level_flatten |
1596 | 1660 | assert new.projection is not None |
1597 | 1661 | new._column_states = derive_column_states_from_subquery( |
|
0 commit comments