Skip to content

Commit 7022e42

Browse files
committed
Refactor INTERPOLATE and add tests
1 parent 815ee5b commit 7022e42

File tree

6 files changed

+198
-128
lines changed

6 files changed

+198
-128
lines changed

src/ast/mod.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ pub use self::operator::{BinaryOperator, UnaryOperator};
4343
pub use self::query::{
4444
AfterMatchSkip, ConnectBy, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode,
4545
ExceptSelectItem, ExcludeSelectItem, ExprWithAlias, Fetch, ForClause, ForJson, ForXml,
46-
GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, Interpolation, Join,
47-
JoinConstraint, JoinOperator, JsonTableColumn, JsonTableColumnErrorHandling, LateralView,
48-
LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure,
49-
NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OrderByExpr,
50-
PivotValueSource, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement,
51-
ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr, SetOperator,
52-
SetQuantifier, SymbolDefinition, Table, TableAlias, TableFactor, TableVersion, TableWithJoins,
53-
Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
46+
GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, Interpolation,
47+
InterpolationArg, Join, JoinConstraint, JoinOperator, JsonTableColumn,
48+
JsonTableColumnErrorHandling, LateralView, LockClause, LockType, MatchRecognizePattern,
49+
MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset,
50+
OffsetRows, OrderByExpr, PivotValueSource, Query, RenameSelectItem, RepetitionQuantifier,
51+
ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr,
52+
SetOperator, SetQuantifier, SymbolDefinition, Table, TableAlias, TableFactor, TableVersion,
53+
TableWithJoins, Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With,
54+
WithFill,
5455
};
5556
pub use self::value::{
5657
escape_double_quote_string, escape_quoted_string, DateTimeField, DollarQuotedString,

src/ast/query.rs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,11 @@ pub struct OrderByExpr {
16301630
/// Optional: `WITH FILL`
16311631
/// Supported by [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
16321632
pub with_fill: Option<WithFill>,
1633+
/// Optional: `INTERPOLATE`
1634+
/// Supported by [ClickHouse syntax]
1635+
///
1636+
/// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
1637+
pub interpolate: Option<InterpolationArg>,
16331638
}
16341639

16351640
impl fmt::Display for OrderByExpr {
@@ -1648,19 +1653,30 @@ impl fmt::Display for OrderByExpr {
16481653
if let Some(ref with_fill) = self.with_fill {
16491654
write!(f, " {}", with_fill)?
16501655
}
1656+
if let Some(ref interpolate) = self.interpolate {
1657+
match interpolate {
1658+
InterpolationArg::NoBody => write!(f, " INTERPOLATE")?,
1659+
InterpolationArg::EmptyBody => write!(f, " INTERPOLATE ()")?,
1660+
InterpolationArg::Columns(columns) => {
1661+
write!(f, " INTERPOLATE ({})", display_comma_separated(columns))?;
1662+
}
1663+
}
1664+
}
16511665
Ok(())
16521666
}
16531667
}
16541668

16551669
/// ClickHouse `WITH FILL` modifier for `ORDER BY` clause.
1670+
/// Supported by [ClickHouse syntax]
1671+
///
1672+
/// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
16561673
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
16571674
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16581675
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
16591676
pub struct WithFill {
16601677
pub from: Option<Expr>,
16611678
pub to: Option<Expr>,
16621679
pub step: Option<Expr>,
1663-
pub interpolate: Vec<Interpolation>,
16641680
}
16651681

16661682
impl fmt::Display for WithFill {
@@ -1675,18 +1691,14 @@ impl fmt::Display for WithFill {
16751691
if let Some(ref step) = self.step {
16761692
write!(f, " STEP {}", step)?;
16771693
}
1678-
if !self.interpolate.is_empty() {
1679-
write!(
1680-
f,
1681-
" INTERPOLATE ({})",
1682-
display_comma_separated(&self.interpolate)
1683-
)?;
1684-
}
16851694
Ok(())
16861695
}
16871696
}
16881697

1689-
/// ClickHouse `INTERPOLATE` clause for use in `WITH FILL` modifier.
1698+
/// ClickHouse `INTERPOLATE` clause for use in `ORDER BY` clause when using `WITH FILL` modifier.
1699+
/// Supported by [ClickHouse syntax]
1700+
///
1701+
/// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
16901702
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
16911703
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16921704
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -1695,6 +1707,15 @@ pub struct Interpolation {
16951707
pub formula: Option<Expr>,
16961708
}
16971709

1710+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1711+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1712+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1713+
pub enum InterpolationArg {
1714+
NoBody,
1715+
EmptyBody,
1716+
Columns(Vec<Interpolation>),
1717+
}
1718+
16981719
impl fmt::Display for Interpolation {
16991720
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17001721
write!(f, "{}", self.column)?;

src/parser/mod.rs

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10408,17 +10408,42 @@ impl<'a> Parser<'a> {
1040810408
None
1040910409
};
1041010410

10411-
let with_fill = if self.parse_keywords(&[Keyword::WITH, Keyword::FILL]) {
10411+
let with_fill = if dialect_of!(self is ClickHouseDialect | GenericDialect)
10412+
&& self.parse_keywords(&[Keyword::WITH, Keyword::FILL])
10413+
{
1041210414
Some(self.parse_with_fill()?)
1041310415
} else {
1041410416
None
1041510417
};
1041610418

10419+
let interpolate = if dialect_of!(self is ClickHouseDialect | GenericDialect)
10420+
&& self.parse_keyword(Keyword::INTERPOLATE)
10421+
{
10422+
if self.consume_token(&Token::LParen) {
10423+
if self.peek_token().token == Token::RParen {
10424+
// INTERPOLATE ()
10425+
self.next_token();
10426+
Some(InterpolationArg::EmptyBody)
10427+
} else {
10428+
// INTERPOLATE ( ... )
10429+
let interpolations = self.parse_interpolations()?;
10430+
self.expect_token(&Token::RParen)?;
10431+
Some(InterpolationArg::Columns(interpolations))
10432+
}
10433+
} else {
10434+
// INTERPOLATE
10435+
Some(InterpolationArg::NoBody)
10436+
}
10437+
} else {
10438+
None
10439+
};
10440+
1041710441
Ok(OrderByExpr {
1041810442
expr,
1041910443
asc,
1042010444
nulls_first,
1042110445
with_fill,
10446+
interpolate,
1042210447
})
1042310448
}
1042410449

@@ -10443,25 +10468,11 @@ impl<'a> Parser<'a> {
1044310468
None
1044410469
};
1044510470

10446-
let interpolate =
10447-
if self.parse_keyword(Keyword::INTERPOLATE) && self.consume_token(&Token::LParen) {
10448-
let interpolations = self.parse_interpolations()?;
10449-
self.expect_token(&Token::RParen)?;
10450-
interpolations
10451-
} else {
10452-
vec![]
10453-
};
10454-
10455-
Ok(WithFill {
10456-
from,
10457-
to,
10458-
step,
10459-
interpolate,
10460-
})
10471+
Ok(WithFill { from, to, step })
1046110472
}
1046210473

1046310474
// Parse a set of comma seperated INTERPOLATE expressions (ClickHouse dialect)
10464-
// that follow the INTERPOLATE keyword in a WITH FILL clause
10475+
// that follow the INTERPOLATE keyword in an ORDER BY clause with the WITH FILL modifier
1046510476
pub fn parse_interpolations(&mut self) -> Result<Vec<Interpolation>, ParserError> {
1046610477
self.parse_comma_separated(|p| p.parse_interpolation())
1046710478
}

tests/sqlparser_clickhouse.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,119 @@ fn parse_group_by_with_modifier() {
681681
}
682682
}
683683

