From 88094e65dd7f45c38fed69744891fa4c8bdbc818 Mon Sep 17 00:00:00 2001 From: Ben Herzberg Date: Thu, 3 Sep 2026 13:31:05 +0300 Subject: [PATCH 1/5] Adding support for APPROXIMATE PERCENTILE_DISC for Redshift --- src/dialect/redshift.rs | 25 +++++++++++++++++++++++++ tests/sqlparser_redshift.rs | 14 ++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs index aa403618fb..7ef7f71e4f 100644 --- a/src/dialect/redshift.rs +++ b/src/dialect/redshift.rs @@ -15,8 +15,11 @@ // specific language governing permissions and limitations // under the License. +use crate::ast::Expr; use crate::dialect::Dialect; use crate::keywords::Keyword; +use crate::parser::{Parser, ParserError}; +use crate::tokenizer::Token; use core::iter::Peekable; use core::str::Chars; @@ -35,6 +38,28 @@ pub struct RedshiftSqlDialect {} // in the Postgres dialect, the query will be parsed as an array, while in the Redshift dialect it will // be a json path impl Dialect for RedshiftSqlDialect { + fn parse_prefix(&self, parser: &mut Parser) -> Option> { + if matches!(&parser.peek_token_ref().token, Token::Word(word) if word.value.eq_ignore_ascii_case("approximate")) + && matches!(&parser.peek_nth_token_ref(1).token, Token::Word(word) if word.value.eq_ignore_ascii_case("percentile_disc")) + { + parser.next_token(); + let function_name = match parser.parse_object_name(false) { + Ok(name) => name, + Err(error) => return Some(Err(error)), + }; + return Some( + parser + .parse_function(function_name) + .map(|function| Expr::Prefixed { + prefix: "APPROXIMATE".into(), + value: Box::new(function), + }), + ); + } + + None + } + /// Determine if a character starts a potential nested quoted identifier. /// Example: RedShift supports the following quote styles to all mean the same thing: /// ```sql diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 31cc4fe583..be4bfdbaea 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -557,3 +557,17 @@ fn parse_unpivot_expression() { fn test_interval_as_column_name() { redshift().verified_stmt("SELECT * FROM table_name WHERE interval = 78"); } + +#[test] +fn parse_approximate_percentile_disc() { + redshift().one_statement_parses_to( + r#"SELECT TOP 10 date.caldate, +COUNT(totalprice), SUM(totalprice), +APPROXIMATE PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY totalprice) +FROM listing +JOIN date ON listing.dateid = date.dateid +GROUP BY date.caldate +ORDER BY 3 DESC"#, + "SELECT TOP 10 date.caldate, COUNT(totalprice), SUM(totalprice), APPROXIMATE PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY totalprice) FROM listing JOIN date ON listing.dateid = date.dateid GROUP BY date.caldate ORDER BY 3 DESC", + ); +} From 369887924db1df11c8e2eedd38f344a061a907fe Mon Sep 17 00:00:00 2001 From: Ben Herzberg Date: Thu, 3 Sep 2026 14:13:23 +0300 Subject: [PATCH 2/5] Fix no_std Redshift build --- src/dialect/redshift.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs index 7ef7f71e4f..3041fb1991 100644 --- a/src/dialect/redshift.rs +++ b/src/dialect/redshift.rs @@ -15,6 +15,9 @@ // specific language governing permissions and limitations // under the License. +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; + use crate::ast::Expr; use crate::dialect::Dialect; use crate::keywords::Keyword; From 47afc93571c08d5c91157725c332d5f6816ce695 Mon Sep 17 00:00:00 2001 From: Ben Herzberg Date: Mon, 7 Sep 2026 17:46:18 +0300 Subject: [PATCH 3/5] Refactored parsing to parser and simplified test --- src/dialect/mod.rs | 5 +++++ src/dialect/redshift.rs | 32 ++++---------------------------- src/parser/mod.rs | 14 ++++++++++++++ tests/sqlparser_redshift.rs | 11 ++--------- 4 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index ff83a4da61..e91dc12647 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -598,6 +598,11 @@ pub trait Dialect: Debug + Any { None } + /// Does the dialect support the `APPROXIMATE PERCENTILE_DISC` function syntax? + fn supports_approximate_percentile_disc(&self) -> bool { + false + } + /// Does the dialect support trailing commas around the query? fn supports_trailing_commas(&self) -> bool { false diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs index 3041fb1991..02b3f6973f 100644 --- a/src/dialect/redshift.rs +++ b/src/dialect/redshift.rs @@ -15,14 +15,8 @@ // specific language governing permissions and limitations // under the License. -#[cfg(not(feature = "std"))] -use alloc::boxed::Box; - -use crate::ast::Expr; use crate::dialect::Dialect; use crate::keywords::Keyword; -use crate::parser::{Parser, ParserError}; -use crate::tokenizer::Token; use core::iter::Peekable; use core::str::Chars; @@ -41,28 +35,6 @@ pub struct RedshiftSqlDialect {} // in the Postgres dialect, the query will be parsed as an array, while in the Redshift dialect it will // be a json path impl Dialect for RedshiftSqlDialect { - fn parse_prefix(&self, parser: &mut Parser) -> Option> { - if matches!(&parser.peek_token_ref().token, Token::Word(word) if word.value.eq_ignore_ascii_case("approximate")) - && matches!(&parser.peek_nth_token_ref(1).token, Token::Word(word) if word.value.eq_ignore_ascii_case("percentile_disc")) - { - parser.next_token(); - let function_name = match parser.parse_object_name(false) { - Ok(name) => name, - Err(error) => return Some(Err(error)), - }; - return Some( - parser - .parse_function(function_name) - .map(|function| Expr::Prefixed { - prefix: "APPROXIMATE".into(), - value: Box::new(function), - }), - ); - } - - None - } - /// Determine if a character starts a potential nested quoted identifier. /// Example: RedShift supports the following quote styles to all mean the same thing: /// ```sql @@ -156,6 +128,10 @@ impl Dialect for RedshiftSqlDialect { true } + fn supports_approximate_percentile_disc(&self) -> bool { + true + } + fn supports_geometric_types(&self) -> bool { true } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 5edc437145..568460d44c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1836,6 +1836,20 @@ impl<'a> Parser<'a> { let dialect = self.dialect; + if dialect.supports_approximate_percentile_disc() + && matches!(&self.peek_token_ref().token, Token::Word(word) if word.value.eq_ignore_ascii_case("approximate")) + && matches!(&self.peek_nth_token_ref(1).token, Token::Word(word) if word.value.eq_ignore_ascii_case("percentile_disc")) + { + self.next_token(); + let function_name = self.parse_object_name(false)?; + return self + .parse_function(function_name) + .map(|function| Expr::Prefixed { + prefix: "APPROXIMATE".into(), + value: Box::new(function), + }); + } + self.advance_token(); let next_token_index = self.get_current_index(); let next_token = self.get_current_token(); diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index be4bfdbaea..be7b0b1b85 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -560,14 +560,7 @@ fn test_interval_as_column_name() { #[test] fn parse_approximate_percentile_disc() { - redshift().one_statement_parses_to( - r#"SELECT TOP 10 date.caldate, -COUNT(totalprice), SUM(totalprice), -APPROXIMATE PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY totalprice) -FROM listing -JOIN date ON listing.dateid = date.dateid -GROUP BY date.caldate -ORDER BY 3 DESC"#, - "SELECT TOP 10 date.caldate, COUNT(totalprice), SUM(totalprice), APPROXIMATE PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY totalprice) FROM listing JOIN date ON listing.dateid = date.dateid GROUP BY date.caldate ORDER BY 3 DESC", + redshift().verified_stmt( + "SELECT APPROXIMATE PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY totalprice)", ); } From fa077fccdb623d3e6031f7b13607825175ec37ef Mon Sep 17 00:00:00 2001 From: Ben Herzberg Date: Wed, 9 Sep 2026 16:41:10 +0300 Subject: [PATCH 4/5] Moving aprx to keywords and other fixes --- src/keywords.rs | 1 + src/parser/mod.rs | 26 ++++++++++++-------------- tests/sqlparser_redshift.rs | 14 +++++++++++++- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/keywords.rs b/src/keywords.rs index 0c50703c3f..7b91eecba0 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -121,6 +121,7 @@ define_keywords!( APPLICATION, APPLY, APPLYBUDGET, + APPROXIMATE, ARCHIVE, ARE, ARRAY, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 568460d44c..2171548f91 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1635,6 +1635,18 @@ impl<'a> Parser<'a> { Keyword::MAP if *self.peek_token_ref() == Token::LBrace && self.dialect.support_map_literal_syntax() => { Ok(Some(self.parse_duckdb_map_literal()?)) } + Keyword::APPROXIMATE + if self.dialect.supports_approximate_percentile_disc() + && self.peek_keyword(Keyword::PERCENTILE_DISC) => + { + self.maybe_parse(|parser| { + let function_name = parser.parse_object_name(false)?; + parser.parse_function(function_name).map(|function| Expr::Prefixed { + prefix: w.to_ident(w_span), + value: Box::new(function), + }) + }) + } Keyword::LAMBDA if self.dialect.supports_lambda_functions() => { Ok(Some(self.parse_lambda_expr()?)) } @@ -1836,20 +1848,6 @@ impl<'a> Parser<'a> { let dialect = self.dialect; - if dialect.supports_approximate_percentile_disc() - && matches!(&self.peek_token_ref().token, Token::Word(word) if word.value.eq_ignore_ascii_case("approximate")) - && matches!(&self.peek_nth_token_ref(1).token, Token::Word(word) if word.value.eq_ignore_ascii_case("percentile_disc")) - { - self.next_token(); - let function_name = self.parse_object_name(false)?; - return self - .parse_function(function_name) - .map(|function| Expr::Prefixed { - prefix: "APPROXIMATE".into(), - value: Box::new(function), - }); - } - self.advance_token(); let next_token_index = self.get_current_index(); let next_token = self.get_current_token(); diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index be7b0b1b85..4c4d9f11b4 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -560,7 +560,19 @@ fn test_interval_as_column_name() { #[test] fn parse_approximate_percentile_disc() { - redshift().verified_stmt( + let dialects = all_dialects_where(|d| d.supports_approximate_percentile_disc()); + dialects.verified_stmt( "SELECT APPROXIMATE PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY totalprice)", ); } + +#[test] +fn parse_approximate_percentile_disc_as_column_alias() { + // Without dialect support, `APPROXIMATE` is just a column name and + // `PERCENTILE_DISC` its implicit alias. + let dialects = all_dialects_where(|d| !d.supports_approximate_percentile_disc()); + dialects.one_statement_parses_to( + "SELECT APPROXIMATE PERCENTILE_DISC FROM t", + "SELECT APPROXIMATE AS PERCENTILE_DISC FROM t", + ); +} From 28e828470e3700f39bc164736e1330a60735dfac Mon Sep 17 00:00:00 2001 From: Ben Herzberg Date: Thu, 10 Sep 2026 11:38:23 +0300 Subject: [PATCH 5/5] Add doc links and merge tests per review --- src/dialect/mod.rs | 1 + src/dialect/redshift.rs | 1 + tests/sqlparser_redshift.rs | 11 ++++------- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index e91dc12647..7c4744c5a7 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -599,6 +599,7 @@ pub trait Dialect: Debug + Any { } /// Does the dialect support the `APPROXIMATE PERCENTILE_DISC` function syntax? + /// See fn supports_approximate_percentile_disc(&self) -> bool { false } diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs index 02b3f6973f..f3dab66ffa 100644 --- a/src/dialect/redshift.rs +++ b/src/dialect/redshift.rs @@ -128,6 +128,7 @@ impl Dialect for RedshiftSqlDialect { true } + /// See fn supports_approximate_percentile_disc(&self) -> bool { true } diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 4c4d9f11b4..9e1c8f8703 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -560,18 +560,15 @@ fn test_interval_as_column_name() { #[test] fn parse_approximate_percentile_disc() { - let dialects = all_dialects_where(|d| d.supports_approximate_percentile_disc()); - dialects.verified_stmt( + let supporting = all_dialects_where(|d| d.supports_approximate_percentile_disc()); + supporting.verified_stmt( "SELECT APPROXIMATE PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY totalprice)", ); -} -#[test] -fn parse_approximate_percentile_disc_as_column_alias() { // Without dialect support, `APPROXIMATE` is just a column name and // `PERCENTILE_DISC` its implicit alias. - let dialects = all_dialects_where(|d| !d.supports_approximate_percentile_disc()); - dialects.one_statement_parses_to( + let non_supporting = all_dialects_where(|d| !d.supports_approximate_percentile_disc()); + non_supporting.one_statement_parses_to( "SELECT APPROXIMATE PERCENTILE_DISC FROM t", "SELECT APPROXIMATE AS PERCENTILE_DISC FROM t", );