Skip to content

feat(Snowflake)!: set default window frame for certain Snowflake ranking functions during transpilation#7195

Merged
geooo109 merged 3 commits intomainfrom
snowflake_window_function_default_frame_transpilation
Mar 4, 2026
Merged

feat(Snowflake)!: set default window frame for certain Snowflake ranking functions during transpilation#7195
geooo109 merged 3 commits intomainfrom
snowflake_window_function_default_frame_transpilation

Conversation

@fivetran-felixhuang
Copy link
Collaborator

@fivetran-felixhuang fivetran-felixhuang commented Mar 3, 2026

The default window frame in Snowflake is ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
https://docs.snowflake.com/en/sql-reference/functions-window-syntax#usage-notes-for-window-frames)

This is not the same as the default window frame in DuckDB (BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) https://duckdb.org/docs/stable/sql/functions/window_functions#framing

This can cause issue during transpilation.

We want to add the default Snowflake window frame to AST when none specified so we can use it to generate the transpiled query with the correct window frame. And when generating Snowflake query from AST, we can omit the generation of window frame clause if it matches the default window frame (omit ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING since it’s default)

In Snowflake, these window ranking functions allow and respect non-default window frames: FIRST_VALUE, LAST_VALUE, NTH_VALUE, DENSE_RANK, RANK. (Other window rankings like LEAD and LAG don't allow window frame other than the default one). For this PR, we will just work on FIRST_VALUE, LAST_VALUE and NTH_VALUE, since there is some further complication with DENSE_RANK and RANK (Snowflake respects the window frame of DENSE_RANK and RANK, but DuckDB ignores the frame, so in this sense these functions are only partially transpilable from Snowflake to Duckdb. We can handle this in a follow-up ticket if we need to support transpilation of these two)

We are introducing exp_supports_null_ordering and WINDOW_FUNCTIONS_WITH_NULL_ORDERING for BigQuery as the current implementation assumes BigQuery's window function with custom frame doesn't support NULL ordering, (NULLS FIRST/LAST) which is not a correct assumption. For example, the following is valid in BigQuery

FIRST_VALUE(val) OVER(
ORDER BY val ASC NULLS LAST
ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING
)

We have a follow-up ticket to investigate NULLS ordering further. For now exp_supports_null_ordering and WINDOW_FUNCTIONS_WITH_NULL_ORDERING are needed to avoid breaking logic around NULL ordering in ordered_sql in generator.py

/integration-tests

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

SQLGlot Integration Test Results

Comparing:

  • this branch (sqlglot:snowflake_window_function_default_frame_transpilation, sqlglot version: snowflake_window_function_default_frame_transpilation)
  • baseline (main, sqlglot version: 29.0.2.dev29)

By Dialect

dialect main sqlglot:snowflake_window_function_default_frame_transpilation transitions links
bigquery -> bigquery 2592/2624 passed (98.8%) 2592/2624 passed (98.8%) No change full result / delta
bigquery -> duckdb 2077/2623 passed (79.2%) 2078/2623 passed (79.2%) 1 fail -> pass full result / delta
duckdb -> duckdb 4003/4003 passed (100.0%) 4003/4003 passed (100.0%) No change full result / delta
snowflake -> duckdb 1617/2642 passed (61.2%) 1636/2642 passed (61.9%) 19 fail -> pass full result / delta
snowflake -> snowflake 2863/2863 passed (100.0%) 2863/2863 passed (100.0%) No change full result / delta

Overall

main: 14755 total, 13152 passed (pass rate: 89.1%), sqlglot version: 29.0.2.dev29

sqlglot:snowflake_window_function_default_frame_transpilation: 14755 total, 13172 passed (pass rate: 89.3%), sqlglot version: snowflake_window_function_default_frame_transpilation

Transitions:
20 fail -> pass

Copy link
Collaborator

@geooo109 geooo109 left a comment

Choose a reason for hiding this comment

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

Let's leave the logic of WINDOW_FUNCTIONS_WITH_NULL_ORDERING for a separate PR, and just focus on the snowflake -> duckdb.

@fivetran-felixhuang The logic we are using needs fixes (take a look at the regression tests).

  1. From the Snowflake Window frames , we can't arbitrarily remove the default window frame next to an ORDER BY.
Valid SF
REGR_AVGX(a, b) OVER (PARTITION BY partition_id ORDER BY row_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 
=> 
Invalid SF
REGR_AVGX(a, b) OVER (PARTITION BY partition_id ORDER BY row_id
  1. From the Snowflake Window frames non-ranking funcs the default of COUNT etc is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, so by removing the ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING we will create regressions between the results of sf -> sf and sf -> duckdb.

Let's make sure that we cover these cases ^ correctly and no regressions are presented.

@fivetran-felixhuang
Copy link
Collaborator Author

Let's leave the logic of WINDOW_FUNCTIONS_WITH_NULL_ORDERING for a separate PR, and just focus on the snowflake -> duckdb.

@fivetran-felixhuang The logic we are using needs fixes (take a look at the regression tests).

  1. From the Snowflake Window frames , we can't arbitrarily remove the default window frame next to an ORDER BY.
Valid SF
REGR_AVGX(a, b) OVER (PARTITION BY partition_id ORDER BY row_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 
=> 
Invalid SF
REGR_AVGX(a, b) OVER (PARTITION BY partition_id ORDER BY row_id
  1. From the Snowflake Window frames non-ranking funcs the default of COUNT etc is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, so by removing the ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING we will create regressions between the results of sf -> sf and sf -> duckdb.

Let's make sure that we cover these cases ^ correctly and no regressions are presented.

the logic is now limited to NTH_VALUE, FIRST_VALUE and LAST_VALUE

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.

Besides Giorgos' comments this LGTM.

@geooo109 geooo109 merged commit e0947ad into main Mar 4, 2026
9 checks passed
@geooo109 geooo109 deleted the snowflake_window_function_default_frame_transpilation branch March 4, 2026 18:39
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