Skip to content

Commit

Permalink
Add support of MATERIAILZED/ALIAS/EPHERMERAL default column options f…
Browse files Browse the repository at this point in the history
…or ClickHouse

ClickHouse supports using MATERIAILZED/ALIAS/EPHERMERAL expression to
set the default value while creating table.

For detailed syntax, please refer to: https://clickhouse.com/docs/en/sql-reference/statements/create/table#default_values
  • Loading branch information
git-hulk committed Jul 23, 2024
1 parent 48ea564 commit f8ca47c
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 0 deletions.
21 changes: 21 additions & 0 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,18 @@ pub enum ColumnOption {
NotNull,
/// `DEFAULT <restricted-expr>`
Default(Expr),

/// ClickHouse supports `MATERIALIZE`, `EPHEMERAL` and `ALIAS` expr to generate default values.
/// Syntax: `b INT MATERIALIZE (a + 1)`
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/create/table#default_values)
/// `MATERIALIZE <expr>`
Materialized(Expr),
/// `EPHEMERAL [<expr>]`
Ephemeral(Option<Expr>),
/// `ALIAS <expr>`
Alias(Expr),

/// `{ PRIMARY KEY | UNIQUE } [<constraint_characteristics>]`
Unique {
is_primary: bool,
Expand Down Expand Up @@ -978,6 +990,15 @@ impl fmt::Display for ColumnOption {
Null => write!(f, "NULL"),
NotNull => write!(f, "NOT NULL"),
Default(expr) => write!(f, "DEFAULT {expr}"),
Materialized(expr) => write!(f, "MATERIALIZED {expr}"),
Ephemeral(expr) => {
if let Some(e) = expr {
write!(f, "EPHEMERAL {e}")
} else {
write!(f, "EPHEMERAL")
}
}
Alias(expr) => write!(f, "ALIAS {expr}"),
Unique {
is_primary,
characteristics,
Expand Down
2 changes: 2 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ define_keywords!(
AFTER,
AGAINST,
AGGREGATION,
ALIAS,
ALL,
ALLOCATE,
ALTER,
Expand Down Expand Up @@ -267,6 +268,7 @@ define_keywords!(
ENFORCED,
ENGINE,
ENUM,
EPHEMERAL,
EPOCH,
EQUALS,
ERROR,
Expand Down
18 changes: 18 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5743,6 +5743,24 @@ impl<'a> Parser<'a> {
Ok(Some(ColumnOption::Null))
} else if self.parse_keyword(Keyword::DEFAULT) {
Ok(Some(ColumnOption::Default(self.parse_expr()?)))
} else if dialect_of!(self is ClickHouseDialect| GenericDialect)
&& self.parse_keyword(Keyword::MATERIALIZED)
{
Ok(Some(ColumnOption::Materialized(self.parse_expr()?)))
} else if dialect_of!(self is ClickHouseDialect| GenericDialect)
&& self.parse_keyword(Keyword::ALIAS)
{
Ok(Some(ColumnOption::Alias(self.parse_expr()?)))
} else if dialect_of!(self is ClickHouseDialect| GenericDialect)
&& self.parse_keyword(Keyword::EPHEMERAL)
{
// The expression is optional for the EPHEMERAL syntax, so we need to check
// if the column definition has remaining tokens before parsing the expression.
if matches!(self.peek_token().token, Token::Comma | Token::RParen) {
Ok(Some(ColumnOption::Ephemeral(None)))
} else {
Ok(Some(ColumnOption::Ephemeral(Some(self.parse_expr()?))))
}
} else if self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
let characteristics = self.parse_constraint_characteristics()?;
Ok(Some(ColumnOption::Unique {
Expand Down
96 changes: 96 additions & 0 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,102 @@ fn parse_create_table_with_primary_key() {
.expect_err("ORDER BY supports one expression with tuple");
}

#[test]
fn parse_create_table_with_variant_default_expressions() {
let sql = concat!(
"CREATE TABLE table (",
"a DATETIME MATERIALIZED now(),",
" b DATETIME EPHEMERAL now(),",
" c DATETIME EPHEMERAL,",
" d STRING ALIAS toString(c)",
") ENGINE=MergeTree"
);
match clickhouse_and_generic().verified_stmt(sql) {
Statement::CreateTable(CreateTable { columns, .. }) => {
assert_eq!(
columns,
vec![
ColumnDef {
name: Ident::new("a"),
data_type: DataType::Datetime(None),
collation: None,
options: vec![ColumnOptionDef {
name: None,
option: ColumnOption::Materialized(Expr::Function(Function {
name: ObjectName(vec![Ident::new("now")]),
args: FunctionArguments::List(FunctionArgumentList {
args: vec![],
duplicate_treatment: None,
clauses: vec![],
}),
parameters: FunctionArguments::None,
null_treatment: None,
filter: None,
over: None,
within_group: vec![],
}))
}],
},
ColumnDef {
name: Ident::new("b"),
data_type: DataType::Datetime(None),
collation: None,
options: vec![ColumnOptionDef {
name: None,
option: ColumnOption::Ephemeral(Some(Expr::Function(Function {
name: ObjectName(vec![Ident::new("now")]),
args: FunctionArguments::List(FunctionArgumentList {
args: vec![],
duplicate_treatment: None,
clauses: vec![],
}),
parameters: FunctionArguments::None,
null_treatment: None,
filter: None,
over: None,
within_group: vec![],
})))
}],
},
ColumnDef {
name: Ident::new("c"),
data_type: DataType::Datetime(None),
collation: None,
options: vec![ColumnOptionDef {
name: None,
option: ColumnOption::Ephemeral(None)
}],
},
ColumnDef {
name: Ident::new("d"),
data_type: DataType::String(None),
collation: None,
options: vec![ColumnOptionDef {
name: None,
option: ColumnOption::Alias(Expr::Function(Function {
name: ObjectName(vec![Ident::new("toString")]),
args: FunctionArguments::List(FunctionArgumentList {
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
Identifier(Ident::new("c"))
))],
duplicate_treatment: None,
clauses: vec![],
}),
parameters: FunctionArguments::None,
null_treatment: None,
filter: None,
over: None,
within_group: vec![],
}))
}],
}
]
)
}
_ => unreachable!(),
}
}

#[test]
fn parse_create_view_with_fields_data_types() {
match clickhouse().verified_stmt(r#"CREATE VIEW v (i "int", f "String") AS SELECT * FROM t"#) {
Expand Down

0 comments on commit f8ca47c

Please sign in to comment.