Skip to content

Commit

Permalink
fix(duckdb)!: Fix STRUCT cast generation (#4366)
Browse files Browse the repository at this point in the history
  • Loading branch information
VaggelisD authored and georgesittas committed Nov 11, 2024
1 parent 79c675a commit 60625ea
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 9 deletions.
18 changes: 12 additions & 6 deletions sqlglot/dialects/duckdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,26 +156,32 @@ def _struct_sql(self: DuckDB.Generator, expression: exp.Struct) -> str:

# BigQuery allows inline construction such as "STRUCT<a STRING, b INTEGER>('str', 1)" which is
# canonicalized to "ROW('str', 1) AS STRUCT(a TEXT, b INT)" in DuckDB
# The transformation to ROW will take place if a cast to STRUCT / ARRAY of STRUCTs is found
# The transformation to ROW will take place if:
# 1. The STRUCT itself does not have proper fields (key := value) as a "proper" STRUCT would
# 2. A cast to STRUCT / ARRAY of STRUCTs is found
ancestor_cast = expression.find_ancestor(exp.Cast)
is_struct_cast = ancestor_cast and any(
casted_type.is_type(exp.DataType.Type.STRUCT)
for casted_type in ancestor_cast.find_all(exp.DataType)
is_bq_inline_struct = (
(expression.find(exp.PropertyEQ) is None)
and ancestor_cast
and any(
casted_type.is_type(exp.DataType.Type.STRUCT)
for casted_type in ancestor_cast.find_all(exp.DataType)
)
)

for i, expr in enumerate(expression.expressions):
is_property_eq = isinstance(expr, exp.PropertyEQ)
value = expr.expression if is_property_eq else expr

if is_struct_cast:
if is_bq_inline_struct:
args.append(self.sql(value))
else:
key = expr.name if is_property_eq else f"_{i}"
args.append(f"{self.sql(exp.Literal.string(key))}: {self.sql(value)}")

csv_args = ", ".join(args)

return f"ROW({csv_args})" if is_struct_cast else f"{{{csv_args}}}"
return f"ROW({csv_args})" if is_bq_inline_struct else f"{{{csv_args}}}"


def _datatype_sql(self: DuckDB.Generator, expression: exp.DataType) -> str:
Expand Down
7 changes: 4 additions & 3 deletions tests/dialects/test_duckdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,7 @@ def test_cast(self):
self.validate_identity("CAST(x AS BINARY)", "CAST(x AS BLOB)")
self.validate_identity("CAST(x AS VARBINARY)", "CAST(x AS BLOB)")
self.validate_identity("CAST(x AS LOGICAL)", "CAST(x AS BOOLEAN)")
self.validate_identity("""CAST({'i': 1, 's': 'foo'} AS STRUCT("s" TEXT, "i" INT))""")
self.validate_identity(
"CAST(ROW(1, ROW(1)) AS STRUCT(number BIGINT, row STRUCT(number BIGINT)))"
)
Expand All @@ -1163,19 +1164,19 @@ def test_cast(self):
)
self.validate_identity(
"CAST([[STRUCT_PACK(a := 1)]] AS STRUCT(a BIGINT)[][])",
"CAST([[ROW(1)]] AS STRUCT(a BIGINT)[][])",
"CAST([[{'a': 1}]] AS STRUCT(a BIGINT)[][])",
)
self.validate_identity(
"CAST([STRUCT_PACK(a := 1)] AS STRUCT(a BIGINT)[])",
"CAST([ROW(1)] AS STRUCT(a BIGINT)[])",
"CAST([{'a': 1}] AS STRUCT(a BIGINT)[])",
)
self.validate_identity(
"STRUCT_PACK(a := 'b')::json",
"CAST({'a': 'b'} AS JSON)",
)
self.validate_identity(
"STRUCT_PACK(a := 'b')::STRUCT(a TEXT)",
"CAST(ROW('b') AS STRUCT(a TEXT))",
"CAST({'a': 'b'} AS STRUCT(a TEXT))",
)

self.validate_all(
Expand Down

0 comments on commit 60625ea

Please sign in to comment.