Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions sqlglot/dialects/duckdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1684,6 +1684,48 @@ def zipf_sql(self: DuckDB.Generator, expression: exp.Zipf) -> str:
replacements = {"s": s, "n": n, "random_expr": random_expr}
return f"({self.sql(exp.replace_placeholders(self.ZIPF_TEMPLATE, **replacements))})"

def unixtodate_sql(self: DuckDB.Generator, expression: exp.UnixToDate) -> str:
"""
Transpile UnixToDate to DuckDB equivalent using make_timestamp_ns with Snowflake logic.

Converts Unix timestamps following Snowflake's interpretation rules:
- Values <= 31536000000 (Jan 1, 1971): treated as seconds, multiply by 1000000000
- Values <= 31536000000000 (Jan 1, 1971 in ms): treated as milliseconds, multiply by 1000000
- Values <= 31536000000000000 (Jan 1, 1971 in μs): treated as microseconds, multiply by 1000
- Larger values: treated as nanoseconds, use as-is
"""
value = expression.this

# Convert input to BIGINT first for comparisons
bigint_value = exp.cast(value, exp.DataType.Type.BIGINT)

# Use CASE to determine precision based on Snowflake thresholds and multiply accordingly so that the value is in nanoseconds
case_expr = (
exp.case()
.when(
exp.LT(this=bigint_value, expression=exp.Literal.number(31536000000)),
exp.Mul(
this=bigint_value, expression=exp.Literal.number(1000000000)
), # seconds
)
.when(
exp.LT(this=bigint_value, expression=exp.Literal.number(31536000000000)),
exp.Mul(
this=bigint_value, expression=exp.Literal.number(1000000)
), # milliseconds
)
.when(
exp.LT(this=bigint_value, expression=exp.Literal.number(31536000000000000)),
exp.Mul(this=bigint_value, expression=exp.Literal.number(1000)), # microseconds
)
.else_(bigint_value)
) # nanoseconds, use as-is

timestamp_expr = exp.func("MAKE_TIMESTAMP_NS", case_expr)
date_expr = exp.cast(timestamp_expr, exp.DataType.Type.DATE)

return self.sql(date_expr)

def tobinary_sql(self: DuckDB.Generator, expression: exp.ToBinary) -> str:
"""
TO_BINARY and TRY_TO_BINARY transpilation:
Expand Down
4 changes: 4 additions & 0 deletions sqlglot/dialects/snowflake.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ def _builder(args: t.List) -> exp.Func:
formatted_exp.set("safe", safe)
return formatted_exp

if kind == exp.DataType.Type.DATE and int_value:
return exp.UnixToDate(this=value, safe=safe)

return exp.Anonymous(this=name, expressions=args)

return _builder
Expand Down Expand Up @@ -1579,6 +1582,7 @@ class Generator(generator.Generator):
exp.TsOrDsToDate: lambda self, e: self.func(
f"{'TRY_' if e.args.get('safe') else ''}TO_DATE", e.this, self.format_time(e)
),
exp.UnixToDate: rename_func("TO_DATE"),
exp.TsOrDsToTime: lambda self, e: self.func(
f"{'TRY_' if e.args.get('safe') else ''}TO_TIME", e.this, self.format_time(e)
),
Expand Down
4 changes: 4 additions & 0 deletions sqlglot/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8172,6 +8172,10 @@ class UnixToStr(Func):
arg_types = {"this": True, "format": False}


class UnixToDate(Func):
arg_types = {"this": True, "safe": False}


# https://prestodb.io/docs/current/functions/datetime.html
# presto has weird zone/hours/minutes
class UnixToTime(Func):
Expand Down
1 change: 1 addition & 0 deletions sqlglot/typing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
exp.StrToDate,
exp.TimeStrToDate,
exp.TsOrDsToDate,
exp.UnixToDate,
}
},
**{
Expand Down
16 changes: 15 additions & 1 deletion tests/dialects/test_snowflake.py
Original file line number Diff line number Diff line change
Expand Up @@ -2548,7 +2548,7 @@ def test_timestamps(self):
self.validate_identity("DATE_PART(yyy, x)", "DATE_PART(YEAR, x)")
self.validate_identity("DATE_TRUNC(yr, x)", "DATE_TRUNC('YEAR', x)")

self.validate_identity("TO_DATE('12345')").assert_is(exp.Anonymous)
self.validate_identity("TO_DATE('12345')").assert_is(exp.UnixToDate)

self.validate_identity(
"SELECT TO_DATE('2019-02-28') + INTERVAL '1 day, 1 year'",
Expand All @@ -2565,6 +2565,20 @@ def test_timestamps(self):
"snowflake": "TO_DATE(x)",
},
)
self.validate_all(
"TO_DATE('1640995200')",
write={
"snowflake": "TO_DATE('1640995200')",
"duckdb": "CAST(MAKE_TIMESTAMP_NS(CASE WHEN CAST('1640995200' AS BIGINT) < 31536000000 THEN CAST('1640995200' AS BIGINT) * 1000000000 WHEN CAST('1640995200' AS BIGINT) < 31536000000000 THEN CAST('1640995200' AS BIGINT) * 1000000 WHEN CAST('1640995200' AS BIGINT) < 31536000000000000 THEN CAST('1640995200' AS BIGINT) * 1000 ELSE CAST('1640995200' AS BIGINT) END) AS DATE)",
},
)
self.validate_all(
"DATE('1640995200')",
write={
"snowflake": "TO_DATE('1640995200')",
"duckdb": "CAST(MAKE_TIMESTAMP_NS(CASE WHEN CAST('1640995200' AS BIGINT) < 31536000000 THEN CAST('1640995200' AS BIGINT) * 1000000000 WHEN CAST('1640995200' AS BIGINT) < 31536000000000 THEN CAST('1640995200' AS BIGINT) * 1000000 WHEN CAST('1640995200' AS BIGINT) < 31536000000000000 THEN CAST('1640995200' AS BIGINT) * 1000 ELSE CAST('1640995200' AS BIGINT) END) AS DATE)",
},
)
self.validate_all(
"TO_DATE(x, 'MM-DD-YYYY')",
write={
Expand Down