Add support of MATERIALIZED/ALIAS/EPHERMERAL default column options for ClickHouse (#1348)

This commit is contained in:
hulk 2024-07-24 00:41:07 +08:00 committed by GitHub
parent b27abf00e2
commit 390d4d3554
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 137 additions and 0 deletions

View file

@ -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,
@ -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,

View file

@ -77,6 +77,7 @@ define_keywords!(
AFTER,
AGAINST,
AGGREGATION,
ALIAS,
ALL,
ALLOCATE,
ALTER,
@ -267,6 +268,7 @@ define_keywords!(
ENFORCED,
ENGINE,
ENUM,
EPHEMERAL,
EPOCH,
EQUALS,
ERROR,

View file

@ -5748,6 +5748,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 {

View file

@ -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"#) {