Skip to content

Commit eb983e5

Browse files
committed
Add support of MATERIAILZED/ALIAS/EPHERMERAL default column options for 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
1 parent 48ea564 commit eb983e5

File tree

4 files changed

+137
-0
lines changed

4 files changed

+137
-0
lines changed

src/ast/ddl.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,18 @@ pub enum ColumnOption {
923923
NotNull,
924924
/// `DEFAULT <restricted-expr>`
925925
Default(Expr),
926+
927+
/// ClickHouse supports `MATERIALIZE`, `EPHEMERAL` and `ALIAS` expr to generate default values.
928+
/// Syntax: `b INT MATERIALIZE (a + 1)`
929+
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/create/table#default_values)
930+
931+
/// MATERIALIZE <expr>
932+
Materialized(Expr),
933+
/// EPHEMERAL [<expr>]
934+
Ephemeral(Option<Expr>),
935+
/// ALIAS <expr>
936+
Alias(Expr),
937+
926938
/// `{ PRIMARY KEY | UNIQUE } [<constraint_characteristics>]`
927939
Unique {
928940
is_primary: bool,
@@ -978,6 +990,15 @@ impl fmt::Display for ColumnOption {
978990
Null => write!(f, "NULL"),
979991
NotNull => write!(f, "NOT NULL"),
980992
Default(expr) => write!(f, "DEFAULT {expr}"),
993+
Materialized(expr) => write!(f, "MATERIALIZED {expr}"),
994+
Ephemeral(expr) => {
995+
if let Some(e) = expr {
996+
write!(f, "EPHEMERAL {e}")
997+
} else {
998+
write!(f, "EPHEMERAL")
999+
}
1000+
}
1001+
Alias(expr) => write!(f, "ALIAS {expr}"),
9811002
Unique {
9821003
is_primary,
9831004
characteristics,

src/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ define_keywords!(
7777
AFTER,
7878
AGAINST,
7979
AGGREGATION,
80+
ALIAS,
8081
ALL,
8182
ALLOCATE,
8283
ALTER,
@@ -267,6 +268,7 @@ define_keywords!(
267268
ENFORCED,
268269
ENGINE,
269270
ENUM,
271+
EPHEMERAL,
270272
EPOCH,
271273
EQUALS,
272274
ERROR,

src/parser/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5743,6 +5743,24 @@ impl<'a> Parser<'a> {
57435743
Ok(Some(ColumnOption::Null))
57445744
} else if self.parse_keyword(Keyword::DEFAULT) {
57455745
Ok(Some(ColumnOption::Default(self.parse_expr()?)))
5746+
} else if dialect_of!(self is ClickHouseDialect| GenericDialect)
5747+
&& self.parse_keyword(Keyword::MATERIALIZED)
5748+
{
5749+
Ok(Some(ColumnOption::Materialized(self.parse_expr()?)))
5750+
} else if dialect_of!(self is ClickHouseDialect| GenericDialect)
5751+
&& self.parse_keyword(Keyword::ALIAS)
5752+
{
5753+
Ok(Some(ColumnOption::Alias(self.parse_expr()?)))
5754+
} else if dialect_of!(self is ClickHouseDialect| GenericDialect)
5755+
&& self.parse_keyword(Keyword::EPHEMERAL)
5756+
{
5757+
// The expression is optional for the EPHEMERAL syntax, so we need to check
5758+
// if the column definition has remaining tokens before parsing the expression.
5759+
if matches!(self.peek_token().token, Token::Comma | Token::RParen) {
5760+
Ok(Some(ColumnOption::Ephemeral(None)))
5761+
} else {
5762+
Ok(Some(ColumnOption::Ephemeral(Some(self.parse_expr()?))))
5763+
}
57465764
} else if self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
57475765
let characteristics = self.parse_constraint_characteristics()?;
57485766
Ok(Some(ColumnOption::Unique {

tests/sqlparser_clickhouse.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,102 @@ fn parse_create_table_with_primary_key() {
493493
.expect_err("ORDER BY supports one expression with tuple");
494494
}
495495

496+
#[test]
497+
fn parse_create_table_with_variant_default_expressions() {
498+
let sql = concat!(
499+
"CREATE TABLE table (",
500+
"a DATETIME MATERIALIZED now(),",
501+
" b DATETIME EPHEMERAL now(),",
502+
" c DATETIME EPHEMERAL,",
503+
" d STRING ALIAS toString(c)",
504+
") ENGINE=MergeTree"
505+
);
506+
match clickhouse_and_generic().verified_stmt(sql) {
507+
Statement::CreateTable(CreateTable { columns, .. }) => {
508+
assert_eq!(
509+
columns,
510+
vec![
511+
ColumnDef {
512+
name: Ident::new("a"),
513+
data_type: DataType::Datetime(None),
514+
collation: None,
515+
options: vec![ColumnOptionDef {
516+
name: None,
517+
option: ColumnOption::Materialized(Expr::Function(Function {
518+
name: ObjectName(vec![Ident::new("now")]),
519+
args: FunctionArguments::List(FunctionArgumentList {
520+
args: vec![],
521+
duplicate_treatment: None,
522+
clauses: vec![],
523+
}),
524+
parameters: FunctionArguments::None,
525+
null_treatment: None,
526+
filter: None,
527+
over: None,
528+
within_group: vec![],
529+
}))
530+
}],
531+
},
532+
ColumnDef {
533+
name: Ident::new("b"),
534+
data_type: DataType::Datetime(None),
535+
collation: None,
536+
options: vec![ColumnOptionDef {
537+
name: None,
538+
option: ColumnOption::Ephemeral(Some(Expr::Function(Function {
539+
name: ObjectName(vec![Ident::new("now")]),
540+
args: FunctionArguments::List(FunctionArgumentList {
541+
args: vec![],
542+
duplicate_treatment: None,
543+
clauses: vec![],
544+
}),
545+
parameters: FunctionArguments::None,
546+
null_treatment: None,
547+
filter: None,
548+
over: None,
549+
within_group: vec![],
550+
})))
551+
}],
552+
},
553+
ColumnDef {
554+
name: Ident::new("c"),
555+
data_type: DataType::Datetime(None),
556+
collation: None,
557+
options: vec![ColumnOptionDef {
558+
name: None,
559+
option: ColumnOption::Ephemeral(None)
560+
}],
561+
},
562+
ColumnDef {
563+
name: Ident::new("d"),
564+
data_type: DataType::String(None),
565+
collation: None,
566+
options: vec![ColumnOptionDef {
567+
name: None,
568+
option: ColumnOption::Alias(Expr::Function(Function {
569+
name: ObjectName(vec![Ident::new("toString")]),
570+
args: FunctionArguments::List(FunctionArgumentList {
571+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
572+
Identifier(Ident::new("c"))
573+
))],
574+
duplicate_treatment: None,
575+
clauses: vec![],
576+
}),
577+
parameters: FunctionArguments::None,
578+
null_treatment: None,
579+
filter: None,
580+
over: None,
581+
within_group: vec![],
582+
}))
583+
}],
584+
}
585+
]
586+
)
587+
}
588+
_ => unreachable!(),
589+
}
590+
}
591+
496592
#[test]
497593
fn parse_create_view_with_fields_data_types() {
498594
match clickhouse().verified_stmt(r#"CREATE VIEW v (i "int", f "String") AS SELECT * FROM t"#) {

0 commit comments

Comments
 (0)