Skip to content

Commit dd7ba72

Browse files
authored
Add support of the ENUM8|ENUM16 for ClickHouse dialect (#1574)
1 parent c761f0b commit dd7ba72

File tree

6 files changed

+179
-49
lines changed

6 files changed

+179
-49
lines changed

src/ast/data_type.rs

+27-5
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,21 @@ use serde::{Deserialize, Serialize};
2525
#[cfg(feature = "visitor")]
2626
use sqlparser_derive::{Visit, VisitMut};
2727

28-
use crate::ast::{display_comma_separated, ObjectName, StructField, UnionField};
28+
use crate::ast::{display_comma_separated, Expr, ObjectName, StructField, UnionField};
2929

3030
use super::{value::escape_single_quote_string, ColumnDef};
3131

32+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
33+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
35+
pub enum EnumMember {
36+
Name(String),
37+
/// ClickHouse allows to specify an integer value for each enum value.
38+
///
39+
/// [clickhouse](https://clickhouse.com/docs/en/sql-reference/data-types/enum)
40+
NamedValue(String, Expr),
41+
}
42+
3243
/// SQL data types
3344
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3445
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -334,7 +345,7 @@ pub enum DataType {
334345
/// [clickhouse]: https://clickhouse.com/docs/en/sql-reference/data-types/nested-data-structures/nested
335346
Nested(Vec<ColumnDef>),
336347
/// Enums
337-
Enum(Vec<String>),
348+
Enum(Vec<EnumMember>, Option<u8>),
338349
/// Set
339350
Set(Vec<String>),
340351
/// Struct
@@ -546,13 +557,24 @@ impl fmt::Display for DataType {
546557
write!(f, "{}({})", ty, modifiers.join(", "))
547558
}
548559
}
549-
DataType::Enum(vals) => {
550-
write!(f, "ENUM(")?;
560+
DataType::Enum(vals, bits) => {
561+
match bits {
562+
Some(bits) => write!(f, "ENUM{}", bits),
563+
None => write!(f, "ENUM"),
564+
}?;
565+
write!(f, "(")?;
551566
for (i, v) in vals.iter().enumerate() {
552567
if i != 0 {
553568
write!(f, ", ")?;
554569
}
555-
write!(f, "'{}'", escape_single_quote_string(v))?;
570+
match v {
571+
EnumMember::Name(name) => {
572+
write!(f, "'{}'", escape_single_quote_string(name))?
573+
}
574+
EnumMember::NamedValue(name, value) => {
575+
write!(f, "'{}' = {}", escape_single_quote_string(name), value)?
576+
}
577+
}
556578
}
557579
write!(f, ")")
558580
}

src/ast/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use sqlparser_derive::{Visit, VisitMut};
4040
use crate::tokenizer::Span;
4141

4242
pub use self::data_type::{
43-
ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, ExactNumberInfo,
43+
ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, EnumMember, ExactNumberInfo,
4444
StructBracketKind, TimezoneInfo,
4545
};
4646
pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue, Use};

src/keywords.rs

+2
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ define_keywords!(
286286
ENFORCED,
287287
ENGINE,
288288
ENUM,
289+
ENUM16,
290+
ENUM8,
289291
EPHEMERAL,
290292
EPOCH,
291293
EQUALS,

src/parser/mod.rs

+55-36
Original file line numberDiff line numberDiff line change
@@ -1049,18 +1049,18 @@ impl<'a> Parser<'a> {
10491049
| Keyword::CURRENT_USER
10501050
| Keyword::SESSION_USER
10511051
| Keyword::USER
1052-
if dialect_of!(self is PostgreSqlDialect | GenericDialect) =>
1053-
{
1054-
Ok(Some(Expr::Function(Function {
1055-
name: ObjectName(vec![w.to_ident(w_span)]),
1056-
parameters: FunctionArguments::None,
1057-
args: FunctionArguments::None,
1058-
null_treatment: None,
1059-
filter: None,
1060-
over: None,
1061-
within_group: vec![],
1062-
})))
1063-
}
1052+
if dialect_of!(self is PostgreSqlDialect | GenericDialect) =>
1053+
{
1054+
Ok(Some(Expr::Function(Function {
1055+
name: ObjectName(vec![w.to_ident(w_span)]),
1056+
parameters: FunctionArguments::None,
1057+
args: FunctionArguments::None,
1058+
null_treatment: None,
1059+
filter: None,
1060+
over: None,
1061+
within_group: vec![],
1062+
})))
1063+
}
10641064
Keyword::CURRENT_TIMESTAMP
10651065
| Keyword::CURRENT_TIME
10661066
| Keyword::CURRENT_DATE
@@ -1075,18 +1075,18 @@ impl<'a> Parser<'a> {
10751075
Keyword::TRY_CAST => Ok(Some(self.parse_cast_expr(CastKind::TryCast)?)),
10761076
Keyword::SAFE_CAST => Ok(Some(self.parse_cast_expr(CastKind::SafeCast)?)),
10771077
Keyword::EXISTS
1078-
// Support parsing Databricks has a function named `exists`.
1079-
if !dialect_of!(self is DatabricksDialect)
1080-
|| matches!(
1078+
// Support parsing Databricks has a function named `exists`.
1079+
if !dialect_of!(self is DatabricksDialect)
1080+
|| matches!(
10811081
self.peek_nth_token(1).token,
10821082
Token::Word(Word {
10831083
keyword: Keyword::SELECT | Keyword::WITH,
10841084
..
10851085
})
10861086
) =>
1087-
{
1088-
Ok(Some(self.parse_exists_expr(false)?))
1089-
}
1087+
{
1088+
Ok(Some(self.parse_exists_expr(false)?))
1089+
}
10901090
Keyword::EXTRACT => Ok(Some(self.parse_extract_expr()?)),
10911091
Keyword::CEIL => Ok(Some(self.parse_ceil_floor_expr(true)?)),
10921092
Keyword::FLOOR => Ok(Some(self.parse_ceil_floor_expr(false)?)),
@@ -1103,22 +1103,22 @@ impl<'a> Parser<'a> {
11031103
Ok(Some(self.parse_array_expr(true)?))
11041104
}
11051105
Keyword::ARRAY
1106-
if self.peek_token() == Token::LParen
1107-
&& !dialect_of!(self is ClickHouseDialect | DatabricksDialect) =>
1108-
{
1109-
self.expect_token(&Token::LParen)?;
1110-
let query = self.parse_query()?;
1111-
self.expect_token(&Token::RParen)?;
1112-
Ok(Some(Expr::Function(Function {
1113-
name: ObjectName(vec![w.to_ident(w_span)]),
1114-
parameters: FunctionArguments::None,
1115-
args: FunctionArguments::Subquery(query),
1116-
filter: None,
1117-
null_treatment: None,
1118-
over: None,
1119-
within_group: vec![],
1120-
})))
1121-
}
1106+
if self.peek_token() == Token::LParen
1107+
&& !dialect_of!(self is ClickHouseDialect | DatabricksDialect) =>
1108+
{
1109+
self.expect_token(&Token::LParen)?;
1110+
let query = self.parse_query()?;
1111+
self.expect_token(&Token::RParen)?;
1112+
Ok(Some(Expr::Function(Function {
1113+
name: ObjectName(vec![w.to_ident(w_span)]),
1114+
parameters: FunctionArguments::None,
1115+
args: FunctionArguments::Subquery(query),
1116+
filter: None,
1117+
null_treatment: None,
1118+
over: None,
1119+
within_group: vec![],
1120+
})))
1121+
}
11221122
Keyword::NOT => Ok(Some(self.parse_not()?)),
11231123
Keyword::MATCH if dialect_of!(self is MySqlDialect | GenericDialect) => {
11241124
Ok(Some(self.parse_match_against()?))
@@ -5023,7 +5023,7 @@ impl<'a> Parser<'a> {
50235023
return Err(ParserError::ParserError(format!("Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO. {e}")))
50245024
}
50255025
}
5026-
},
5026+
}
50275027
};
50285028
Ok(owner)
50295029
}
@@ -7997,6 +7997,23 @@ impl<'a> Parser<'a> {
79977997
}
79987998
}
79997999