684+
#[test]
685+
fn parse_select_order_by_with_fill_interpolate() {
686+
let sql = "SELECT id, fname, lname FROM customer WHERE id < 5 \
687+
ORDER BY \
688+
fname ASC NULLS FIRST WITH FILL FROM 10 TO 20 STEP 2, \
689+
lname DESC NULLS LAST WITH FILL FROM 30 TO 40 STEP 3 \
690+
INTERPOLATE (col1 AS col1 + 1) \
691+
LIMIT 2";
692+
let select = clickhouse().verified_query(sql);
693+
assert_eq!(
694+
vec![
695+
OrderByExpr {
696+
expr: Expr::Identifier(Ident::new("fname")),
697+
asc: Some(true),
698+
nulls_first: Some(true),
699+
with_fill: Some(WithFill {
700+
from: Some(Expr::Value(number("10"))),
701+
to: Some(Expr::Value(number("20"))),
702+
step: Some(Expr::Value(number("2"))),
703+
}),
704+
interpolate: None,
705+
},
706+
OrderByExpr {
707+
expr: Expr::Identifier(Ident::new("lname")),
708+
asc: Some(false),
709+
nulls_first: Some(false),
710+
with_fill: Some(WithFill {
711+
from: Some(Expr::Value(number("30"))),
712+
to: Some(Expr::Value(number("40"))),
713+
step: Some(Expr::Value(number("3"))),
714+
}),
715+
interpolate: Some(InterpolationArg::Columns(vec![Interpolation {
716+
column: Expr::Identifier(Ident::new("col1")),
717+
formula: Some(Expr::BinaryOp {
718+
left: Box::new(Expr::Identifier(Ident::new("col1"))),
719+
op: BinaryOperator::Plus,
720+
right: Box::new(Expr::Value(number("1"))),
721+
}),
722+
}]))
723+
},
724+
],
725+
select.order_by
726+
);
727+
assert_eq!(Some(Expr::Value(number("2"))), select.limit);
728+
}
729+
730+
#[test]
731+
fn parse_with_fill() {
732+
let sql = "SELECT fname FROM customer \
733+
ORDER BY fname WITH FILL FROM 10 TO 20 STEP 2";
734+
let select = clickhouse().verified_query(sql);
735+
assert_eq!(
736+
Some(WithFill {
737+
from: Some(Expr::Value(number("10"))),
738+
to: Some(Expr::Value(number("20"))),
739+
step: Some(Expr::Value(number("2"))),
740+
}),
741+
select.order_by[0].with_fill
742+
);
743+
}
744+
745+
#[test]
746+
fn parse_interpolation_body_with_columns() {
747+
let sql = "SELECT fname FROM customer ORDER BY fname WITH FILL \
748+
INTERPOLATE (col1 AS col1 + 1, col2 AS col3, col4 AS col4 + 4)";
749+
let select = clickhouse().verified_query(sql);
750+
assert_eq!(
751+
Some(InterpolationArg::Columns(vec![
752+
Interpolation {
753+
column: Expr::Identifier(Ident::new("col1")),
754+
formula: Some(Expr::BinaryOp {
755+
left: Box::new(Expr::Identifier(Ident::new("col1"))),
756+
op: BinaryOperator::Plus,
757+
right: Box::new(Expr::Value(number("1"))),
758+
}),
759+
},
760+
Interpolation {
761+
column: Expr::Identifier(Ident::new("col2")),
762+
formula: Some(Expr::Identifier(Ident::new("col3"))),
763+
},
764+
Interpolation {
765+
column: Expr::Identifier(Ident::new("col4")),
766+
formula: Some(Expr::BinaryOp {
767+
left: Box::new(Expr::Identifier(Ident::new("col4"))),
768+
op: BinaryOperator::Plus,
769+
right: Box::new(Expr::Value(number("4"))),
770+
}),
771+
},
772+
])),
773+
select.order_by[0].interpolate
774+
);
775+
}
776+
777+
#[test]
778+
fn parse_interpolation_without_body() {
779+
let sql = "SELECT fname FROM customer ORDER BY fname WITH FILL INTERPOLATE";
780+
let select = clickhouse().verified_query(sql);
781+
assert_eq!(
782+
Some(InterpolationArg::NoBody),
783+
select.order_by[0].interpolate
784+
);
785+
}
786+
787+
#[test]
788+
fn parse_interpolation_with_empty_body() {
789+
let sql = "SELECT fname FROM customer ORDER BY fname WITH FILL INTERPOLATE ()";
790+
let select = clickhouse().verified_query(sql);
791+
assert_eq!(
792+
Some(InterpolationArg::EmptyBody),
793+
select.order_by[0].interpolate
794+
);
795+
}
796+
684797
fn clickhouse() -> TestedDialects {
685798
TestedDialects {
686799
dialects: vec![Box::new(ClickHouseDialect {})],

0 commit comments

Comments
 (0)