Skip to content

Commit ec93edf

Browse files
authored
fix: make DataType.type return self instead of a clone (#7708)
annotate_types stored a deep clone of each DataType node in the node's own _type slot via `self._set_type(e, e.copy())`. The .copy() existed to avoid a self-cycle that would break recursive consumers of _type (serde.dump, etc). But Expr.__deepcopy__ also recursively walked _type, so every later e.copy() of an ancestor dragged the shadow type along. The work compounded across nested STRUCT levels and turned annotate into a >100s operation on wide, deeply nested STRUCT casts. Conceptually, asking "what type is this DataType?" should answer "itself". Special-case DataType in Expression.type the same way Cast already is, via an is_data_type ClassVar: - Expression.type returns self when self.is_data_type is True. - DataType (and all its subclasses) set is_data_type = True. - The DataType annotator becomes a no-op (no copy needed; no _type to set). - serde.dump skips the TYPE field when node.type is node, since storing a self-reference is meaningless and was the original reason the annotator needed the .copy(). - _to_s uses node.is_data_type so DataType subclasses (IntervalSpan, PseudoType, ObjectIdentifier) are skipped uniformly during repr. Behavior change to call out: on a DataType `dt`, `dt.type is dt` is now true (previously it returned a clone, equal but not identical). Equality semantics are unchanged.
1 parent 63608d6 commit ec93edf

4 files changed

Lines changed: 8 additions & 3 deletions

File tree

sqlglot/expressions/core.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class Expr:
8888
_hash_raw_args: t.ClassVar[bool] = False
8989
is_subquery: t.ClassVar[bool] = False
9090
is_cast: t.ClassVar[bool] = False
91+
is_data_type: t.ClassVar[bool] = False
9192

9293
args: dict[str, t.Any]
9394
parent: Expr | None
@@ -962,6 +963,8 @@ def output_name(self) -> str:
962963

963964
@property
964965
def type(self) -> DataType | None:
966+
if self.is_data_type:
967+
return self # type: ignore[return-value]
965968
if self.is_cast:
966969
return self._type or self.to # type: ignore[attr-defined]
967970
return self._type
@@ -2552,7 +2555,7 @@ def _to_s(node: t.Any, verbose: bool = False, level: int = 0, repr_str: bool = F
25522555
if isinstance(node, Expr):
25532556
args = {k: v for k, v in node.args.items() if (v is not None and v != []) or verbose}
25542557

2555-
if (node.type or verbose) and type(node).__name__ != "DataType":
2558+
if (node.type or verbose) and not node.is_data_type:
25562559
args["_type"] = node.type
25572560
if node.comments or verbose:
25582561
args["_comments"] = node.comments

sqlglot/expressions/datatypes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ class DataType(Expression):
187187
"collate": False,
188188
}
189189

190+
is_data_type: t.ClassVar[bool] = True
191+
190192
Type: t.ClassVar[Type[DType]] = DType
191193

192194
STRUCT_TYPES: t.ClassVar[set[DType]] = {

sqlglot/serde.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def dump(expression: exp.Expr) -> list[dict[str, t.Any]]:
5050

5151
payload[CLASS] = klass
5252

53-
if node.type:
53+
if node.type and node.type is not node:
5454
payload[TYPE] = dump(node.type)
5555
if node.comments:
5656
payload[COMMENTS] = node.comments

sqlglot/typing/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@
332332
e, exp.DType.BIGINT if e.args.get("big_int") else exp.DType.INT
333333
)
334334
},
335-
exp.DataType: {"annotator": lambda self, e: self._set_type(e, e.copy())},
335+
exp.DataType: {"annotator": lambda _, e: e},
336336
exp.Div: {"annotator": lambda self, e: self._annotate_div(e)},
337337
exp.Distinct: {"annotator": lambda self, e: self._annotate_by_args(e, "expressions")},
338338
exp.Dot: {"annotator": lambda self, e: self._annotate_dot(e)},

0 commit comments

Comments
 (0)