mirror of
				https://github.com/apache/datafusion-sqlparser-rs.git
				synced 2025-10-29 14:27:36 +00:00 
			
		
		
		
	[ClickHouse] Add support for WITH FILL to OrderByExpr (#1330)
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
		
							parent
							
								
									20f7ac59e3
								
							
						
					
					
						commit
						845a1aaddd
					
				
					 9 changed files with 397 additions and 47 deletions
				
			
		|  | @ -43,14 +43,15 @@ pub use self::operator::{BinaryOperator, UnaryOperator}; | |||
| pub use self::query::{ | ||||
|     AfterMatchSkip, ConnectBy, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode, | ||||
|     ExceptSelectItem, ExcludeSelectItem, ExprWithAlias, Fetch, ForClause, ForJson, ForXml, | ||||
|     FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, Join, | ||||
|     JoinConstraint, JoinOperator, JsonTableColumn, JsonTableColumnErrorHandling, LateralView, | ||||
|     LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure, | ||||
|     NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OrderByExpr, | ||||
|     PivotValueSource, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, | ||||
|     ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr, SetOperator, | ||||
|     SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableFactor, TableVersion, | ||||
|     TableWithJoins, Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With, | ||||
|     FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, Interpolate, | ||||
|     InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonTableColumn, | ||||
|     JsonTableColumnErrorHandling, LateralView, LockClause, LockType, MatchRecognizePattern, | ||||
|     MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, | ||||
|     OffsetRows, OrderBy, OrderByExpr, PivotValueSource, Query, RenameSelectItem, | ||||
|     RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, | ||||
|     SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, | ||||
|     TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity, ValueTableMode, | ||||
|     Values, WildcardAdditionalOptions, With, WithFill, | ||||
| }; | ||||
| pub use self::value::{ | ||||
|     escape_double_quote_string, escape_quoted_string, DateTimeField, DollarQuotedString, | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ pub struct Query { | |||
|     /// SELECT or UNION / EXCEPT / INTERSECT
 | ||||
|     pub body: Box<SetExpr>, | ||||
|     /// ORDER BY
 | ||||
|     pub order_by: Vec<OrderByExpr>, | ||||
|     pub order_by: Option<OrderBy>, | ||||
|     /// `LIMIT { <N> | ALL }`
 | ||||
|     pub limit: Option<Expr>, | ||||
| 
 | ||||
|  | @ -67,8 +67,17 @@ impl fmt::Display for Query { | |||
|             write!(f, "{with} ")?; | ||||
|         } | ||||
|         write!(f, "{}", self.body)?; | ||||
|         if !self.order_by.is_empty() { | ||||
|             write!(f, " ORDER BY {}", display_comma_separated(&self.order_by))?; | ||||
|         if let Some(ref order_by) = self.order_by { | ||||
|             write!(f, " ORDER BY")?; | ||||
|             if !order_by.exprs.is_empty() { | ||||
|                 write!(f, " {}", display_comma_separated(&order_by.exprs))?; | ||||
|             } | ||||
|             if let Some(ref interpolate) = order_by.interpolate { | ||||
|                 match &interpolate.exprs { | ||||
|                     Some(exprs) => write!(f, " INTERPOLATE ({})", display_comma_separated(exprs))?, | ||||
|                     None => write!(f, " INTERPOLATE")?, | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if let Some(ref limit) = self.limit { | ||||
|             write!(f, " LIMIT {limit}")?; | ||||
|  | @ -1668,6 +1677,18 @@ pub enum JoinConstraint { | |||
|     None, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||
| pub struct OrderBy { | ||||
|     pub exprs: Vec<OrderByExpr>, | ||||
|     /// Optional: `INTERPOLATE`
 | ||||
|     /// Supported by [ClickHouse syntax]
 | ||||
|     ///
 | ||||
|     /// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
 | ||||
|     pub interpolate: Option<Interpolate>, | ||||
| } | ||||
| 
 | ||||
| /// An `ORDER BY` expression
 | ||||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||
|  | @ -1678,6 +1699,9 @@ pub struct OrderByExpr { | |||
|     pub asc: Option<bool>, | ||||
|     /// Optional `NULLS FIRST` or `NULLS LAST`
 | ||||
|     pub nulls_first: Option<bool>, | ||||
|     /// Optional: `WITH FILL`
 | ||||
|     /// Supported by [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
 | ||||
|     pub with_fill: Option<WithFill>, | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for OrderByExpr { | ||||
|  | @ -1693,6 +1717,67 @@ impl fmt::Display for OrderByExpr { | |||
|             Some(false) => write!(f, " NULLS LAST")?, | ||||
|             None => (), | ||||
|         } | ||||
|         if let Some(ref with_fill) = self.with_fill { | ||||
|             write!(f, " {}", with_fill)? | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// ClickHouse `WITH FILL` modifier for `ORDER BY` clause.
 | ||||
| /// Supported by [ClickHouse syntax]
 | ||||
| ///
 | ||||
| /// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
 | ||||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||
| pub struct WithFill { | ||||
|     pub from: Option<Expr>, | ||||
|     pub to: Option<Expr>, | ||||
|     pub step: Option<Expr>, | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for WithFill { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         write!(f, "WITH FILL")?; | ||||
|         if let Some(ref from) = self.from { | ||||
|             write!(f, " FROM {}", from)?; | ||||
|         } | ||||
|         if let Some(ref to) = self.to { | ||||
|             write!(f, " TO {}", to)?; | ||||
|         } | ||||
|         if let Some(ref step) = self.step { | ||||
|             write!(f, " STEP {}", step)?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// ClickHouse `INTERPOLATE` clause for use in `ORDER BY` clause when using `WITH FILL` modifier.
 | ||||
| /// Supported by [ClickHouse syntax]
 | ||||
| ///
 | ||||
| /// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
 | ||||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||
| pub struct InterpolateExpr { | ||||
|     pub column: Ident, | ||||
|     pub expr: Option<Expr>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||
| pub struct Interpolate { | ||||
|     pub exprs: Option<Vec<InterpolateExpr>>, | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for InterpolateExpr { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         write!(f, "{}", self.column)?; | ||||
|         if let Some(ref expr) = self.expr { | ||||
|             write!(f, " AS {}", expr)?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -297,6 +297,7 @@ define_keywords!( | |||
|     FILE, | ||||
|     FILES, | ||||
|     FILE_FORMAT, | ||||
|     FILL, | ||||
|     FILTER, | ||||
|     FIRST, | ||||
|     FIRST_VALUE, | ||||
|  | @ -382,6 +383,7 @@ define_keywords!( | |||
|     INT64, | ||||
|     INT8, | ||||
|     INTEGER, | ||||
|     INTERPOLATE, | ||||
|     INTERSECT, | ||||
|     INTERSECTION, | ||||
|     INTERVAL, | ||||
|  | @ -682,6 +684,7 @@ define_keywords!( | |||
|     STDDEV_SAMP, | ||||
|     STDIN, | ||||
|     STDOUT, | ||||
|     STEP, | ||||
|     STORAGE_INTEGRATION, | ||||
|     STORED, | ||||
|     STRICT, | ||||
|  |  | |||
|  | @ -7934,7 +7934,7 @@ impl<'a> Parser<'a> { | |||
|                 body: self.parse_insert_setexpr_boxed()?, | ||||
|                 limit: None, | ||||
|                 limit_by: vec![], | ||||
|                 order_by: vec![], | ||||
|                 order_by: None, | ||||
|                 offset: None, | ||||
|                 fetch: None, | ||||
|                 locks: vec![], | ||||
|  | @ -7948,7 +7948,7 @@ impl<'a> Parser<'a> { | |||
|                 body: self.parse_update_setexpr_boxed()?, | ||||
|                 limit: None, | ||||
|                 limit_by: vec![], | ||||
|                 order_by: vec![], | ||||
|                 order_by: None, | ||||
|                 offset: None, | ||||
|                 fetch: None, | ||||
|                 locks: vec![], | ||||
|  | @ -7960,9 +7960,19 @@ impl<'a> Parser<'a> { | |||
|             let body = self.parse_boxed_query_body(0)?; | ||||
| 
 | ||||
|             let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) { | ||||
|                 self.parse_comma_separated(Parser::parse_order_by_expr)? | ||||
|                 let order_by_exprs = self.parse_comma_separated(Parser::parse_order_by_expr)?; | ||||
|                 let interpolate = if dialect_of!(self is ClickHouseDialect | GenericDialect) { | ||||
|                     self.parse_interpolations()? | ||||
|                 } else { | ||||
|                     None | ||||
|                 }; | ||||
| 
 | ||||
|                 Some(OrderBy { | ||||
|                     exprs: order_by_exprs, | ||||
|                     interpolate, | ||||
|                 }) | ||||
|             } else { | ||||
|                 vec![] | ||||
|                 None | ||||
|             }; | ||||
| 
 | ||||
|             let mut limit = None; | ||||
|  | @ -9193,7 +9203,7 @@ impl<'a> Parser<'a> { | |||
|                 subquery: Box::new(Query { | ||||
|                     with: None, | ||||
|                     body: Box::new(values), | ||||
|                     order_by: vec![], | ||||
|                     order_by: None, | ||||
|                     limit: None, | ||||
|                     limit_by: vec![], | ||||
|                     offset: None, | ||||
|  | @ -10519,13 +10529,77 @@ impl<'a> Parser<'a> { | |||
|             None | ||||
|         }; | ||||
| 
 | ||||
|         let with_fill = if dialect_of!(self is ClickHouseDialect | GenericDialect) | ||||
|             && self.parse_keywords(&[Keyword::WITH, Keyword::FILL]) | ||||
|         { | ||||
|             Some(self.parse_with_fill()?) | ||||
|         } else { | ||||
|             None | ||||
|         }; | ||||
| 
 | ||||
|         Ok(OrderByExpr { | ||||
|             expr, | ||||
|             asc, | ||||
|             nulls_first, | ||||
|             with_fill, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     // Parse a WITH FILL clause (ClickHouse dialect)
 | ||||
|     // that follow the WITH FILL keywords in a ORDER BY clause
 | ||||
|     pub fn parse_with_fill(&mut self) -> Result<WithFill, ParserError> { | ||||
|         let from = if self.parse_keyword(Keyword::FROM) { | ||||
|             Some(self.parse_expr()?) | ||||
|         } else { | ||||
|             None | ||||
|         }; | ||||
| 
 | ||||
|         let to = if self.parse_keyword(Keyword::TO) { | ||||
|             Some(self.parse_expr()?) | ||||
|         } else { | ||||
|             None | ||||
|         }; | ||||
| 
 | ||||
|         let step = if self.parse_keyword(Keyword::STEP) { | ||||
|             Some(self.parse_expr()?) | ||||
|         } else { | ||||
|             None | ||||
|         }; | ||||
| 
 | ||||
|         Ok(WithFill { from, to, step }) | ||||
|     } | ||||
| 
 | ||||
|     // Parse a set of comma seperated INTERPOLATE expressions (ClickHouse dialect)
 | ||||
|     // that follow the INTERPOLATE keyword in an ORDER BY clause with the WITH FILL modifier
 | ||||
|     pub fn parse_interpolations(&mut self) -> Result<Option<Interpolate>, ParserError> { | ||||
|         if !self.parse_keyword(Keyword::INTERPOLATE) { | ||||
|             return Ok(None); | ||||
|         } | ||||
| 
 | ||||
|         if self.consume_token(&Token::LParen) { | ||||
|             let interpolations = self.parse_comma_separated0(|p| p.parse_interpolation())?; | ||||
|             self.expect_token(&Token::RParen)?; | ||||
|             // INTERPOLATE () and INTERPOLATE ( ... ) variants
 | ||||
|             return Ok(Some(Interpolate { | ||||
|                 exprs: Some(interpolations), | ||||
|             })); | ||||
|         } | ||||
| 
 | ||||
|         // INTERPOLATE
 | ||||
|         Ok(Some(Interpolate { exprs: None })) | ||||
|     } | ||||
| 
 | ||||
|     // Parse a INTERPOLATE expression (ClickHouse dialect)
 | ||||
|     pub fn parse_interpolation(&mut self) -> Result<InterpolateExpr, ParserError> { | ||||
|         let column = self.parse_identifier(false)?; | ||||
|         let expr = if self.parse_keyword(Keyword::AS) { | ||||
|             Some(self.parse_expr()?) | ||||
|         } else { | ||||
|             None | ||||
|         }; | ||||
|         Ok(InterpolateExpr { column, expr }) | ||||
|     } | ||||
| 
 | ||||
|     /// Parse a TOP clause, MSSQL equivalent of LIMIT,
 | ||||
|     /// that follows after `SELECT [DISTINCT]`.
 | ||||
|     pub fn parse_top(&mut self) -> Result<Top, ParserError> { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Nick Presta
						Nick Presta