From 630882fdbaa9fd9432e87f4c7d842a18dab2fbcc Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 31 Oct 2026 13:05:18 +0100 Subject: [PATCH 1/3] Core: Keep untyped string window frame offsets untyped --- src/parser/mod.rs | 5 +---- tests/sqlparser_common.rs | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 11accc3996..68ae456241 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2794,10 +2794,7 @@ impl<'a> Parser<'a> { let rows = if self.parse_keyword(Keyword::UNBOUNDED) { None } else { - Some(Box::new(match &self.peek_token_ref().token { - Token::SingleQuotedString(_) => self.parse_interval()?, - _ => self.parse_expr()?, - })) + Some(Box::new(self.parse_expr()?)) }; if self.parse_keyword(Keyword::PRECEDING) { Ok(WindowFrameBound::Preceding(rows)) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 7627f39185..4b4778356d 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -23323,3 +23323,43 @@ fn display_refresh_materialized_view_propagates_write_errors() { assert_eq!(write!(writer, "{statement}"), Err(fmt::Error), "{rejected}"); } } + +#[test] +fn parse_window_frame_untyped_string_offset() { + let dialects = all_dialects_except(|d| d.require_interval_qualifier()); + dialects.verified_stmt( + "SELECT sum(x) OVER (ORDER BY f RANGE BETWEEN 'inf' PRECEDING AND '1 day' FOLLOWING) FROM t", + ); + dialects.verified_stmt( + "SELECT sum(x) OVER (ORDER BY f ROWS BETWEEN 'hello' PRECEDING AND 10 FOLLOWING) FROM t", + ); + + let select = dialects.verified_only_select( + "SELECT sum(x) OVER (ORDER BY f RANGE BETWEEN INTERVAL '1' DAY PRECEDING AND CURRENT ROW) FROM t", + ); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { over, .. }) => match over.as_ref() { + Some(WindowType::WindowSpec(WindowSpec { + window_frame: Some(frame), + .. + })) => assert_eq!( + frame, + &WindowFrame { + units: WindowFrameUnits::Range, + start_bound: WindowFrameBound::Preceding(Some(Box::new(Expr::Interval( + Interval { + value: Box::new(Expr::value(Value::SingleQuotedString("1".to_owned()))), + leading_field: Some(DateTimeField::Day), + leading_precision: None, + last_field: None, + fractional_seconds_precision: None, + }, + )))), + end_bound: Some(WindowFrameBound::CurrentRow), + } + ), + _ => panic!("expected a window specification"), + }, + _ => panic!("expected a window function"), + } +} From a5ee4fe7f2015f8c34aeb1fe7cae147ee7a66126 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 31 Oct 2026 15:14:35 +0100 Subject: [PATCH 2/3] Add fuzz seed for untyped window frame offsets --- fuzz/fuzz_seeds/postgres_2781_window_frame_untyped_offset.sql | 1 + 1 file changed, 1 insertion(+) create mode 100644 fuzz/fuzz_seeds/postgres_2781_window_frame_untyped_offset.sql diff --git a/fuzz/fuzz_seeds/postgres_2781_window_frame_untyped_offset.sql b/fuzz/fuzz_seeds/postgres_2781_window_frame_untyped_offset.sql new file mode 100644 index 0000000000..a9e9c7c32c --- /dev/null +++ b/fuzz/fuzz_seeds/postgres_2781_window_frame_untyped_offset.sql @@ -0,0 +1 @@ +SELECT sum(x) OVER (ORDER BY f RANGE BETWEEN 'inf' PRECEDING AND '1 day' FOLLOWING) FROM t; From 52d27f6db5126c953ee8b6548831b8345de762e3 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 31 Oct 2026 16:16:26 +0100 Subject: [PATCH 3/3] Cover the frame offset parse error --- tests/sqlparser_common.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 4b4778356d..97bf589da2 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -23333,6 +23333,14 @@ fn parse_window_frame_untyped_string_offset() { dialects.verified_stmt( "SELECT sum(x) OVER (ORDER BY f ROWS BETWEEN 'hello' PRECEDING AND 10 FOLLOWING) FROM t", ); + assert_eq!( + dialects + .parse_sql_statements( + "SELECT sum(x) OVER (ORDER BY f ROWS BETWEEN ) PRECEDING AND CURRENT ROW) FROM t" + ) + .unwrap_err(), + ParserError::ParserError("Expected: an expression, found: )".to_string()) + ); let select = dialects.verified_only_select( "SELECT sum(x) OVER (ORDER BY f RANGE BETWEEN INTERVAL '1' DAY PRECEDING AND CURRENT ROW) FROM t",