From e05eb87b7c7db52c7f0fb4533259c61e54be958c Mon Sep 17 00:00:00 2001 From: git-hulk Date: Sat, 30 Nov 2024 23:25:41 +0800 Subject: [PATCH 1/3] Add support of the ENUM8|ENUM16 for ClickHouse dialect For the documentation, please refer to: https://clickhouse.com/docs/en/sql-reference/data-types/enum --- src/ast/data_type.rs | 30 +++++++++-- src/ast/mod.rs | 2 +- src/dialect/clickhouse.rs | 6 +++ src/dialect/mod.rs | 5 ++ src/keywords.rs | 2 + src/parser/mod.rs | 95 ++++++++++++++++++++++------------- tests/sqlparser_clickhouse.rs | 80 +++++++++++++++++++++++++++++ tests/sqlparser_mysql.rs | 14 ++++-- 8 files changed, 188 insertions(+), 46 deletions(-) diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index ccca7f4cb..1d5aeaf5b 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -25,10 +25,21 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "visitor")] use sqlparser_derive::{Visit, VisitMut}; -use crate::ast::{display_comma_separated, ObjectName, StructField, UnionField}; +use crate::ast::{display_comma_separated, ObjectName, StructField, UnionField, Value}; use super::{value::escape_single_quote_string, ColumnDef}; +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum EnumValue { + String(String), + /// ClickHouse allows to specify an integer value for each enum value. + /// + /// [clickhouse](https://clickhouse.com/docs/en/sql-reference/data-types/enum) + Pair(String, Value), +} + /// SQL data types #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -334,7 +345,7 @@ pub enum DataType { /// [clickhouse]: https://clickhouse.com/docs/en/sql-reference/data-types/nested-data-structures/nested Nested(Vec), /// Enums - Enum(Vec), + Enum(Vec, Option), /// Set Set(Vec), /// Struct @@ -546,13 +557,22 @@ impl fmt::Display for DataType { write!(f, "{}({})", ty, modifiers.join(", ")) } } - DataType::Enum(vals) => { - write!(f, "ENUM(")?; + DataType::Enum(vals, bits) => { + match bits { + Some(bits) => write!(f, "ENUM{}", bits), + None => write!(f, "ENUM"), + }?; + write!(f, "(")?; for (i, v) in vals.iter().enumerate() { if i != 0 { write!(f, ", ")?; } - write!(f, "'{}'", escape_single_quote_string(v))?; + match v { + EnumValue::String(v) => write!(f, "'{}'", escape_single_quote_string(v))?, + EnumValue::Pair(v, i) => { + write!(f, "'{}' = {}", escape_single_quote_string(v), i)? + } + } } write!(f, ")") } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 326375b5f..54533708b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -40,7 +40,7 @@ use sqlparser_derive::{Visit, VisitMut}; use crate::tokenizer::Span; pub use self::data_type::{ - ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, + ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, EnumValue, ExactNumberInfo, StructBracketKind, TimezoneInfo, }; pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue, Use}; diff --git a/src/dialect/clickhouse.rs b/src/dialect/clickhouse.rs index 0c8f08040..05fd04807 100644 --- a/src/dialect/clickhouse.rs +++ b/src/dialect/clickhouse.rs @@ -50,4 +50,10 @@ impl Dialect for ClickHouseDialect { fn supports_limit_comma(&self) -> bool { true } + + /// ClickHouse supports `Enum8` and `Enum16` types. + /// See [ClickHouse](https://clickhouse.com/docs/en/sql-reference/data-types/enum) + fn supports_enum_type_with_bits(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index f40cba719..145f082f8 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -707,6 +707,11 @@ pub trait Dialect: Debug + Any { fn is_reserved_for_identifier(&self, kw: Keyword) -> bool { keywords::RESERVED_FOR_IDENTIFIER.contains(&kw) } + + /// Return true if the dialect supports the Enum type with bits like Enum8, Enum16 + fn supports_enum_type_with_bits(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/keywords.rs b/src/keywords.rs index e00e26a62..be3910f8f 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -286,6 +286,8 @@ define_keywords!( ENFORCED, ENGINE, ENUM, + ENUM16, + ENUM8, EPHEMERAL, EPOCH, EQUALS, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7b175f1d9..38204ddb6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1049,18 +1049,18 @@ impl<'a> Parser<'a> { | Keyword::CURRENT_USER | Keyword::SESSION_USER | Keyword::USER - if dialect_of!(self is PostgreSqlDialect | GenericDialect) => - { - Ok(Some(Expr::Function(Function { - name: ObjectName(vec![w.to_ident(w_span)]), - parameters: FunctionArguments::None, - args: FunctionArguments::None, - null_treatment: None, - filter: None, - over: None, - within_group: vec![], - }))) - } + if dialect_of!(self is PostgreSqlDialect | GenericDialect) => + { + Ok(Some(Expr::Function(Function { + name: ObjectName(vec![w.to_ident(w_span)]), + parameters: FunctionArguments::None, + args: FunctionArguments::None, + null_treatment: None, + filter: None, + over: None, + within_group: vec![], + }))) + } Keyword::CURRENT_TIMESTAMP | Keyword::CURRENT_TIME | Keyword::CURRENT_DATE @@ -1075,18 +1075,18 @@ impl<'a> Parser<'a> { Keyword::TRY_CAST => Ok(Some(self.parse_cast_expr(CastKind::TryCast)?)), Keyword::SAFE_CAST => Ok(Some(self.parse_cast_expr(CastKind::SafeCast)?)), Keyword::EXISTS - // Support parsing Databricks has a function named `exists`. - if !dialect_of!(self is DatabricksDialect) - || matches!( + // Support parsing Databricks has a function named `exists`. + if !dialect_of!(self is DatabricksDialect) + || matches!( self.peek_nth_token(1).token, Token::Word(Word { keyword: Keyword::SELECT | Keyword::WITH, .. }) ) => - { - Ok(Some(self.parse_exists_expr(false)?)) - } + { + Ok(Some(self.parse_exists_expr(false)?)) + } Keyword::EXTRACT => Ok(Some(self.parse_extract_expr()?)), Keyword::CEIL => Ok(Some(self.parse_ceil_floor_expr(true)?)), Keyword::FLOOR => Ok(Some(self.parse_ceil_floor_expr(false)?)), @@ -1103,22 +1103,22 @@ impl<'a> Parser<'a> { Ok(Some(self.parse_array_expr(true)?)) } Keyword::ARRAY - if self.peek_token() == Token::LParen - && !dialect_of!(self is ClickHouseDialect | DatabricksDialect) => - { - self.expect_token(&Token::LParen)?; - let query = self.parse_query()?; - self.expect_token(&Token::RParen)?; - Ok(Some(Expr::Function(Function { - name: ObjectName(vec![w.to_ident(w_span)]), - parameters: FunctionArguments::None, - args: FunctionArguments::Subquery(query), - filter: None, - null_treatment: None, - over: None, - within_group: vec![], - }))) - } + if self.peek_token() == Token::LParen + && !dialect_of!(self is ClickHouseDialect | DatabricksDialect) => + { + self.expect_token(&Token::LParen)?; + let query = self.parse_query()?; + self.expect_token(&Token::RParen)?; + Ok(Some(Expr::Function(Function { + name: ObjectName(vec![w.to_ident(w_span)]), + parameters: FunctionArguments::None, + args: FunctionArguments::Subquery(query), + filter: None, + null_treatment: None, + over: None, + within_group: vec![], + }))) + } Keyword::NOT => Ok(Some(self.parse_not()?)), Keyword::MATCH if dialect_of!(self is MySqlDialect | GenericDialect) => { Ok(Some(self.parse_match_against()?)) @@ -5023,7 +5023,7 @@ impl<'a> Parser<'a> { return Err(ParserError::ParserError(format!("Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO. {e}"))) } } - }, + } }; Ok(owner) } @@ -7997,6 +7997,27 @@ impl<'a> Parser<'a> { } } + pub fn parse_enum_values(&mut self) -> Result, ParserError> { + self.expect_token(&Token::LParen)?; + let values = self.parse_comma_separated(Parser::parse_enum_value)?; + self.expect_token(&Token::RParen)?; + Ok(values) + } + + pub fn parse_enum_value(&mut self) -> Result { + let str = self.parse_literal_string()?; + let value = match self.peek_token().token { + Token::Eq => { + // Consume the `=` token + self.next_token(); + let value = self.parse_number_value()?; + EnumValue::Pair(str, value) + } + _ => EnumValue::String(str), + }; + Ok(value) + } + /// Parse a SQL datatype (in the context of a CREATE TABLE statement for example) pub fn parse_data_type(&mut self) -> Result { let (ty, trailing_bracket) = self.parse_data_type_helper()?; @@ -8235,7 +8256,9 @@ impl<'a> Parser<'a> { Keyword::BIGDECIMAL => Ok(DataType::BigDecimal( self.parse_exact_number_optional_precision_scale()?, )), - Keyword::ENUM => Ok(DataType::Enum(self.parse_string_values()?)), + Keyword::ENUM => Ok(DataType::Enum(self.parse_enum_values()?, None)), + Keyword::ENUM8 => Ok(DataType::Enum(self.parse_enum_values()?, Some(8))), + Keyword::ENUM16 => Ok(DataType::Enum(self.parse_enum_values()?, Some(16))), Keyword::SET => Ok(DataType::Set(self.parse_string_values()?)), Keyword::ARRAY => { if dialect_of!(self is SnowflakeDialect) { diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index ed0c74021..546d904ae 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -1621,6 +1621,86 @@ fn parse_explain_table() { } } +#[test] +fn parse_create_table_with_enum_types() { + let sql = "CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = 2), bar ENUM16('a' = 1, 'b' = 2), baz ENUM('a', 'b'))"; + match clickhouse().verified_stmt(sql) { + Statement::CreateTable(CreateTable { name, columns, .. }) => { + assert_eq!(name.to_string(), "t0"); + assert_eq!( + vec![ + ColumnDef { + name: Ident::new("foo"), + data_type: DataType::Enum( + vec![ + EnumValue::Pair( + "a".to_string(), + Number("1".parse().unwrap(), false) + ), + EnumValue::Pair( + "b".to_string(), + Number("2".parse().unwrap(), false) + ) + ], + Some(8) + ), + collation: None, + options: vec![], + }, + ColumnDef { + name: Ident::new("bar"), + data_type: DataType::Enum( + vec![ + EnumValue::Pair( + "a".to_string(), + Number("1".parse().unwrap(), false) + ), + EnumValue::Pair( + "b".to_string(), + Number("2".parse().unwrap(), false) + ) + ], + Some(16) + ), + collation: None, + options: vec![], + }, + ColumnDef { + name: Ident::new("baz"), + data_type: DataType::Enum( + vec![ + EnumValue::String("a".to_string()), + EnumValue::String("b".to_string()) + ], + None + ), + collation: None, + options: vec![], + } + ], + columns + ); + } + _ => unreachable!(), + } + + // invalid case missing value for enum pair + assert_eq!( + clickhouse_and_generic() + .parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = ))") + .unwrap_err(), + ParserError("Expected: a value, found: )".to_string()) + ); + + // invalid case that name is not a string + assert_eq!( + clickhouse_and_generic() + .parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 2))") + .unwrap_err(), + ParserError("Expected: literal string, found: 2".to_string()) + ); +} + fn clickhouse() -> TestedDialects { TestedDialects::new(vec![Box::new(ClickHouseDialect {})]) } diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index f7a21f99b..1f87395e0 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -685,7 +685,7 @@ fn table_constraint_unique_primary_ctor( #[test] fn parse_create_table_primary_and_unique_key() { let sqls = ["UNIQUE KEY", "PRIMARY KEY"] - .map(|key_ty|format!("CREATE TABLE foo (id INT PRIMARY KEY AUTO_INCREMENT, bar INT NOT NULL, CONSTRAINT bar_key {key_ty} (bar))")); + .map(|key_ty| format!("CREATE TABLE foo (id INT PRIMARY KEY AUTO_INCREMENT, bar INT NOT NULL, CONSTRAINT bar_key {key_ty} (bar))")); let index_type_display = [Some(KeyOrIndexDisplay::Key), None]; @@ -753,7 +753,7 @@ fn parse_create_table_primary_and_unique_key() { #[test] fn parse_create_table_primary_and_unique_key_with_index_options() { let sqls = ["UNIQUE INDEX", "PRIMARY KEY"] - .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')")); + .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')")); let index_type_display = [Some(KeyOrIndexDisplay::Index), None]; @@ -827,7 +827,7 @@ fn parse_create_table_primary_and_unique_key_with_index_type() { #[test] fn parse_create_table_primary_and_unique_key_characteristic_test() { let sqls = ["UNIQUE INDEX", "PRIMARY KEY"] - .map(|key_ty|format!("CREATE TABLE x (y INT, CONSTRAINT constr {key_ty} (y) NOT DEFERRABLE INITIALLY IMMEDIATE)")); + .map(|key_ty| format!("CREATE TABLE x (y INT, CONSTRAINT constr {key_ty} (y) NOT DEFERRABLE INITIALLY IMMEDIATE)")); for sql in &sqls { mysql_and_generic().verified_stmt(sql); } @@ -890,7 +890,13 @@ fn parse_create_table_set_enum() { }, ColumnDef { name: Ident::new("baz"), - data_type: DataType::Enum(vec!["a".to_string(), "b".to_string()]), + data_type: DataType::Enum( + vec![ + EnumValue::String("a".to_string()), + EnumValue::String("b".to_string()) + ], + None + ), collation: None, options: vec![], } From 87bfbce60b90911bac2d4ba6dc1c61c7e06890bd Mon Sep 17 00:00:00 2001 From: git-hulk Date: Sun, 1 Dec 2024 21:21:29 +0800 Subject: [PATCH 2/3] Fix review comments --- src/ast/data_type.rs | 18 ++++---- src/ast/mod.rs | 2 +- src/dialect/clickhouse.rs | 6 --- src/dialect/mod.rs | 5 -- src/parser/mod.rs | 28 +++++------- tests/sqlparser_clickhouse.rs | 80 -------------------------------- tests/sqlparser_common.rs | 86 +++++++++++++++++++++++++++++++++-- tests/sqlparser_mysql.rs | 4 +- 8 files changed, 108 insertions(+), 121 deletions(-) diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index 1d5aeaf5b..5b0239e17 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -25,19 +25,19 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "visitor")] use sqlparser_derive::{Visit, VisitMut}; -use crate::ast::{display_comma_separated, ObjectName, StructField, UnionField, Value}; +use crate::ast::{display_comma_separated, Expr, ObjectName, StructField, UnionField}; use super::{value::escape_single_quote_string, ColumnDef}; #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub enum EnumValue { - String(String), +pub enum EnumMember { + Name(String), /// ClickHouse allows to specify an integer value for each enum value. /// /// [clickhouse](https://clickhouse.com/docs/en/sql-reference/data-types/enum) - Pair(String, Value), + NamedValue(String, Expr), } /// SQL data types @@ -345,7 +345,7 @@ pub enum DataType { /// [clickhouse]: https://clickhouse.com/docs/en/sql-reference/data-types/nested-data-structures/nested Nested(Vec), /// Enums - Enum(Vec, Option), + Enum(Vec, Option), /// Set Set(Vec), /// Struct @@ -568,9 +568,11 @@ impl fmt::Display for DataType { write!(f, ", ")?; } match v { - EnumValue::String(v) => write!(f, "'{}'", escape_single_quote_string(v))?, - EnumValue::Pair(v, i) => { - write!(f, "'{}' = {}", escape_single_quote_string(v), i)? + EnumMember::Name(name) => { + write!(f, "'{}'", escape_single_quote_string(name))? + } + EnumMember::NamedValue(name, value) => { + write!(f, "'{}' = {}", escape_single_quote_string(name), value)? } } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 54533708b..f782b3634 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -40,7 +40,7 @@ use sqlparser_derive::{Visit, VisitMut}; use crate::tokenizer::Span; pub use self::data_type::{ - ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, EnumValue, ExactNumberInfo, + ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, EnumMember, ExactNumberInfo, StructBracketKind, TimezoneInfo, }; pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue, Use}; diff --git a/src/dialect/clickhouse.rs b/src/dialect/clickhouse.rs index 05fd04807..0c8f08040 100644 --- a/src/dialect/clickhouse.rs +++ b/src/dialect/clickhouse.rs @@ -50,10 +50,4 @@ impl Dialect for ClickHouseDialect { fn supports_limit_comma(&self) -> bool { true } - - /// ClickHouse supports `Enum8` and `Enum16` types. - /// See [ClickHouse](https://clickhouse.com/docs/en/sql-reference/data-types/enum) - fn supports_enum_type_with_bits(&self) -> bool { - true - } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 145f082f8..f40cba719 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -707,11 +707,6 @@ pub trait Dialect: Debug + Any { fn is_reserved_for_identifier(&self, kw: Keyword) -> bool { keywords::RESERVED_FOR_IDENTIFIER.contains(&kw) } - - /// Return true if the dialect supports the Enum type with bits like Enum8, Enum16 - fn supports_enum_type_with_bits(&self) -> bool { - false - } } /// This represents the operators for which precedence must be defined diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 38204ddb6..04a103c61 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -7997,25 +7997,21 @@ impl<'a> Parser<'a> { } } - pub fn parse_enum_values(&mut self) -> Result, ParserError> { + pub fn parse_enum_values(&mut self) -> Result, ParserError> { self.expect_token(&Token::LParen)?; - let values = self.parse_comma_separated(Parser::parse_enum_value)?; + let values = self.parse_comma_separated(|parser| { + let name = parser.parse_literal_string()?; + let e = if parser.consume_token(&Token::Eq) { + let value = parser.parse_number()?; + EnumMember::NamedValue(name, value) + } else { + EnumMember::Name(name) + }; + Ok(e) + })?; self.expect_token(&Token::RParen)?; - Ok(values) - } - pub fn parse_enum_value(&mut self) -> Result { - let str = self.parse_literal_string()?; - let value = match self.peek_token().token { - Token::Eq => { - // Consume the `=` token - self.next_token(); - let value = self.parse_number_value()?; - EnumValue::Pair(str, value) - } - _ => EnumValue::String(str), - }; - Ok(value) + Ok(values) } /// Parse a SQL datatype (in the context of a CREATE TABLE statement for example) diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 546d904ae..ed0c74021 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -1621,86 +1621,6 @@ fn parse_explain_table() { } } -#[test] -fn parse_create_table_with_enum_types() { - let sql = "CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = 2), bar ENUM16('a' = 1, 'b' = 2), baz ENUM('a', 'b'))"; - match clickhouse().verified_stmt(sql) { - Statement::CreateTable(CreateTable { name, columns, .. }) => { - assert_eq!(name.to_string(), "t0"); - assert_eq!( - vec![ - ColumnDef { - name: Ident::new("foo"), - data_type: DataType::Enum( - vec![ - EnumValue::Pair( - "a".to_string(), - Number("1".parse().unwrap(), false) - ), - EnumValue::Pair( - "b".to_string(), - Number("2".parse().unwrap(), false) - ) - ], - Some(8) - ), - collation: None, - options: vec![], - }, - ColumnDef { - name: Ident::new("bar"), - data_type: DataType::Enum( - vec![ - EnumValue::Pair( - "a".to_string(), - Number("1".parse().unwrap(), false) - ), - EnumValue::Pair( - "b".to_string(), - Number("2".parse().unwrap(), false) - ) - ], - Some(16) - ), - collation: None, - options: vec![], - }, - ColumnDef { - name: Ident::new("baz"), - data_type: DataType::Enum( - vec![ - EnumValue::String("a".to_string()), - EnumValue::String("b".to_string()) - ], - None - ), - collation: None, - options: vec![], - } - ], - columns - ); - } - _ => unreachable!(), - } - - // invalid case missing value for enum pair - assert_eq!( - clickhouse_and_generic() - .parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = ))") - .unwrap_err(), - ParserError("Expected: a value, found: )".to_string()) - ); - - // invalid case that name is not a string - assert_eq!( - clickhouse_and_generic() - .parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 2))") - .unwrap_err(), - ParserError("Expected: literal string, found: 2".to_string()) - ); -} - fn clickhouse() -> TestedDialects { TestedDialects::new(vec![Box::new(ClickHouseDialect {})]) } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e80223807..cd0cf81fc 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -51,6 +51,7 @@ mod test_utils; use pretty_assertions::assert_eq; use sqlparser::ast::ColumnOption::Comment; use sqlparser::ast::Expr::{Identifier, UnaryOp}; +use sqlparser::ast::Value::Number; use sqlparser::test_utils::all_dialects_except; #[test] @@ -9250,7 +9251,7 @@ fn parse_cache_table() { format!( "CACHE {table_flag} TABLE '{cache_table_name}' OPTIONS('K1' = 'V1', 'K2' = 0.88) {sql}", ) - .as_str() + .as_str() ), Statement::Cache { table_flag: Some(ObjectName(vec![Ident::new(table_flag)])), @@ -9275,7 +9276,7 @@ fn parse_cache_table() { format!( "CACHE {table_flag} TABLE '{cache_table_name}' OPTIONS('K1' = 'V1', 'K2' = 0.88) AS {sql}", ) - .as_str() + .as_str() ), Statement::Cache { table_flag: Some(ObjectName(vec![Ident::new(table_flag)])), @@ -11459,7 +11460,7 @@ fn parse_explain_with_option_list() { }), }, ]; - run_explain_analyze ( + run_explain_analyze( all_dialects_where(|d| d.supports_explain_with_utility_options()), "EXPLAIN (ANALYZE, VERBOSE true, WAL OFF, FORMAT YAML, USER_DEF_NUM -100.1) SELECT sqrt(id) FROM foo", false, @@ -12459,3 +12460,82 @@ fn parse_create_table_with_bit_types() { _ => unreachable!(), } } + +fn parse_create_table_with_enum_types() { + let sql = "CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = 2), bar ENUM16('a' = 1, 'b' = 2), baz ENUM('a', 'b'))"; + match all_dialects().verified_stmt(sql) { + Statement::CreateTable(CreateTable { name, columns, .. }) => { + std::assert_eq!(name.to_string(), "t0"); + std::assert_eq!( + vec![ + ColumnDef { + name: Ident::new("foo"), + data_type: DataType::Enum( + vec![ + EnumMember::NamedValue( + "a".to_string(), + Expr::Value(Number("1".parse().unwrap(), false)) + ), + EnumMember::NamedValue( + "b".to_string(), + Expr::Value(Number("2".parse().unwrap(), false)) + ) + ], + Some(8) + ), + collation: None, + options: vec![], + }, + ColumnDef { + name: Ident::new("bar"), + data_type: DataType::Enum( + vec![ + EnumMember::NamedValue( + "a".to_string(), + Expr::Value(Number("1".parse().unwrap(), false)) + ), + EnumMember::NamedValue( + "b".to_string(), + Expr::Value(Number("2".parse().unwrap(), false)) + ) + ], + Some(16) + ), + collation: None, + options: vec![], + }, + ColumnDef { + name: Ident::new("baz"), + data_type: DataType::Enum( + vec![ + EnumMember::Name("a".to_string()), + EnumMember::Name("b".to_string()) + ], + None + ), + collation: None, + options: vec![], + } + ], + columns + ); + } + _ => unreachable!(), + } + + // invalid case missing value for enum pair + std::assert_eq!( + all_dialects() + .parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = ))") + .unwrap_err(), + ParserError::ParserError("Expected: a value, found: )".to_string()) + ); + + // invalid case that name is not a string + std::assert_eq!( + all_dialects() + .parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 2))") + .unwrap_err(), + ParserError::ParserError("Expected: literal string, found: 2".to_string()) + ); +} diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 1f87395e0..cac1af852 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -892,8 +892,8 @@ fn parse_create_table_set_enum() { name: Ident::new("baz"), data_type: DataType::Enum( vec![ - EnumValue::String("a".to_string()), - EnumValue::String("b".to_string()) + EnumMember::Name("a".to_string()), + EnumMember::Name("b".to_string()) ], None ), From db938606bb5a7be194e849f9b8837d90c75c035d Mon Sep 17 00:00:00 2001 From: git-hulk Date: Wed, 4 Dec 2024 11:03:24 +0800 Subject: [PATCH 3/3] Remove std prefix --- tests/sqlparser_common.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index cd0cf81fc..61c742dac 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -12461,12 +12461,13 @@ fn parse_create_table_with_bit_types() { } } +#[test] fn parse_create_table_with_enum_types() { let sql = "CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = 2), bar ENUM16('a' = 1, 'b' = 2), baz ENUM('a', 'b'))"; match all_dialects().verified_stmt(sql) { Statement::CreateTable(CreateTable { name, columns, .. }) => { - std::assert_eq!(name.to_string(), "t0"); - std::assert_eq!( + assert_eq!(name.to_string(), "t0"); + assert_eq!( vec![ ColumnDef { name: Ident::new("foo"), @@ -12524,7 +12525,7 @@ fn parse_create_table_with_enum_types() { } // invalid case missing value for enum pair - std::assert_eq!( + assert_eq!( all_dialects() .parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = ))") .unwrap_err(), @@ -12532,7 +12533,7 @@ fn parse_create_table_with_enum_types() { ); // invalid case that name is not a string - std::assert_eq!( + assert_eq!( all_dialects() .parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 2))") .unwrap_err(),