Skip to content

Commit d21b563

Browse files
author
aleksei.p
committed
ClickHouse: support of create table query with primary key and parametrised table engine
1 parent 4b60866 commit d21b563

File tree

5 files changed

+162
-17
lines changed

5 files changed

+162
-17
lines changed

src/ast/helpers/stmt_create_table.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use sqlparser_derive::{Visit, VisitMut};
1010
use super::super::dml::CreateTable;
1111
use crate::ast::{
1212
ColumnDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit,
13-
Query, SqlOption, Statement, TableConstraint,
13+
OneOrManyWithParens, Query, SqlOption, Statement, TableConstraint, TableEngine,
1414
};
1515
use crate::parser::ParserError;
1616

@@ -65,14 +65,15 @@ pub struct CreateTableBuilder {
6565
pub without_rowid: bool,
6666
pub like: Option<ObjectName>,
6767
pub clone: Option<ObjectName>,
68-
pub engine: Option<String>,
68+
pub engine: Option<TableEngine>,
6969
pub comment: Option<String>,
7070
pub auto_increment_offset: Option<u32>,
7171
pub default_charset: Option<String>,
7272
pub collation: Option<String>,
7373
pub on_commit: Option<OnCommit>,
7474
pub on_cluster: Option<String>,
75-
pub order_by: Option<Vec<Ident>>,
75+
pub primary_key: Option<Box<Expr>>,
76+
pub order_by: Option<OneOrManyWithParens<Expr>>,
7677
pub partition_by: Option<Box<Expr>>,
7778
pub cluster_by: Option<Vec<Ident>>,
7879
pub options: Option<Vec<SqlOption>>,
@@ -108,6 +109,7 @@ impl CreateTableBuilder {
108109
collation: None,
109110
on_commit: None,
110111
on_cluster: None,
112+
primary_key: None,
111113
order_by: None,
112114
partition_by: None,
113115
cluster_by: None,
@@ -203,7 +205,7 @@ impl CreateTableBuilder {
203205
self
204206
}
205207

206-
pub fn engine(mut self, engine: Option<String>) -> Self {
208+
pub fn engine(mut self, engine: Option<TableEngine>) -> Self {
207209
self.engine = engine;
208210
self
209211
}
@@ -238,7 +240,12 @@ impl CreateTableBuilder {
238240
self
239241
}
240242

241-
pub fn order_by(mut self, order_by: Option<Vec<Ident>>) -> Self {
243+
pub fn primary_key(mut self, primary_key: Option<Box<Expr>>) -> Self {
244+
self.primary_key = primary_key;
245+
self
246+
}
247+
248+
pub fn order_by(mut self, order_by: Option<OneOrManyWithParens<Expr>>) -> Self {
242249
self.order_by = order_by;
243250
self
244251
}
@@ -291,6 +298,7 @@ impl CreateTableBuilder {
291298
collation: self.collation,
292299
on_commit: self.on_commit,
293300
on_cluster: self.on_cluster,
301+
primary_key: self.primary_key,
294302
order_by: self.order_by,
295303
partition_by: self.partition_by,
296304
cluster_by: self.cluster_by,
@@ -334,6 +342,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
334342
collation,
335343
on_commit,
336344
on_cluster,
345+
primary_key,
337346
order_by,
338347
partition_by,
339348
cluster_by,
@@ -366,6 +375,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
366375
collation,
367376
on_commit,
368377
on_cluster,
378+
primary_key,
369379
order_by,
370380
partition_by,
371381
cluster_by,

src/ast/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6315,6 +6315,29 @@ impl Display for MySQLColumnPosition {
63156315
}
63166316
}
63176317

6318+
/// Engine of DB. Some warehouse has parameters of engine, e.g. [clickhouse]
6319+
///
6320+
/// [clickhouse]: https://clickhouse.com/docs/en/engines/table-engines
6321+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
6322+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6323+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
6324+
pub struct TableEngine {
6325+
pub name: String,
6326+
pub parameters: Option<Vec<Ident>>,
6327+
}
6328+
6329+
impl Display for TableEngine {
6330+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
6331+
write!(f, "{}", self.name)?;
6332+
6333+
if let Some(parameters) = self.parameters.as_ref() {
6334+
write!(f, "({})", display_comma_separated(parameters))?;
6335+
}
6336+
6337+
Ok(())
6338+
}
6339+
}
6340+
63186341
#[cfg(test)]
63196342
mod tests {
63206343
use super::*;

src/parser/mod.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5262,7 +5262,15 @@ impl<'a> Parser<'a> {
52625262
self.expect_token(&Token::Eq)?;
52635263
let next_token = self.next_token();
52645264
match next_token.token {
5265-
Token::Word(w) => Some(w.value),
5265+
Token::Word(w) => {
5266+
let name = w.value;
5267+
let parameters = if self.peek_token() == Token::LParen {
5268+
Some(self.parse_parenthesized_identifiers()?)
5269+
} else {
5270+
None
5271+
};
5272+
Some(TableEngine { name, parameters })
5273+
}
52665274
_ => self.expected("identifier", next_token)?,
52675275
}
52685276
} else {
@@ -5280,17 +5288,27 @@ impl<'a> Parser<'a> {
52805288
None
52815289
};
52825290

5291+
// ClickHouse supports `PRIMARY KEY`, before `ORDER BY`
5292+
// https://clickhouse.com/docs/en/sql-reference/statements/create/table#primary-key
5293+
let primary_key = if dialect_of!(self is ClickHouseDialect | GenericDialect)
5294+
&& self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY])
5295+
{
5296+
Some(Box::new(self.parse_expr()?))
5297+
} else {
5298+
None
5299+
};
5300+
52835301
let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
52845302
if self.consume_token(&Token::LParen) {
52855303
let columns = if self.peek_token() != Token::RParen {
5286-
self.parse_comma_separated(|p| p.parse_identifier(false))?
5304+
self.parse_comma_separated(|p| p.parse_expr())?
52875305
} else {
52885306
vec![]
52895307
};
52905308
self.expect_token(&Token::RParen)?;
5291-
Some(columns)
5309+
Some(OneOrManyWithParens::Many(columns))
52925310
} else {
5293-
Some(vec![self.parse_identifier(false)?])
5311+
Some(OneOrManyWithParens::One(self.parse_expr()?))
52945312
}
52955313
} else {
52965314
None
@@ -5388,6 +5406,7 @@ impl<'a> Parser<'a> {
53885406
.partition_by(big_query_config.partition_by)
53895407
.cluster_by(big_query_config.cluster_by)
53905408
.options(big_query_config.options)
5409+
.primary_key(primary_key)
53915410
.strict(strict)
53925411
.build())
53935412
}
@@ -9041,7 +9060,7 @@ impl<'a> Parser<'a> {
90419060
let partitions: Vec<Ident> = if dialect_of!(self is MySqlDialect | GenericDialect)
90429061
&& self.parse_keyword(Keyword::PARTITION)
90439062
{
9044-
self.parse_partitions()?
9063+
self.parse_parenthesized_identifiers()?
90459064
} else {
90469065
vec![]
90479066
};
@@ -10969,7 +10988,7 @@ impl<'a> Parser<'a> {
1096910988
})
1097010989
}
1097110990

10972-
fn parse_partitions(&mut self) -> Result<Vec<Ident>, ParserError> {
10991+
fn parse_parenthesized_identifiers(&mut self) -> Result<Vec<Ident>, ParserError> {
1097310992
self.expect_token(&Token::LParen)?;
1097410993
let partitions = self.parse_comma_separated(|p| p.parse_identifier(false))?;
1097510994
self.expect_token(&Token::RParen)?;

tests/sqlparser_clickhouse.rs

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,9 @@ fn parse_delimited_identifiers() {
211211
#[test]
212212
fn parse_create_table() {
213213
clickhouse().verified_stmt(r#"CREATE TABLE "x" ("a" "int") ENGINE=MergeTree ORDER BY ("x")"#);
214-
clickhouse().one_statement_parses_to(
215-
r#"CREATE TABLE "x" ("a" "int") ENGINE=MergeTree ORDER BY "x""#,
216-
r#"CREATE TABLE "x" ("a" "int") ENGINE=MergeTree ORDER BY ("x")"#,
217-
);
214+
clickhouse().verified_stmt(r#"CREATE TABLE "x" ("a" "int") ENGINE=MergeTree ORDER BY "x""#);
218215
clickhouse().verified_stmt(
219-
r#"CREATE TABLE "x" ("a" "int") ENGINE=MergeTree ORDER BY ("x") AS SELECT * FROM "t" WHERE true"#,
216+
r#"CREATE TABLE "x" ("a" "int") ENGINE=MergeTree ORDER BY "x" AS SELECT * FROM "t" WHERE true"#,
220217
);
221218
}
222219

@@ -410,6 +407,96 @@ fn parse_create_table_with_nested_data_types() {
410407
}
411408
}
412409

410+
#[test]
411+
fn parse_create_table_with_primary_key() {
412+
match clickhouse_and_generic().one_statement_parses_to(
413+
concat!(
414+
r#"CREATE TABLE db.table (`i` Int, `k` Int)"#,
415+
" ENGINE=SharedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')",
416+
" PRIMARY KEY tuple(i)",
417+
" ORDER BY tuple(i)",
418+
),
419+
concat!(
420+
r#"CREATE TABLE db.table (`i` INT, `k` INT)"#,
421+
" ENGINE=SharedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')",
422+
" PRIMARY KEY tuple(i)",
423+
" ORDER BY tuple(i)",
424+
),
425+
) {
426+
Statement::CreateTable {
427+
name,
428+
columns,
429+
engine,
430+
primary_key,
431+
order_by,
432+
..
433+
} => {
434+
assert_eq!(name.to_string(), "db.table");
435+
assert_eq!(
436+
vec![
437+
ColumnDef {
438+
name: Ident::with_quote('`', "i"),
439+
data_type: DataType::Int(None),
440+
collation: None,
441+
options: vec![],
442+
},
443+
ColumnDef {
444+
name: Ident::with_quote('`', "k"),
445+
data_type: DataType::Int(None),
446+
collation: None,
447+
options: vec![],
448+
},
449+
],
450+
columns
451+
);
452+
assert_eq!(
453+
engine,
454+
Some(TableEngine {
455+
name: "SharedMergeTree".to_string(),
456+
parameters: Some(vec![
457+
Ident::with_quote('\'', "/clickhouse/tables/{uuid}/{shard}"),
458+
Ident::with_quote('\'', "{replica}"),
459+
]),
460+
})
461+
);
462+
fn assert_function(actual: &Function, name: &str, arg: &str) -> bool {
463+
assert_eq!(actual.name, ObjectName(vec![Ident::new(name)]));
464+
assert_eq!(
465+
actual.args,
466+
FunctionArguments::List(FunctionArgumentList {
467+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Identifier(
468+
Ident::new(arg)
469+
)),)],
470+
duplicate_treatment: None,
471+
clauses: vec![],
472+
})
473+
);
474+
true
475+
}
476+
match primary_key.unwrap().as_ref() {
477+
Expr::Function(primary_key) => {
478+
assert!(assert_function(primary_key, "tuple", "i"));
479+
}
480+
_ => panic!("unexpected primary key type"),
481+
}
482+
match order_by {
483+
Some(OneOrManyWithParens::One(Expr::Function(order_by))) => {
484+
assert!(assert_function(&order_by, "tuple", "i"));
485+
}
486+
_ => panic!("unexpected order by type"),
487+
};
488+
}
489+
_ => unreachable!(),
490+
}
491+
492+
clickhouse_and_generic()
493+
.parse_sql_statements(concat!(
494+
r#"CREATE TABLE db.table (`i` Int, `k` Int)"#,
495+
" ORDER BY tuple(i), tuple(k)",
496+
))
497+
.expect_err("ORDER BY supports one expression with tuple");
498+
}
499+
413500
#[test]
414501
fn parse_create_view_with_fields_data_types() {
415502
match clickhouse().verified_stmt(r#"CREATE VIEW v (i "int", f "String") AS SELECT * FROM t"#) {

tests/sqlparser_mysql.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,13 @@ fn parse_create_table_engine_default_charset() {
773773
},],
774774
columns
775775
);
776-
assert_eq!(engine, Some("InnoDB".to_string()));
776+
assert_eq!(
777+
engine,
778+
Some(TableEngine {
779+
name: "InnoDB".to_string(),
780+
parameters: None
781+
})
782+
);
777783
assert_eq!(default_charset, Some("utf8mb3".to_string()));
778784
}
779785
_ => unreachable!(),

0 commit comments

Comments
 (0)