Skip to content

Commit

Permalink
Adding support for ClickHouse parameterized view
Browse files Browse the repository at this point in the history
tiny change
  • Loading branch information
Lordworms committed Apr 9, 2024
1 parent a0ed14c commit 2bce6f5
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 2 deletions.
14 changes: 14 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,17 @@ pub enum Expr {
///
/// See <https://docs.snowflake.com/en/sql-reference/constructs/where#joins-in-the-where-clause>.
OuterJoin(Box<Expr>),

/// ClickHouse supported parameterized view
/// for example
/// ```sql
/// CREATE VIEW view AS SELECT * FROM TABLE WHERE Column1={column1:datatype1} and Column2={column2:datatype2}
/// ```
/// see doc <https://clickhouse.com/docs/en/sql-reference/statements/create/view#parameterized-view>
ParameterizedViewArg {
column: Ident,
data_type: DataType,
},
}

impl fmt::Display for CastFormat {
Expand Down Expand Up @@ -1251,6 +1262,9 @@ impl fmt::Display for Expr {
Expr::OuterJoin(expr) => {
write!(f, "{expr} (+)")
}
Expr::ParameterizedViewArg { column, data_type } => {
write!(f, "{{{}:{}}}", column, data_type)
}
}
}
}
Expand Down
34 changes: 32 additions & 2 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -928,14 +928,38 @@ impl<'a> Parser<'a> {

Ok(Statement::ReleaseSavepoint { name })
}

/// Parse clickhouse view parameter style `column1:data_type`
pub fn parse_column_with_data_type(&mut self) -> Result<Expr, ParserError> {
self.expect_token(&Token::LBrace)?;
let name = self.parse_identifier(false)?;
self.expect_token(&Token::Colon)?;
let data_type = self.parse_data_type()?;
self.expect_token(&Token::RBrace)?;
Ok(Expr::ParameterizedViewArg {
column: (name),
data_type: (data_type),
})
}
/// Parse an expression prefix
pub fn parse_prefix(&mut self) -> Result<Expr, ParserError> {
// allow the dialect to override prefix parsing
if let Some(prefix) = self.dialect.parse_prefix(self) {
return prefix;
}

// ClickHouse support using {column:Datatype} format in its creation of parametrised views
match self.peek_token().token {
// check if last token is WHERE and if it is do it, else do the following logic
Token::LBrace if dialect_of!(self is ClickHouseDialect | GenericDialect) => {
self.prev_token();
// prev token is EQ
if self.consume_token(&Token::Eq) {
return self.parse_column_with_data_type();
} else {
self.next_token();
}
}
_ => {}
}
// PostgreSQL allows any string literal to be preceded by a type name, indicating that the
// string literal represents a literal of that type. Some examples:
//
Expand Down Expand Up @@ -2542,6 +2566,12 @@ impl<'a> Parser<'a> {
}
self.parse_map_access(expr)
} else if Token::Colon == tok {
if dialect_of!(self is ClickHouseDialect) && self.index() > 0 {
return Ok(Expr::ParameterizedViewArg {
column: Ident::new(expr.to_string()),
data_type: self.parse_data_type()?,
});
}
Ok(Expr::JsonAccess {
left: Box::new(expr),
operator: JsonOperator::Colon,
Expand Down
5 changes: 5 additions & 0 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,8 @@ fn clickhouse_and_generic() -> TestedDialects {
options: None,
}
}

#[test]
fn parse_create_parametrised_views() {
clickhouse().verified_stmt("CREATE VIEW view AS SELECT * FROM A WHERE Column1 = {column1:datatype1} AND Column2 = {column2:datatype2}");
}
25 changes: 25 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8685,3 +8685,28 @@ fn parse_map_access_expr() {
let _ = dialects.verified_expr(sql);
}
}
fn general_dialect() -> TestedDialects {
TestedDialects {
dialects: vec![Box::new(GenericDialect {})],
options: None,
}
}
#[test]
fn parse_create_parametrised_views() {
// 1. verify this
general_dialect().verified_stmt("CREATE VIEW view AS SELECT * FROM A WHERE Column1 = {column1:datatype1} AND Column2 = {column2:datatype2}");

// 2. verify single column def
general_dialect()
.verified_stmt("CREATE VIEW view AS SELECT * FROM A WHERE Column1 = {column1:datatype1}");

// 3. Error case I : column =
let mut res = general_dialect()
.parse_sql_statements("CREATE VIEW view AS SELECT * FROM A WHERE Column1 = {column1}");
assert!(res.is_err());
// 4. No column def
res = general_dialect().parse_sql_statements(
"CREATE VIEW view AS SELECT * FROM A WHERE Column1 = {column1:} AND Column2 = {column2:}",
);
assert!(res.is_err());
}

0 comments on commit 2bce6f5

Please sign in to comment.