diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 5ae4679af..f17e9a00b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -348,6 +348,8 @@ pub enum Expr { ListAgg(ListAgg), /// The `ARRAY_AGG` function `SELECT ARRAY_AGG(... ORDER BY ...)` ArrayAgg(ArrayAgg), + /// The `PERCENTILE_CONT` function `SELECT PERCENTILE_CONT(...) WITHIN GROUP (ORDER BY ...)` + PercentileCont(PercentileCont), /// The `GROUPING SETS` expr. GroupingSets(Vec>), /// The `CUBE` expr. @@ -549,6 +551,7 @@ impl fmt::Display for Expr { Expr::ArraySubquery(s) => write!(f, "ARRAY({})", s), Expr::ListAgg(listagg) => write!(f, "{}", listagg), Expr::ArrayAgg(arrayagg) => write!(f, "{}", arrayagg), + Expr::PercentileCont(percentilecont) => write!(f, "{}", percentilecont), Expr::GroupingSets(sets) => { write!(f, "GROUPING SETS (")?; let mut sep = ""; @@ -2523,6 +2526,25 @@ impl fmt::Display for ArrayAgg { } } +/// A `PERCENTILE_CONT` invocation `PERCENTILE_CONT( ) WITHIN GROUP (ORDER BY )`` +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PercentileCont { + pub expr: Box, + pub within_group: Box, +} + +impl fmt::Display for PercentileCont { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "PERCENTILE_CONT({}) WITHIN GROUP (ORDER BY {})", + self.expr, self.within_group, + )?; + Ok(()) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ObjectType { diff --git a/src/parser.rs b/src/parser.rs index 753c6a11d..209841656 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -464,6 +464,7 @@ impl<'a> Parser<'a> { Keyword::TRIM => self.parse_trim_expr(), Keyword::INTERVAL => self.parse_literal_interval(), Keyword::LISTAGG => self.parse_listagg_expr(), + Keyword::PERCENTILE_CONT => self.parse_percentile_cont_expr(), // Treat ARRAY[1,2,3] as an array [1,2,3], otherwise try as function call Keyword::ARRAY if self.peek_token() == Token::LBracket => { self.expect_token(&Token::LBracket)?; @@ -1056,6 +1057,19 @@ impl<'a> Parser<'a> { })) } + /// Parse a SQL PERCENTILE_CONT expression, e.g. `PERCENTILE_CONT(...) WITHIN GROUP (ORDER BY ...)`. + pub fn parse_percentile_cont_expr(&mut self) -> Result { + self.expect_token(&Token::LParen)?; + let expr = Box::new(self.parse_expr()?); + self.expect_token(&Token::RParen)?; + self.expect_keywords(&[Keyword::WITHIN, Keyword::GROUP])?; + self.expect_token(&Token::LParen)?; + self.expect_keywords(&[Keyword::ORDER, Keyword::BY])?; + let within_group = Box::new(self.parse_order_by_expr()?); + self.expect_token(&Token::RParen)?; + Ok(Expr::PercentileCont(PercentileCont { expr, within_group })) + } + // This function parses date/time fields for both the EXTRACT function-like // operator and interval qualifiers. EXTRACT supports a wider set of // date/time fields than interval qualifiers, so this function may need to diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 96071f1f2..4268ebb5b 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1736,6 +1736,30 @@ fn parse_array_agg_func() { } } +#[test] +fn parse_percentile_cont() { + let sql = "SELECT PERCENTILE_CONT(0.0) WITHIN GROUP (ORDER BY name ASC NULLS FIRST)"; + let select = verified_only_select(sql); + + #[cfg(feature = "bigdecimal")] + let value = bigdecimal::BigDecimal::from(0); + #[cfg(not(feature = "bigdecimal"))] + let value = "0".to_string(); + let expr = Box::new(Expr::Value(Value::Number(value, false))); + let within_group = Box::new(OrderByExpr { + expr: Expr::Identifier(Ident { + value: "name".to_string(), + quote_style: None, + }), + asc: Some(true), + nulls_first: Some(true), + }); + assert_eq!( + &Expr::PercentileCont(PercentileCont { expr, within_group }), + expr_from_projection(only(&select.projection)) + ); +} + #[test] fn parse_create_table() { let sql = "CREATE TABLE uk_cities (\