8000+
pub fn parse_enum_values(&mut self) -> Result<Vec<EnumMember>, ParserError> {
8001+
self.expect_token(&Token::LParen)?;
8002+
let values = self.parse_comma_separated(|parser| {
8003+
let name = parser.parse_literal_string()?;
8004+
let e = if parser.consume_token(&Token::Eq) {
8005+
let value = parser.parse_number()?;
8006+
EnumMember::NamedValue(name, value)
8007+
} else {
8008+
EnumMember::Name(name)
8009+
};
8010+
Ok(e)
8011+
})?;
8012+
self.expect_token(&Token::RParen)?;
8013+
8014+
Ok(values)
8015+
}
8016+
80008017
/// Parse a SQL datatype (in the context of a CREATE TABLE statement for example)
80018018
pub fn parse_data_type(&mut self) -> Result<DataType, ParserError> {
80028019
let (ty, trailing_bracket) = self.parse_data_type_helper()?;
@@ -8235,7 +8252,9 @@ impl<'a> Parser<'a> {
82358252
Keyword::BIGDECIMAL => Ok(DataType::BigDecimal(
82368253
self.parse_exact_number_optional_precision_scale()?,
82378254
)),
8238-
Keyword::ENUM => Ok(DataType::Enum(self.parse_string_values()?)),
8255+
Keyword::ENUM => Ok(DataType::Enum(self.parse_enum_values()?, None)),
8256+
Keyword::ENUM8 => Ok(DataType::Enum(self.parse_enum_values()?, Some(8))),
8257+
Keyword::ENUM16 => Ok(DataType::Enum(self.parse_enum_values()?, Some(16))),
82398258
Keyword::SET => Ok(DataType::Set(self.parse_string_values()?)),
82408259
Keyword::ARRAY => {
82418260
if dialect_of!(self is SnowflakeDialect) {

tests/sqlparser_common.rs

+84-3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ mod test_utils;
5151
use pretty_assertions::assert_eq;
5252
use sqlparser::ast::ColumnOption::Comment;
5353
use sqlparser::ast::Expr::{Identifier, UnaryOp};
54+
use sqlparser::ast::Value::Number;
5455
use sqlparser::test_utils::all_dialects_except;
5556

5657
#[test]
@@ -9250,7 +9251,7 @@ fn parse_cache_table() {
92509251
format!(
92519252
"CACHE {table_flag} TABLE '{cache_table_name}' OPTIONS('K1' = 'V1', 'K2' = 0.88) {sql}",
92529253
)
9253-
.as_str()
9254+
.as_str()
92549255
),
92559256
Statement::Cache {
92569257
table_flag: Some(ObjectName(vec![Ident::new(table_flag)])),
@@ -9275,7 +9276,7 @@ fn parse_cache_table() {
92759276
format!(
92769277
"CACHE {table_flag} TABLE '{cache_table_name}' OPTIONS('K1' = 'V1', 'K2' = 0.88) AS {sql}",
92779278
)
9278-
.as_str()
9279+
.as_str()
92799280
),
92809281
Statement::Cache {
92819282
table_flag: Some(ObjectName(vec![Ident::new(table_flag)])),
@@ -11459,7 +11460,7 @@ fn parse_explain_with_option_list() {
1145911460
}),
1146011461
},
1146111462
];
11462-
run_explain_analyze (
11463+
run_explain_analyze(
1146311464
all_dialects_where(|d| d.supports_explain_with_utility_options()),
1146411465
"EXPLAIN (ANALYZE, VERBOSE true, WAL OFF, FORMAT YAML, USER_DEF_NUM -100.1) SELECT sqrt(id) FROM foo",
1146511466
false,
@@ -12459,3 +12460,83 @@ fn parse_create_table_with_bit_types() {
1245912460
_ => unreachable!(),
1246012461
}
1246112462
}
12463+
12464+
#[test]
12465+
fn parse_create_table_with_enum_types() {
12466+
let sql = "CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = 2), bar ENUM16('a' = 1, 'b' = 2), baz ENUM('a', 'b'))";
12467+
match all_dialects().verified_stmt(sql) {
12468+
Statement::CreateTable(CreateTable { name, columns, .. }) => {
12469+
assert_eq!(name.to_string(), "t0");
12470+
assert_eq!(
12471+
vec![
12472+
ColumnDef {
12473+
name: Ident::new("foo"),
12474+
data_type: DataType::Enum(
12475+
vec![
12476+
EnumMember::NamedValue(
12477+
"a".to_string(),
12478+
Expr::Value(Number("1".parse().unwrap(), false))
12479+
),
12480+
EnumMember::NamedValue(
12481+
"b".to_string(),
12482+
Expr::Value(Number("2".parse().unwrap(), false))
12483+
)
12484+
],
12485+
Some(8)
12486+
),
12487+
collation: None,
12488+
options: vec![],
12489+
},
12490+
ColumnDef {
12491+
name: Ident::new("bar"),
12492+
data_type: DataType::Enum(
12493+
vec![
12494+
EnumMember::NamedValue(
12495+
"a".to_string(),
12496+
Expr::Value(Number("1".parse().unwrap(), false))
12497+
),
12498+
EnumMember::NamedValue(
12499+
"b".to_string(),
12500+
Expr::Value(Number("2".parse().unwrap(), false))
12501+
)
12502+
],
12503+
Some(16)
12504+
),
12505+
collation: None,
12506+
options: vec![],
12507+
},
12508+
ColumnDef {
12509+
name: Ident::new("baz"),
12510+
data_type: DataType::Enum(
12511+
vec![
12512+
EnumMember::Name("a".to_string()),
12513+
EnumMember::Name("b".to_string())
12514+
],
12515+
None
12516+
),
12517+
collation: None,
12518+
options: vec![],
12519+
}
12520+
],
12521+
columns
12522+
);
12523+
}
12524+
_ => unreachable!(),
12525+
}
12526+
12527+
// invalid case missing value for enum pair
12528+
assert_eq!(
12529+
all_dialects()
12530+
.parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = ))")
12531+
.unwrap_err(),
12532+
ParserError::ParserError("Expected: a value, found: )".to_string())
12533+
);
12534+
12535+
// invalid case that name is not a string
12536+
assert_eq!(
12537+
all_dialects()
12538+
.parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 2))")
12539+
.unwrap_err(),
12540+
ParserError::ParserError("Expected: literal string, found: 2".to_string())
12541+
);
12542+
}

