Skip to content

Commit 12faee5

Browse files
author
aleksei.p
committed
ClickHouse: support of create table query with primary key and parametrised table engine
1 parent 375742d commit 12faee5

File tree

5 files changed

+172
-20
lines changed

5 files changed

+172
-20
lines changed

src/ast/helpers/stmt_create_table.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use sqlparser_derive::{Visit, VisitMut};
99

1010
use crate::ast::{
1111
ColumnDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit,
12-
Query, SqlOption, Statement, TableConstraint,
12+
OneOrManyWithParens, Query, SqlOption, Statement, TableConstraint, TableEngine,
1313
};
1414
use crate::parser::ParserError;
1515

@@ -64,14 +64,15 @@ pub struct CreateTableBuilder {
6464
pub without_rowid: bool,
6565
pub like: Option<ObjectName>,
6666
pub clone: Option<ObjectName>,
67-
pub engine: Option<String>,
67+
pub engine: Option<TableEngine>,
6868
pub comment: Option<String>,
6969
pub auto_increment_offset: Option<u32>,
7070
pub default_charset: Option<String>,
7171
pub collation: Option<String>,
7272
pub on_commit: Option<OnCommit>,
7373
pub on_cluster: Option<String>,
74-
pub order_by: Option<Vec<Ident>>,
74+
pub primary_key: Option<Box<Expr>>,
75+
pub order_by: Option<OneOrManyWithParens<Expr>>,
7576
pub partition_by: Option<Box<Expr>>,
7677
pub cluster_by: Option<Vec<Ident>>,
7778
pub options: Option<Vec<SqlOption>>,
@@ -107,6 +108,7 @@ impl CreateTableBuilder {
107108
collation: None,
108109
on_commit: None,
109110
on_cluster: None,
111+
primary_key: None,
110112
order_by: None,
111113
partition_by: None,
112114
cluster_by: None,
@@ -202,7 +204,7 @@ impl CreateTableBuilder {
202204
self
203205
}
204206

205-
pub fn engine(mut self, engine: Option<String>) -> Self {
207+
pub fn engine(mut self, engine: Option<TableEngine>) -> Self {
206208
self.engine = engine;
207209
self
208210
}
@@ -237,7 +239,12 @@ impl CreateTableBuilder {
237239
self
238240
}
239241

240-
pub fn order_by(mut self, order_by: Option<Vec<Ident>>) -> Self {
242+
pub fn primary_key(mut self, primary_key: Option<Box<Expr>>) -> Self {
243+
self.primary_key = primary_key;
244+
self
245+
}
246+
247+
pub fn order_by(mut self, order_by: Option<OneOrManyWithParens<Expr>>) -> Self {
241248
self.order_by = order_by;
242249
self
243250
}
@@ -290,6 +297,7 @@ impl CreateTableBuilder {
290297
collation: self.collation,
291298
on_commit: self.on_commit,
292299
on_cluster: self.on_cluster,
300+
primary_key: self.primary_key,
293301
order_by: self.order_by,
294302
partition_by: self.partition_by,
295303
cluster_by: self.cluster_by,
@@ -333,6 +341,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
333341
collation,
334342
on_commit,
335343
on_cluster,
344+
primary_key,
336345
order_by,
337346
partition_by,
338347
cluster_by,
@@ -365,6 +374,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
365374
collation,
366375
on_commit,
367376
on_cluster,
377+
primary_key,
368378
order_by,
369379
partition_by,
370380
cluster_by,

src/ast/mod.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,7 +1995,7 @@ pub enum Statement {
19951995
without_rowid: bool,
19961996
like: Option<ObjectName>,
19971997
clone: Option<ObjectName>,
1998-
engine: Option<String>,
1998+
engine: Option<TableEngine>,
19991999
comment: Option<String>,
20002000
auto_increment_offset: Option<u32>,
20012001
default_charset: Option<String>,
@@ -2004,10 +2004,13 @@ pub enum Statement {
20042004
/// ClickHouse "ON CLUSTER" clause:
20052005
/// <https://clickhouse.com/docs/en/sql-reference/distributed-ddl/>
20062006
on_cluster: Option<String>,
2007+
/// ClickHouse "PRIMARY KEY " clause.
2008+
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/table/>
2009+
primary_key: Option<Box<Expr>>,
20072010
/// ClickHouse "ORDER BY " clause. Note that omitted ORDER BY is different
20082011
/// than empty (represented as ()), the latter meaning "no sorting".
20092012
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/table/>
2010-
order_by: Option<Vec<Ident>>,
2013+
order_by: Option<OneOrManyWithParens<Expr>>,
20112014
/// BigQuery: A partition expression for the table.
20122015
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#partition_expression>
20132016
partition_by: Option<Box<Expr>>,
@@ -3392,6 +3395,7 @@ impl fmt::Display for Statement {
33923395
collation,
33933396
on_commit,
33943397
on_cluster,
3398+
primary_key,
33953399
order_by,
33963400
partition_by,
33973401
cluster_by,
@@ -3560,8 +3564,11 @@ impl fmt::Display for Statement {
35603564
if let Some(auto_increment_offset) = auto_increment_offset {
35613565
write!(f, " AUTO_INCREMENT {auto_increment_offset}")?;
35623566
}
3567+
if let Some(primary_key) = primary_key {
3568+
write!(f, " PRIMARY KEY {primary_key}")?;
3569+
}
35633570
if let Some(order_by) = order_by {
3564-
write!(f, " ORDER BY ({})", display_comma_separated(order_by))?;
3571+
write!(f, " ORDER BY {order_by}")?;
35653572
}
35663573
if let Some(partition_by) = partition_by.as_ref() {
35673574
write!(f, " PARTITION BY {partition_by}")?;
@@ -6554,6 +6561,29 @@ impl Display for MySQLColumnPosition {
65546561
}
65556562
}
65566563

6564+
/// Engine of DB. Some warehouse has parameters of engine, e.g. [clickhouse]
6565+
///
6566+
/// [clickhouse]: https://clickhouse.com/docs/en/engines/table-engines
6567+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
6568+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6569+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
6570+
pub struct TableEngine {
6571+
pub name: String,
6572+
pub parameters: Option<Vec<Ident>>,
6573+
}
6574+
6575+
impl Display for TableEngine {
6576+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
6577+
write!(f, "{}", self.name)?;
6578+
6579+
if let Some(parameters) = self.parameters.as_ref() {
6580+
write!(f, "({})", display_comma_separated(parameters))?;
6581+
}
6582+
6583+
Ok(())
6584+
}
6585+
}
6586+
65576587
#[cfg(test)]
65586588
mod tests {
65596589
use super::*;

src/parser/mod.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5139,7 +5139,15 @@ impl<'a> Parser<'a> {
51395139
self.expect_token(&Token::Eq)?;
51405140
let next_token = self.next_token();
51415141
match next_token.token {
5142-
Token::Word(w) => Some(w.value),
5142+
Token::Word(w) => {
5143+
let name = w.value;
5144+
let parameters = if self.peek_token() == Token::LParen {
5145+
Some(self.parse_parenthesized_identifiers()?)
5146+
} else {
5147+
None
5148+
};
5149+
Some(TableEngine { name, parameters })
5150+
}
51435151
_ => self.expected("identifier", next_token)?,
51445152
}
51455153
} else {
@@ -5157,17 +5165,27 @@ impl<'a> Parser<'a> {
51575165
None
51585166
};
51595167

5168+
// ClickHouse supports `PRIMARY KEY`, before `ORDER BY`
5169+
// https://clickhouse.com/docs/en/sql-reference/statements/create/table#primary-key
5170+
let primary_key = if dialect_of!(self is ClickHouseDialect | GenericDialect)
5171+
&& self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY])
5172+
{
5173+
Some(Box::new(self.parse_expr()?))
5174+
} else {
5175+
None
5176+
};
5177+
51605178
let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
51615179
if self.consume_token(&Token::LParen) {
51625180
let columns = if self.peek_token() != Token::RParen {
5163-
self.parse_comma_separated(|p| p.parse_identifier(false))?
5181+
self.parse_comma_separated(|p| p.parse_expr())?
51645182
} else {
51655183
vec![]
51665184
};
51675185
self.expect_token(&Token::RParen)?;
5168-
Some(columns)
5186+
Some(OneOrManyWithParens::Many(columns))
51695187
} else {
5170-
Some(vec![self.parse_identifier(false)?])
5188+
Some(OneOrManyWithParens::One(self.parse_expr()?))
51715189
}
51725190
} else {
51735191
None
@@ -5265,6 +5283,7 @@ impl<'a> Parser<'a> {
52655283
.partition_by(big_query_config.partition_by)
52665284
.cluster_by(big_query_config.cluster_by)
52675285
.options(big_query_config.options)
5286+
.primary_key(primary_key)
52685287
.strict(strict)
52695288
.build())
52705289
}
@@ -8818,7 +8837,7 @@ impl<'a> Parser<'a> {
88188837
let partitions: Vec<Ident> = if dialect_of!(self is MySqlDialect | GenericDialect)
88198838
&& self.parse_keyword(Keyword::PARTITION)
88208839
{
8821-
self.parse_partitions()?
8840+
self.parse_parenthesized_identifiers()?
88228841
} else {
88238842
vec![]
88248843
};
@@ -10737,7 +10756,7 @@ impl<'a> Parser<'a> {
1073710756
})
1073810757
}
1073910758

10740-
fn parse_partitions(&mut self) -> Result<Vec<Ident>, ParserError> {
10759+
fn parse_parenthesized_identifiers(&mut self) -> Result<Vec<Ident>, ParserError> {
1074110760
self.expect_token(&Token::LParen)?;
1074210761
let partitions = self.parse_comma_separated(|p| p.parse_identifier(false))?;
1074310762
self.expect_token(&Token::RParen)?;

tests/sqlparser_clickhouse.rs

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,102 @@ 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

220+
#[test]
221+
fn parse_create_table_with_primary_key() {
222+
match clickhouse_and_generic().one_statement_parses_to(
223+
concat!(
224+
r#"CREATE TABLE db.table (`i` Int, `k` Int)"#,
225+
" ENGINE=SharedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')",
226+
" PRIMARY KEY tuple(i)",
227+
" ORDER BY tuple(i)",
228+
),
229+
concat!(
230+
r#"CREATE TABLE db.table (`i` INT, `k` INT)"#,
231+
" ENGINE=SharedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')",
232+
" PRIMARY KEY tuple(i)",
233+
" ORDER BY tuple(i)",
234+
),
235+
) {
236+
Statement::CreateTable {
237+
name,
238+
columns,
239+
engine,
240+
primary_key,
241+
order_by,
242+
..
243+
} => {
244+
assert_eq!(name.to_string(), "db.table");
245+
assert_eq!(
246+
vec![
247+
ColumnDef {
248+
name: Ident::with_quote('`', "i"),
249+
data_type: DataType::Int(None),
250+
collation: None,
251+
options: vec![],
252+
},
253+
ColumnDef {
254+
name: Ident::with_quote('`', "k"),
255+
data_type: DataType::Int(None),
256+
collation: None,
257+
options: vec![],
258+
},
259+
],
260+
columns
261+
);
262+
assert_eq!(
263+
engine,
264+
Some(TableEngine {
265+
name: "SharedMergeTree".to_string(),
266+
parameters: Some(vec![
267+
Ident::with_quote('\'', "/clickhouse/tables/{uuid}/{shard}"),
268+
Ident::with_quote('\'', "{replica}"),
269+
]),
270+
})
271+
);
272+
fn assert_function(actual: &Function, name: &str, arg: &str) -> bool {
273+
assert_eq!(actual.name, ObjectName(vec![Ident::new(name)]));
274+
assert_eq!(
275+
actual.args,
276+
FunctionArguments::List(FunctionArgumentList {
277+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Identifier(
278+
Ident::new(arg)
279+
)),)],
280+
duplicate_treatment: None,
281+
clauses: vec![],
282+
})
283+
);
284+
true
285+
}
286+
match primary_key.unwrap().as_ref() {
287+
Expr::Function(primary_key) => {
288+
assert!(assert_function(primary_key, "tuple", "i"));
289+
}
290+
_ => panic!("unexpected primary key type"),
291+
}
292+
match order_by {
293+
Some(OneOrManyWithParens::One(Expr::Function(order_by))) => {
294+
assert!(assert_function(&order_by, "tuple", "i"));
295+
}
296+
_ => panic!("unexpected order by type"),
297+
};
298+
}
299+
_ => unreachable!(),
300+
}
301+
302+
clickhouse_and_generic()
303+
.parse_sql_statements(concat!(
304+
r#"CREATE TABLE db.table (`i` Int, `k` Int)"#,
305+
" ORDER BY tuple(i), tuple(k)",
306+
))
307+
.expect_err("ORDER BY supports one expression with tuple");
308+
}
309+
223310
#[test]
224311
fn parse_create_view_with_fields_data_types() {
225312
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)