Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions src/ast/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "visitor")]
use sqlparser_derive::{Visit, VisitMut};

use crate::ast::{display_comma_separated, Expr, ObjectName, StructField, UnionField};
use crate::ast::{display_comma_separated, Expr, Ident, ObjectName, StructField, UnionField};

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

Expand All @@ -42,6 +42,29 @@ pub enum EnumMember {
NamedValue(String, Expr),
}

/// One entry of a custom data type's parenthesized modifier list.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum TypeModifier {
/// Identifier or keyword segment, quotes carried by `Ident`.
Identifier(Ident),
/// Numeric literal, the `bool` carries the `L` long suffix like `Value::Number`.
Number(String, bool),
/// Single quoted string literal.
String(String),
}

impl fmt::Display for TypeModifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Identifier(id) => write!(f, "{id}"),
Self::Number(n, long) => write!(f, "{}{long}", n, long = if *long { "L" } else { "" }),
Self::String(s) => write!(f, "'{}'", escape_single_quote_string(s)),
}
}
}

/// SQL data types
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -441,8 +464,8 @@ pub enum DataType {
///
/// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html
VarBit(Option<u64>),
/// Custom types.
Custom(ObjectName, Vec<String>),
/// Custom type with a modifier list, e.g. `GEOMETRY(POINT, 4326)`.
Custom(ObjectName, Vec<TypeModifier>),
/// Arrays.
Array(ArrayElemTypeDef),
/// Map, see [ClickHouse], [Hive].
Expand Down Expand Up @@ -744,7 +767,7 @@ impl fmt::Display for DataType {
if modifiers.is_empty() {
write!(f, "{ty}")
} else {
write!(f, "{}({})", ty, modifiers.join(", "))
write!(f, "{ty}({})", display_comma_separated(modifiers))
}
}
DataType::Enum(vals, bits) => {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ use crate::{

pub use self::data_type::{
ArrayElemTypeDef, BinaryLength, CharLengthUnits, CharacterLength, DataType, EnumMember,
ExactNumberInfo, IntervalFields, MapBracketKind, StructBracketKind, TimezoneInfo,
ExactNumberInfo, IntervalFields, MapBracketKind, StructBracketKind, TimezoneInfo, TypeModifier,
};
pub use self::dcl::{
AlterRoleOperation, CreateRole, Grant, ResetConfig, Revoke, RoleOption, SecondaryRoles,
Expand Down
22 changes: 15 additions & 7 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14627,15 +14627,19 @@ impl<'a> Parser<'a> {
}

/// Parse optional type modifiers appearing in parentheses e.g. `(UNSIGNED, ZEROFILL)`.
pub fn parse_optional_type_modifiers(&mut self) -> Result<Option<Vec<String>>, ParserError> {
pub fn parse_optional_type_modifiers(
&mut self,
) -> Result<Option<Vec<TypeModifier>>, ParserError> {
if self.consume_token(&Token::LParen) {
let mut modifiers = Vec::new();
loop {
let next_token = self.next_token();
match next_token.token {
Token::Word(w) => modifiers.push(w.to_string()),
Token::Number(n, _) => modifiers.push(n),
Token::SingleQuotedString(s) => modifiers.push(s),
Token::Word(w) => {
modifiers.push(TypeModifier::Identifier(w.to_ident(next_token.span)))
}
Token::Number(n, long) => modifiers.push(TypeModifier::Number(n, long)),
Token::SingleQuotedString(s) => modifiers.push(TypeModifier::String(s)),

Token::Comma => {
continue;
Expand Down Expand Up @@ -21791,7 +21795,8 @@ mod tests {
#[cfg(test)]
mod test_parse_data_type {
use crate::ast::{
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo,
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, Ident, ObjectName,
TimezoneInfo, TypeModifier,
};
use crate::dialect::{AnsiDialect, GenericDialect, PostgreSqlDialect};
use crate::test_utils::TestedDialects;
Expand Down Expand Up @@ -21982,7 +21987,7 @@ mod tests {
"GEOMETRY(POINT)",
DataType::Custom(
ObjectName::from(vec!["GEOMETRY".into()]),
vec!["POINT".to_string()]
vec![TypeModifier::Identifier(Ident::new("POINT"))]
)
);

Expand All @@ -21991,7 +21996,10 @@ mod tests {
"GEOMETRY(POINT, 4326)",
DataType::Custom(
ObjectName::from(vec!["GEOMETRY".into()]),
vec!["POINT".to_string(), "4326".to_string()]
vec![
TypeModifier::Identifier(Ident::new("POINT")),
TypeModifier::Number("4326".to_string(), false),
]
)
);
}
Expand Down
26 changes: 18 additions & 8 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1990,7 +1990,7 @@ fn parse_object_type_parameter() {
columns[0].data_type,
DataType::Custom(
ObjectName::from(vec![Ident::new("Object")]),
vec!["json".to_string()]
vec![TypeModifier::String("json".to_string())]
)
);

Expand All @@ -2001,13 +2001,26 @@ fn parse_object_type_parameter() {
);
}

clickhouse_and_generic().verified_stmt("CREATE TABLE t (o Object('json'))");
clickhouse_and_generic().verified_stmt("CREATE TABLE t (o OBJECT())");
clickhouse_and_generic().verified_stmt("CREATE TABLE t (o OBJECT(city VARCHAR NOT NULL))");

for (sql, modifiers) in [
("CREATE TABLE t (o OBJECT(10))", vec!["10"]),
("CREATE TABLE t (o OBJECT(foo))", vec!["foo"]),
("CREATE TABLE t (o OBJECT(foo, bar))", vec!["foo", "bar"]),
(
"CREATE TABLE t (o OBJECT(10))",
vec![TypeModifier::Number("10".to_string(), false)],
),
(
"CREATE TABLE t (o OBJECT(foo))",
vec![TypeModifier::Identifier(Ident::new("foo"))],
),
(
"CREATE TABLE t (o OBJECT(foo, bar))",
vec![
TypeModifier::Identifier(Ident::new("foo")),
TypeModifier::Identifier(Ident::new("bar")),
],
),
] {
for dialects in [
clickhouse(),
Expand All @@ -2023,10 +2036,7 @@ fn parse_object_type_parameter() {
columns[0].data_type,
DataType::Custom(
ObjectName::from(vec![Ident::new("OBJECT")]),
modifiers
.iter()
.map(|modifier| (*modifier).to_owned())
.collect(),
modifiers.clone()
)
);

Expand Down
34 changes: 33 additions & 1 deletion tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6953,7 +6953,7 @@ fn parse_text_type_modifier_double_colon_cast() {
expr: Box::new(Expr::Identifier(Ident::new("ID"))),
data_type: DataType::Custom(
ObjectName::from(vec![Ident::new("TEXT")]),
vec!["16777216".to_string()]
vec![TypeModifier::Number("16777216".to_string(), false)]
),
format: None,
}
Expand Down Expand Up @@ -21022,3 +21022,35 @@ fn parse_window_spec_keyword_base_window_name() {
"SELECT count(*) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t",
);
}

#[test]
fn parse_custom_type_modifier_spellings() {
for sql in [
r#"CREATE TABLE t (c foo('a b'))"#,
r#"CREATE TABLE t (c foo(')'))"#,
r#"CREATE TABLE t (c foo('it''s'))"#,
"CREATE TABLE t (c foo(bar, 9))",
] {
all_dialects().verified_stmt(sql);
}
let generic = TestedDialects::new(vec![Box::new(GenericDialect {})]);
generic.verified_stmt(r#"CREATE TABLE t (c foo("quoted"))"#);
generic.verified_stmt("CREATE TABLE t (c foo(9L))");

let statements = generic
.parse_sql_statements("CREATE TABLE t (c foo(bar, 9, 'z', 42L))")
.unwrap();
let Statement::CreateTable(create_table) = &statements[0] else {
panic!("expected CREATE TABLE")
};
let DataType::Custom(_, modifiers) = &create_table.columns[0].data_type else {
panic!("expected a custom type")
};
let expected = vec![
TypeModifier::Identifier(Ident::new("bar")),
TypeModifier::Number("9".to_string(), false),
TypeModifier::String("z".to_string()),
TypeModifier::Number("42".to_string(), true),
];
assert_eq!(modifiers, &expected);
}
Loading