Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ClickHouse] Add support for WITH FILL to OrderByExpr #1330

Merged
merged 13 commits into from
Jul 20, 2024
Merged
16 changes: 8 additions & 8 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
AfterMatchSkip, ConnectBy, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode,
ExceptSelectItem, ExcludeSelectItem, ExprWithAlias, Fetch, ForClause, ForJson, ForXml,
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, SymbolDefinition, Table,
TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity, ValueTableMode,
Values, WildcardAdditionalOptions, With,
GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, Interpolation, 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, 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,
Expand Down
59 changes: 59 additions & 0 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,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 {
Expand All @@ -1642,6 +1645,62 @@ 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.
nickpresta marked this conversation as resolved.
Show resolved Hide resolved
#[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>,
pub interpolate: Vec<Interpolation>,
nickpresta marked this conversation as resolved.
Show resolved Hide resolved
}

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)?;
}
if !self.interpolate.is_empty() {
write!(
f,
" INTERPOLATE ({})",
display_comma_separated(&self.interpolate)
)?;
}
Ok(())
}
}

/// ClickHouse `INTERPOLATE` clause for use in `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 Interpolation {
pub column: Expr,
pub formula: Option<Expr>,
nickpresta marked this conversation as resolved.
Show resolved Hide resolved
}

impl fmt::Display for Interpolation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.column)?;
if let Some(ref formula) = self.formula {
write!(f, " AS {}", formula)?;
}
Ok(())
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ define_keywords!(
FILE,
FILES,
FILE_FORMAT,
FILL,
FILTER,
FIRST,
FIRST_VALUE,
Expand Down Expand Up @@ -382,6 +383,7 @@ define_keywords!(
INT64,
INT8,
INTEGER,
INTERPOLATE,
INTERSECT,
INTERSECTION,
INTERVAL,
Expand Down Expand Up @@ -678,6 +680,7 @@ define_keywords!(
STDDEV_SAMP,
STDIN,
STDOUT,
STEP,
STORAGE_INTEGRATION,
STORED,
STRICT,
Expand Down
62 changes: 62 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10408,13 +10408,75 @@ impl<'a> Parser<'a> {
None
};

let with_fill = if self.parse_keywords(&[Keyword::WITH, Keyword::FILL]) {
Some(self.parse_with_fill()?)
} else {
None
};
nickpresta marked this conversation as resolved.
Show resolved Hide resolved

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
};

let interpolate =
if self.parse_keyword(Keyword::INTERPOLATE) && self.consume_token(&Token::LParen) {
let interpolations = self.parse_interpolations()?;
self.expect_token(&Token::RParen)?;
interpolations
} else {
vec![]
};

Ok(WithFill {
from,
to,
step,
interpolate,
})
}

// Parse a set of comma seperated INTERPOLATE expressions (ClickHouse dialect)
// that follow the INTERPOLATE keyword in a WITH FILL clause
pub fn parse_interpolations(&mut self) -> Result<Vec<Interpolation>, ParserError> {
self.parse_comma_separated(|p| p.parse_interpolation())
}

// Parse a INTERPOLATE expression (ClickHouse dialect)
pub fn parse_interpolation(&mut self) -> Result<Interpolation, ParserError> {
let column = self.parse_expr()?;
let formula = if self.parse_keyword(Keyword::AS) {
Some(self.parse_expr()?)
} else {
None
};
nickpresta marked this conversation as resolved.
Show resolved Hide resolved
Ok(Interpolation { column, formula })
}

/// Parse a TOP clause, MSSQL equivalent of LIMIT,
/// that follows after `SELECT [DISTINCT]`.
pub fn parse_top(&mut self) -> Result<Top, ParserError> {
Expand Down
Loading