tests/sqlparser_mysql.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ fn table_constraint_unique_primary_ctor(
685685
#[test]
686686
fn parse_create_table_primary_and_unique_key() {
687687
let sqls = ["UNIQUE KEY", "PRIMARY KEY"]
688-
.map(|key_ty|format!("CREATE TABLE foo (id INT PRIMARY KEY AUTO_INCREMENT, bar INT NOT NULL, CONSTRAINT bar_key {key_ty} (bar))"));
688+
.map(|key_ty| format!("CREATE TABLE foo (id INT PRIMARY KEY AUTO_INCREMENT, bar INT NOT NULL, CONSTRAINT bar_key {key_ty} (bar))"));
689689

690690
let index_type_display = [Some(KeyOrIndexDisplay::Key), None];
691691

@@ -753,7 +753,7 @@ fn parse_create_table_primary_and_unique_key() {
753753
#[test]
754754
fn parse_create_table_primary_and_unique_key_with_index_options() {
755755
let sqls = ["UNIQUE INDEX", "PRIMARY KEY"]
756-
.map(|key_ty|format!("CREATE TABLE foo (bar INT, var INT, CONSTRAINT constr {key_ty} index_name (bar, var) USING HASH COMMENT 'yes, ' USING BTREE COMMENT 'MySQL allows')"));
756+
.map(|key_ty| format!("CREATE TABLE foo (bar INT, var INT, CONSTRAINT constr {key_ty} index_name (bar, var) USING HASH COMMENT 'yes, ' USING BTREE COMMENT 'MySQL allows')"));
757757

758758
let index_type_display = [Some(KeyOrIndexDisplay::Index), None];
759759

@@ -827,7 +827,7 @@ fn parse_create_table_primary_and_unique_key_with_index_type() {
827827
#[test]
828828
fn parse_create_table_primary_and_unique_key_characteristic_test() {
829829
let sqls = ["UNIQUE INDEX", "PRIMARY KEY"]
830-
.map(|key_ty|format!("CREATE TABLE x (y INT, CONSTRAINT constr {key_ty} (y) NOT DEFERRABLE INITIALLY IMMEDIATE)"));
830+
.map(|key_ty| format!("CREATE TABLE x (y INT, CONSTRAINT constr {key_ty} (y) NOT DEFERRABLE INITIALLY IMMEDIATE)"));
831831
for sql in &sqls {
832832
mysql_and_generic().verified_stmt(sql);
833833
}
@@ -890,7 +890,13 @@ fn parse_create_table_set_enum() {
890890
},
891891
ColumnDef {
892892
name: Ident::new("baz"),
893-
data_type: DataType::Enum(vec!["a".to_string(), "b".to_string()]),
893+
data_type: DataType::Enum(
894+
vec![
895+
EnumMember::Name("a".to_string()),
896+
EnumMember::Name("b".to_string())
897+
],
898+
None
899+
),
894900
collation: None,
895901
options: vec![],
896902
}

0 commit comments

Comments
 (0)