Skip to content

Commit 24f8a57

Browse files
SNOW-3384967: reduce desc query for alias (#4208)
1 parent 2051e02 commit 24f8a57

4 files changed

Lines changed: 346 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
- Fixed a bug where `StringType` columns from Iceberg tables were not recognized as max-size strings.
1313
- Improved the `FileNotFoundError` message raised when `INFER_SCHEMA` returns zero rows so it also points to file format options (`PARSE_HEADER`, `SKIP_HEADER`, `ON_ERROR=CONTINUE`) that can silently filter everything out, instead of only suggesting a missing path.
1414

15+
#### Improvements
16+
17+
- When `Session.reduce_describe_query_enabled` is enabled, fewer DESCRIBE queries are issued when the outer query only projects or renames columns from an inner subquery whose column types are already known.
18+
1519
## 1.50.0 (2026-04-23)
1620

1721
### Snowpark Python API Updates

src/snowflake/snowpark/_internal/analyzer/select_statement.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@
8585
has_invalid_projection_merge_functions,
8686
)
8787
from snowflake.snowpark._internal.utils import (
88-
is_sql_select_statement,
8988
ExprAliasUpdateDict,
89+
is_sql_select_statement,
9090
)
9191
import snowflake.snowpark.context as context
9292

@@ -1592,6 +1592,70 @@ def select(self, cols: List[Expression]) -> "SelectStatement":
15921592
)
15931593
)
15941594

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+
15951659
new.flatten_disabled = disable_next_level_flatten
15961660
assert new.projection is not None
15971661
new._column_states = derive_column_states_from_subquery(

tests/integ/test_cte.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ def test_binary(session, type, action):
259259

260260
def test_join_with_alias_dataframe(session):
261261
expected_describe_count = (
262-
3
262+
2
263263
if (session.reduce_describe_query_enabled and session.sql_simplifier_enabled)
264264
else 4
265265
)

0 commit comments

Comments
 (0)