Add support of the ENUM8|ENUM16 for ClickHouse dialect (#1574)

This commit is contained in:
hulk 2024-12-05 22:59:07 +08:00 committed by GitHub
parent c761f0babb
commit dd7ba72a0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 179 additions and 49 deletions

View file

@ -25,10 +25,21 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "visitor")] #[cfg(feature = "visitor")]
use sqlparser_derive::{Visit, VisitMut}; use sqlparser_derive::{Visit, VisitMut};
use crate::ast::{display_comma_separated, ObjectName, StructField, UnionField}; use crate::ast::{display_comma_separated, Expr, ObjectName, StructField, UnionField};
use super::{value::escape_single_quote_string, ColumnDef}; 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 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)
NamedValue(String, Expr),
}
/// SQL data types /// SQL data types
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[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 /// [clickhouse]: https://clickhouse.com/docs/en/sql-reference/data-types/nested-data-structures/nested
Nested(Vec<ColumnDef>), Nested(Vec<ColumnDef>),
/// Enums /// Enums
Enum(Vec<String>), Enum(Vec<EnumMember>, Option<u8>),
/// Set /// Set
Set(Vec<String>), Set(Vec<String>),
/// Struct /// Struct
@ -546,13 +557,24 @@ impl fmt::Display for DataType {
write!(f, "{}({})", ty, modifiers.join(", ")) write!(f, "{}({})", ty, modifiers.join(", "))
} }
} }
DataType::Enum(vals) => { DataType::Enum(vals, bits) => {
write!(f, "ENUM(")?; match bits {
Some(bits) => write!(f, "ENUM{}", bits),
None => write!(f, "ENUM"),
}?;
write!(f, "(")?;
for (i, v) in vals.iter().enumerate() { for (i, v) in vals.iter().enumerate() {
if i != 0 { if i != 0 {
write!(f, ", ")?; write!(f, ", ")?;
} }
write!(f, "'{}'", escape_single_quote_string(v))?; match v {
EnumMember::Name(name) => {
write!(f, "'{}'", escape_single_quote_string(name))?
}
EnumMember::NamedValue(name, value) => {
write!(f, "'{}' = {}", escape_single_quote_string(name), value)?
}
}
} }
write!(f, ")") write!(f, ")")
} }

View file

@ -40,7 +40,7 @@ use sqlparser_derive::{Visit, VisitMut};
use crate::tokenizer::Span; use crate::tokenizer::Span;
pub use self::data_type::{ pub use self::data_type::{
ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, EnumMember, ExactNumberInfo,
StructBracketKind, TimezoneInfo, StructBracketKind, TimezoneInfo,
}; };
pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue, Use}; pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue, Use};

View file

@ -286,6 +286,8 @@ define_keywords!(
ENFORCED, ENFORCED,
ENGINE, ENGINE,
ENUM, ENUM,
ENUM16,
ENUM8,
EPHEMERAL, EPHEMERAL,
EPOCH, EPOCH,
EQUALS, EQUALS,

View file

@ -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}"))) return Err(ParserError::ParserError(format!("Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO. {e}")))
} }
} }
}, }
}; };
Ok(owner) Ok(owner)
} }
@ -7997,6 +7997,23 @@ impl<'a> Parser<'a> {
} }
} }
pub fn parse_enum_values(&mut self) -> Result<Vec<EnumMember>, ParserError> {
self.expect_token(&Token::LParen)?;
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)
}
/// Parse a SQL datatype (in the context of a CREATE TABLE statement for example) /// Parse a SQL datatype (in the context of a CREATE TABLE statement for example)
pub fn parse_data_type(&mut self) -> Result<DataType, ParserError> { pub fn parse_data_type(&mut self) -> Result<DataType, ParserError> {
let (ty, trailing_bracket) = self.parse_data_type_helper()?; let (ty, trailing_bracket) = self.parse_data_type_helper()?;
@ -8235,7 +8252,9 @@ impl<'a> Parser<'a> {
Keyword::BIGDECIMAL => Ok(DataType::BigDecimal( Keyword::BIGDECIMAL => Ok(DataType::BigDecimal(
self.parse_exact_number_optional_precision_scale()?, 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::SET => Ok(DataType::Set(self.parse_string_values()?)),
Keyword::ARRAY => { Keyword::ARRAY => {
if dialect_of!(self is SnowflakeDialect) { if dialect_of!(self is SnowflakeDialect) {

View file

@ -51,6 +51,7 @@ mod test_utils;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use sqlparser::ast::ColumnOption::Comment; use sqlparser::ast::ColumnOption::Comment;
use sqlparser::ast::Expr::{Identifier, UnaryOp}; use sqlparser::ast::Expr::{Identifier, UnaryOp};
use sqlparser::ast::Value::Number;
use sqlparser::test_utils::all_dialects_except; use sqlparser::test_utils::all_dialects_except;
#[test] #[test]
@ -12459,3 +12460,83 @@ fn parse_create_table_with_bit_types() {
_ => unreachable!(), _ => unreachable!(),
} }
} }
#[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, .. }) => {
assert_eq!(name.to_string(), "t0");
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
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
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())
);
}

View file

@ -890,7 +890,13 @@ fn parse_create_table_set_enum() {
}, },
ColumnDef { ColumnDef {
name: Ident::new("baz"), name: Ident::new("baz"),
data_type: DataType::Enum(vec!["a".to_string(), "b".to_string()]), data_type: DataType::Enum(
vec![
EnumMember::Name("a".to_string()),
EnumMember::Name("b".to_string())
],
None
),
collation: None, collation: None,
options: vec![], options: vec![],
} }