Skip to content

Commit 9c97177

Browse files
git-hulklustefaniak
authored andcommitted
Add support of MATERIALIZED/ALIAS/EPHERMERAL default column options for ClickHouse (apache#1348)
# Conflicts: # src/ast/ddl.rs # src/keywords.rs # tests/sqlparser_clickhouse.rs
1 parent 5916475 commit 9c97177

File tree

4 files changed

+220
-2
lines changed

4 files changed

+220
-2
lines changed

src/ast/ddl.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,19 @@ pub enum ColumnOption {
660660
NotNull,
661661
/// `DEFAULT <restricted-expr>`
662662
Default(Expr),
663-
/// `{ PRIMARY KEY | UNIQUE }`
663+
664+
/// ClickHouse supports `MATERIALIZE`, `EPHEMERAL` and `ALIAS` expr to generate default values.
665+
/// Syntax: `b INT MATERIALIZE (a + 1)`
666+
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/create/table#default_values)
667+
668+
/// `MATERIALIZE <expr>`
669+
Materialized(Expr),
670+
/// `EPHEMERAL [<expr>]`
671+
Ephemeral(Option<Expr>),
672+
/// `ALIAS <expr>`
673+
Alias(Expr),
674+
675+
/// `{ PRIMARY KEY | UNIQUE } [<constraint_characteristics>]`
664676
Unique {
665677
is_primary: bool,
666678
characteristics: Vec<ConstraintCharacteristics>,
@@ -702,6 +714,15 @@ impl fmt::Display for ColumnOption {
702714
Null => write!(f, "NULL"),
703715
NotNull => write!(f, "NOT NULL"),
704716
Default(expr) => write!(f, "DEFAULT {expr}"),
717+
Materialized(expr) => write!(f, "MATERIALIZED {expr}"),
718+
Ephemeral(expr) => {
719+
if let Some(e) = expr {
720+
write!(f, "EPHEMERAL {e}")
721+
} else {
722+
write!(f, "EPHEMERAL")
723+
}
724+
}
725+
Alias(expr) => write!(f, "ALIAS {expr}"),
705726
Unique {
706727
is_primary,
707728
characteristics,

src/keywords.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ define_keywords!(
7575
ADMIN,
7676
AFTER,
7777
AGAINST,
78+
AGGREGATION,
79+
ALIAS,
7880
ALL,
7981
ALLOCATE,
8082
ALTER,
@@ -252,6 +254,7 @@ define_keywords!(
252254
ENFORCED,
253255
ENGINE,
254256
ENUM,
257+
EPHEMERAL,
255258
EPOCH,
256259
EQUALS,
257260
ERROR,

src/parser/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4574,6 +4574,24 @@ impl<'a> Parser<'a> {
45744574
Ok(Some(ColumnOption::Null))
45754575
} else if self.parse_keyword(Keyword::DEFAULT) {
45764576
Ok(Some(ColumnOption::Default(self.parse_expr()?)))
4577+
} else if dialect_of!(self is ClickHouseDialect| GenericDialect)
4578+
&& self.parse_keyword(Keyword::MATERIALIZED)
4579+
{
4580+
Ok(Some(ColumnOption::Materialized(self.parse_expr()?)))
4581+
} else if dialect_of!(self is ClickHouseDialect| GenericDialect)
4582+
&& self.parse_keyword(Keyword::ALIAS)
4583+
{
4584+
Ok(Some(ColumnOption::Alias(self.parse_expr()?)))
4585+
} else if dialect_of!(self is ClickHouseDialect| GenericDialect)
4586+
&& self.parse_keyword(Keyword::EPHEMERAL)
4587+
{
4588+
// The expression is optional for the EPHEMERAL syntax, so we need to check
4589+
// if the column definition has remaining tokens before parsing the expression.
4590+
if matches!(self.peek_token().token, Token::Comma | Token::RParen) {
4591+
Ok(Some(ColumnOption::Ephemeral(None)))
4592+
} else {
4593+
Ok(Some(ColumnOption::Ephemeral(Some(self.parse_expr()?))))
4594+
}
45774595
} else if self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
45784596
let characteristics = self.parse_constraint_characteristics()?;
45794597
Ok(Some(ColumnOption::Unique {

tests/sqlparser_clickhouse.rs

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use sqlparser::ast::Ident;
2020
use sqlparser::ast::SelectItem::UnnamedExpr;
2121
use sqlparser::ast::TableFactor::Table;
2222
use sqlparser::ast::*;
23-
use sqlparser::dialect::ClickHouseDialect;
23+
use sqlparser::dialect::{ClickHouseDialect, GenericDialect};
2424
use test_utils::*;
2525

2626
#[macro_use]
@@ -455,6 +455,175 @@ fn parse_limit_by() {
455455
);
456456
}
457457

458+
#[test]
459+
fn parse_create_table_with_variant_default_expressions() {
460+
let sql = concat!(
461+
"CREATE TABLE table (",
462+
"a DATETIME MATERIALIZED now(),",
463+
" b DATETIME EPHEMERAL now(),",
464+
" c DATETIME EPHEMERAL,",
465+
" d STRING ALIAS toString(c)",
466+
") ENGINE=MergeTree"
467+
);
468+
match clickhouse().verified_stmt(sql) {
469+
Statement::CreateTable { columns, .. } => {
470+
assert_eq!(
471+
columns,
472+
vec![
473+
ColumnDef {
474+
name: Ident::new("a").empty_span(),
475+
data_type: DataType::Custom(
476+
ObjectName(vec![Ident::new("DATETIME")]),
477+
vec![]
478+
),
479+
collation: None,
480+
codec: None,
481+
options: vec![ColumnOptionDef {
482+
name: None,
483+
option: ColumnOption::Materialized(Expr::Function(Function {
484+
name: ObjectName(vec![Ident::new("now")]),
485+
args: vec![],
486+
null_treatment: None,
487+
over: None,
488+
distinct: false,
489+
special: false,
490+
order_by: vec![],
491+
limit: None,
492+
within_group: None,
493+
on_overflow: None,
494+
}))
495+
}],
496+
column_options: vec![],
497+
mask: None,
498+
column_location: None,
499+
},
500+
ColumnDef {
501+
name: Ident::new("b").empty_span(),
502+
data_type: DataType::Custom(
503+
ObjectName(vec![Ident::new("DATETIME")]),
504+
vec![]
505+
),
506+
collation: None,
507+
codec: None,
508+
options: vec![ColumnOptionDef {
509+
name: None,
510+
option: ColumnOption::Ephemeral(Some(Expr::Function(Function {
511+
name: ObjectName(vec![Ident::new("now")]),
512+
args: vec![],
513+
null_treatment: None,
514+
over: None,
515+
distinct: false,
516+
special: false,
517+
order_by: vec![],
518+
limit: None,
519+
within_group: None,
520+
on_overflow: None,
521+
})))
522+
}],
523+
column_options: vec![],
524+
mask: None,
525+
column_location: None,
526+
},
527+
ColumnDef {
528+
name: Ident::new("c").empty_span(),
529+
data_type: DataType::Custom(
530+
ObjectName(vec![Ident::new("DATETIME")]),
531+
vec![]
532+
),
533+
collation: None,
534+
codec: None,
535+
options: vec![ColumnOptionDef {
536+
name: None,
537+
option: ColumnOption::Ephemeral(None)
538+
}],
539+
column_options: vec![],
540+
mask: None,
541+
column_location: None,
542+
},
543+
ColumnDef {
544+
name: Ident::new("d").empty_span(),
545+
data_type: DataType::Custom(ObjectName(vec![Ident::new("STRING")]), vec![]),
546+
collation: None,
547+
codec: None,
548+
options: vec![ColumnOptionDef {
549+
name: None,
550+
option: ColumnOption::Alias(Expr::Function(Function {
551+
name: ObjectName(vec![Ident::new("toString")]),
552+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
553+
Expr::Identifier(Ident::new("c").empty_span())
554+
))],
555+
null_treatment: None,
556+
over: None,
557+
distinct: false,
558+
special: false,
559+
order_by: vec![],
560+
limit: None,
561+
within_group: None,
562+
on_overflow: None,
563+
}))
564+
}],
565+
column_options: vec![],
566+
mask: None,
567+
column_location: None,
568+
}
569+
]
570+
)
571+
}
572+
_ => unreachable!(),
573+
}
574+
}
575+
576+
#[test]
577+
fn parse_create_view_with_fields_data_types() {
578+
match clickhouse().verified_stmt(r#"CREATE VIEW v (i "int", f "String") AS SELECT * FROM t"#) {
579+
Statement::CreateView {
580+
name,
581+
columns_with_types,
582+
..
583+
} => {
584+
assert_eq!(name, ObjectName(vec!["v".into()]));
585+
assert_eq!(
586+
columns_with_types,
587+
vec![
588+
ColumnDef {
589+
name: Ident::new("i").empty_span(),
590+
data_type: DataType::Custom(
591+
ObjectName(vec![Ident {
592+
value: "int".into(),
593+
quote_style: Some('"')
594+
}]),
595+
vec![]
596+
),
597+
collation: None,
598+
codec: None,
599+
options: vec![],
600+
column_options: vec![],
601+
mask: None,
602+
column_location: None,
603+
},
604+
ColumnDef {
605+
name: Ident::new("f").empty_span(),
606+
data_type: DataType::Custom(
607+
ObjectName(vec![Ident {
608+
value: "String".into(),
609+
quote_style: Some('"')
610+
}]),
611+
vec![]
612+
),
613+
collation: None,
614+
codec: None,
615+
options: vec![],
616+
column_options: vec![],
617+
mask: None,
618+
column_location: None,
619+
},
620+
]
621+
);
622+
}
623+
_ => unreachable!(),
624+
}
625+
}
626+
458627
#[test]
459628
fn parse_array_accessor() {
460629
clickhouse().one_statement_parses_to(
@@ -552,3 +721,10 @@ fn clickhouse() -> TestedDialects {
552721
options: None,
553722
}
554723
}
724+
725+
fn clickhouse_and_generic() -> TestedDialects {
726+
TestedDialects {
727+
dialects: vec![Box::new(ClickHouseDialect {}), Box::new(GenericDialect {})],
728+
options: None,
729+
}
730+
}

0 commit comments

Comments
 (0)