diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index b515c261..3e428e21 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -715,3 +715,43 @@ impl fmt::Display for ReferentialAction { }) } } + +/// SQL user defined type definition +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum UserDefinedTypeRepresentation { + Composite { + attributes: Vec, + }, +} + +impl fmt::Display for UserDefinedTypeRepresentation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UserDefinedTypeRepresentation::Composite { attributes } => { + write!(f, "({})", display_comma_separated(attributes)) + } + } + } +} + +/// SQL user defined type attribute definition +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct UserDefinedTypeCompositeAttributeDef { + pub name: Ident, + pub data_type: DataType, + pub collation: Option, +} + +impl fmt::Display for UserDefinedTypeCompositeAttributeDef { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.name, self.data_type)?; + if let Some(collation) = &self.collation { + write!(f, " COLLATE {collation}")?; + } + Ok(()) + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index ee3366a2..7f3b4742 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -31,6 +31,7 @@ pub use self::data_type::{ pub use self::ddl::{ AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, GeneratedAs, IndexType, KeyOrIndexDisplay, ReferentialAction, TableConstraint, + UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, }; pub use self::operator::{BinaryOperator, UnaryOperator}; pub use self::query::{ @@ -1711,6 +1712,11 @@ pub enum Statement { sequence_options: Vec, owned_by: Option, }, + /// CREATE TYPE `` + CreateType { + name: ObjectName, + representation: UserDefinedTypeRepresentation, + }, } impl fmt::Display for Statement { @@ -2921,6 +2927,12 @@ impl fmt::Display for Statement { } Ok(()) } + Statement::CreateType { + name, + representation, + } => { + write!(f, "CREATE TYPE {name} AS {representation}") + } } } } diff --git a/src/parser.rs b/src/parser.rs index bab58145..b89077fb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2365,6 +2365,8 @@ impl<'a> Parser<'a> { self.parse_create_role() } else if self.parse_keyword(Keyword::SEQUENCE) { self.parse_create_sequence(temporary) + } else if self.parse_keyword(Keyword::TYPE) { + self.parse_create_type() } else { self.expected("an object type after CREATE", self.peek_token()) } @@ -7053,6 +7055,46 @@ impl<'a> Parser<'a> { window_frame, }) } + + pub fn parse_create_type(&mut self) -> Result { + let name = self.parse_object_name()?; + self.expect_keyword(Keyword::AS)?; + + let mut attributes = vec![]; + if !self.consume_token(&Token::LParen) || self.consume_token(&Token::RParen) { + return Ok(Statement::CreateType { + name, + representation: UserDefinedTypeRepresentation::Composite { attributes }, + }); + } + + loop { + let attr_name = self.parse_identifier()?; + let attr_data_type = self.parse_data_type()?; + let attr_collation = if self.parse_keyword(Keyword::COLLATE) { + Some(self.parse_object_name()?) + } else { + None + }; + attributes.push(UserDefinedTypeCompositeAttributeDef { + name: attr_name, + data_type: attr_data_type, + collation: attr_collation, + }); + let comma = self.consume_token(&Token::Comma); + if self.consume_token(&Token::RParen) { + // allow a trailing comma + break; + } else if !comma { + return self.expected("',' or ')' after attribute definition", self.peek_token()); + } + } + + Ok(Statement::CreateType { + name, + representation: UserDefinedTypeRepresentation::Composite { attributes }, + }) + } } impl Word { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index a066126d..ad350705 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -7092,3 +7092,29 @@ fn parse_trailing_comma() { trailing_commas.verified_stmt("SELECT DISTINCT ON (album_id) name FROM track"); } + +#[test] +fn parse_create_type() { + let create_type = + verified_stmt("CREATE TYPE db.type_name AS (foo INT, bar TEXT COLLATE \"de_DE\")"); + assert_eq!( + Statement::CreateType { + name: ObjectName(vec![Ident::new("db"), Ident::new("type_name")]), + representation: UserDefinedTypeRepresentation::Composite { + attributes: vec![ + UserDefinedTypeCompositeAttributeDef { + name: Ident::new("foo"), + data_type: DataType::Int(None), + collation: None, + }, + UserDefinedTypeCompositeAttributeDef { + name: Ident::new("bar"), + data_type: DataType::Text, + collation: Some(ObjectName(vec![Ident::with_quote('\"', "de_DE")])), + } + ] + } + }, + create_type + ); +}