Skip to content

Conversation

@toriwei
Copy link
Collaborator

@toriwei toriwei commented Dec 30, 2025

Improve Snowflake --> DuckDB transpilation of DATE_TRUNC.

Return types / casting

  • Snowflake's return type matches the type of the date/time value (second argument) -- docs
  • DuckDB's return type matches the date/time granularity (first argument) -- docs
Snowflake: SELECT DATE_TRUNC(YEAR, TIMESTAMP '2026-01-01 00:00:00')
2026-01-01 00:00:00
Transpiled DuckDB: SELECT DATE_TRUNC('YEAR', CAST('2026-01-01 00:00:00' AS TIMESTAMP))
2026-01-01


Snowflake: SELECT DATE_TRUNC('HOUR', DATE '2026-01-01')
2026-01-01
Transpiled DuckDB: DATE_TRUNC('HOUR', CAST('2026-01-01' AS DATE))
2026-01-01 00:00:00

Fix:
In the Snowflake parser, modify the AST when the input of DATE_TRUNC(granularity_part, value) is either not both dates or both not times. Then, handle casting to the granularity_part type on DuckDB side.

Snowflake: DATE_TRUNC(YEAR, TIMESTAMP '2026-01-01 00:00:00')
2026-01-01 00:00:00
Transpiled DuckDB: CAST(DATE_TRUNC('YEAR', CAST('2026-01-01 00:00:00' AS TIMESTAMP)) AS TIMESTAMP)
2026-01-01 00:00:00

Snowflake: DATE_TRUNC('HOUR', DATE '2026-01-01')
2026-01-01
Transpiled DuckDB: CAST(DATE_TRUNC('HOUR', CAST('2026-01-01' AS DATE)) AS DATE)
2026-01-01

Support Snowflake's DATE_TRUNC(time_part, time)
Snowflake can truncate time input (HH:MM:SS), but DuckDB cannot.

Snowflake: SELECT DATE_TRUNC(HOUR, TIME '14:23:45.123456') AS time_hour
14:00:00
DuckDB: SELECT DATE_TRUNC('HOUR', CAST('14:23:45.123456' AS TIME)) AS time_hour
Binder Error:
No function matches the given name and argument types 'date_trunc(STRING_LITERAL, TIME)'. You might need to add explicit type casts.
        Candidate functions:
        date_trunc(VARCHAR, DATE) -> TIMESTAMP
        date_trunc(VARCHAR, INTERVAL) -> INTERVAL
        date_trunc(VARCHAR, TIMESTAMP) -> TIMESTAMP

Fix: Add a dummy date to the time expression to create a full date and time. After truncating, cast it to TIME to isolate the time.

DuckDB: SELECT CAST(DATE_TRUNC('HOUR', CAST('1970-01-01' AS DATE) + CAST('14:23:45.123456' AS TIME)) AS TIME);
14:00:00

@github-actions
Copy link
Contributor

github-actions bot commented Dec 30, 2025

SQLGlot Integration Test Results

Comparing:

  • this branch (sqlglot:tori/date-trunc-casting, sqlglot version: tori/date-trunc-casting)
  • baseline (main, sqlglot version: 28.5.1.dev72)

⚠️ Limited to dialects: snowflake, duckdb

By Dialect

dialect main sqlglot:tori/date-trunc-casting difference links
duckdb -> duckdb 4003/4003 passed (100.0%) 4003/4003 passed (100.0%) No change full result / delta
snowflake -> duckdb 578/847 passed (68.2%) 579/847 passed (68.4%) ⬆ improved by 0.1% full result / delta
snowflake -> snowflake 847/847 passed (100.0%) 847/847 passed (100.0%) No change full result / delta

Overall

main: 5697 total, 5428 passed (pass rate: 95.3%), sqlglot version: 28.5.1.dev72

sqlglot:tori/date-trunc-casting: 5697 total, 5429 passed (pass rate: 95.3%), sqlglot version: tori/date-trunc-casting

Difference: No change

@toriwei toriwei force-pushed the tori/date-trunc-casting branch from 8be4fc7 to 3083567 Compare January 5, 2026 17:42
@toriwei toriwei changed the title feat(snowflake)!: preserve part type for DATE_TRUNC(granularity_part, value) feat(snowflake)!: update transpilation of DATE_TRUNC to duckdb Jan 5, 2026
Comment on lines 6511 to 6512
class DateTrunc(Func):
arg_types = {"unit": True, "this": True, "zone": False}
arg_types = {"unit": True, "this": True, "zone": False, "cast_to_granularity_type": False}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we want to set a flag in Snowflake that means "the output type matches the value's type" instead of specifying an "action" like "cast", etc? This feels backwards to me.

Perhaps input_type_preserved more accurately represents this? Ditto for TimestampTrunc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed to input_type_preserved. i agree, this naming is a better representation since we set the flag in Snowflake (snowflake.py#L267)

if not date.type:
from sqlglot.optimizer.annotate_types import annotate_types

date = annotate_types(date, dialect=self.dialect)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's only annotate if the boolean flag is set, otherwise the type attribute is not needed.

@toriwei toriwei force-pushed the tori/date-trunc-casting branch from d22dc92 to 9105f96 Compare January 7, 2026 18:51
Copy link
Collaborator

@georgesittas georgesittas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of final comments, feel free to merge when addressed.

@toriwei toriwei merged commit c9a70c0 into main Jan 7, 2026
9 checks passed
@toriwei toriwei deleted the tori/date-trunc-casting branch January 7, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants