From b2bdb35bcae8b2f8cbf6b591cb06608f62a919c2 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 9 Nov 2025 14:38:08 +0100 Subject: [PATCH 1/3] Added support for CREATE TYPE --- src/ast/ddl.rs | 301 ++++++++++++++++++++++++++++++++++++- src/ast/mod.rs | 19 +-- src/keywords.rs | 19 +++ src/parser/mod.rs | 308 ++++++++++++++++++++++++++++++++++++-- tests/sqlparser_common.rs | 124 ++++++++++++--- 5 files changed, 726 insertions(+), 45 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index fd481213f..d039363f6 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2002,22 +2002,47 @@ impl fmt::Display for DropBehavior { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum UserDefinedTypeRepresentation { + /// Composite type: `CREATE TYPE name AS (attributes)` Composite { attributes: Vec, }, + /// Enum type: `CREATE TYPE name AS ENUM (labels)` + /// /// Note: this is PostgreSQL-specific. See - Enum { labels: Vec }, + Enum { + labels: Vec + }, + /// Range type: `CREATE TYPE name AS RANGE (options)` + Range { + options: Vec, + }, + /// Base type (SQL definition): `CREATE TYPE name (options)` + /// + /// Note the lack of `AS` keyword + SqlDefinition { + options: Vec, + }, + /// When the representation of the type is not specified. + /// This is used in `CREATE TYPE ;` statements. + None, } 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)) + Self::Composite { attributes } => { + write!(f, "AS ({})", display_comma_separated(attributes)) + } + Self::Enum { labels } => { + write!(f, "AS ENUM ({})", display_comma_separated(labels)) + } + Self::Range { options } => { + write!(f, "AS RANGE ({})", display_comma_separated(options)) } - UserDefinedTypeRepresentation::Enum { labels } => { - write!(f, "ENUM ({})", display_comma_separated(labels)) + Self::SqlDefinition { options } => { + write!(f, "({})", display_comma_separated(options)) } + Self::None => Ok(()), } } } @@ -2042,6 +2067,272 @@ impl fmt::Display for UserDefinedTypeCompositeAttributeDef { } } +/// Internal length specification for PostgreSQL user-defined base types. +/// +/// Specifies the internal length in bytes of the new type's internal representation. +/// The default assumption is that it is variable-length. +/// +/// # PostgreSQL Documentation +/// See: +/// +/// # Examples +/// ```sql +/// CREATE TYPE mytype ( +/// INPUT = in_func, +/// OUTPUT = out_func, +/// INTERNALLENGTH = 16 -- Fixed 16-byte length +/// ); +/// +/// CREATE TYPE mytype2 ( +/// INPUT = in_func, +/// OUTPUT = out_func, +/// INTERNALLENGTH = VARIABLE -- Variable length +/// ); +/// ``` +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum UserDefinedTypeInternalLength { + /// Fixed internal length: `INTERNALLENGTH = ` + Fixed(u64), + /// Variable internal length: `INTERNALLENGTH = VARIABLE` + Variable, +} + +impl fmt::Display for UserDefinedTypeInternalLength { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UserDefinedTypeInternalLength::Fixed(n) => write!(f, "{}", n), + UserDefinedTypeInternalLength::Variable => write!(f, "VARIABLE"), + } + } +} + +/// Alignment specification for PostgreSQL user-defined base types. +/// +/// Specifies the storage alignment requirement for values of the data type. +/// The allowed values equate to alignment on 1, 2, 4, or 8 byte boundaries. +/// Note that variable-length types must have an alignment of at least 4, since +/// they necessarily contain an int4 as their first component. +/// +/// # PostgreSQL Documentation +/// See: +/// +/// # Examples +/// ```sql +/// CREATE TYPE mytype ( +/// INPUT = in_func, +/// OUTPUT = out_func, +/// ALIGNMENT = int4 -- 4-byte alignment +/// ); +/// ``` +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum Alignment { + /// Single-byte alignment: `ALIGNMENT = char` + Char, + /// 2-byte alignment: `ALIGNMENT = int2` + Int2, + /// 4-byte alignment: `ALIGNMENT = int4` + Int4, + /// 8-byte alignment: `ALIGNMENT = double` + Double, +} + +impl fmt::Display for Alignment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Alignment::Char => write!(f, "char"), + Alignment::Int2 => write!(f, "int2"), + Alignment::Int4 => write!(f, "int4"), + Alignment::Double => write!(f, "double"), + } + } +} + +/// Storage specification for PostgreSQL user-defined base types. +/// +/// Specifies the storage strategy for values of the data type: +/// - `plain`: Prevents compression and out-of-line storage (for fixed-length types) +/// - `external`: Allows out-of-line storage but not compression +/// - `extended`: Allows both compression and out-of-line storage (default for most types) +/// - `main`: Allows compression but discourages out-of-line storage +/// +/// # PostgreSQL Documentation +/// See: +/// +/// # Examples +/// ```sql +/// CREATE TYPE mytype ( +/// INPUT = in_func, +/// OUTPUT = out_func, +/// STORAGE = plain +/// ); +/// ``` +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum UserDefinedTypeStorage { + /// No compression or out-of-line storage: `STORAGE = plain` + Plain, + /// Out-of-line storage allowed, no compression: `STORAGE = external` + External, + /// Both compression and out-of-line storage allowed: `STORAGE = extended` + Extended, + /// Compression allowed, out-of-line discouraged: `STORAGE = main` + Main, +} + +impl fmt::Display for UserDefinedTypeStorage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UserDefinedTypeStorage::Plain => write!(f, "plain"), + UserDefinedTypeStorage::External => write!(f, "external"), + UserDefinedTypeStorage::Extended => write!(f, "extended"), + UserDefinedTypeStorage::Main => write!(f, "main"), + } + } +} + +/// Options for PostgreSQL `CREATE TYPE ... AS RANGE` statement. +/// +/// Range types are data types representing a range of values of some element type +/// (called the range's subtype). These options configure the behavior of the range type. +/// +/// # PostgreSQL Documentation +/// See: +/// +/// # Examples +/// ```sql +/// CREATE TYPE int4range AS RANGE ( +/// SUBTYPE = int4, +/// SUBTYPE_OPCLASS = int4_ops, +/// CANONICAL = int4range_canonical, +/// SUBTYPE_DIFF = int4range_subdiff +/// ); +/// ``` +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum UserDefinedTypeRangeOption { + /// The element type that the range type will represent: `SUBTYPE = subtype` + Subtype(DataType), + /// The operator class for the subtype: `SUBTYPE_OPCLASS = subtype_operator_class` + SubtypeOpClass(ObjectName), + /// Collation to use for ordering the subtype: `COLLATION = collation` + Collation(ObjectName), + /// Function to convert range values to canonical form: `CANONICAL = canonical_function` + Canonical(ObjectName), + /// Function to compute the difference between two subtype values: `SUBTYPE_DIFF = subtype_diff_function` + SubtypeDiff(ObjectName), + /// Name of the corresponding multirange type: `MULTIRANGE_TYPE_NAME = multirange_type_name` + MultirangeTypeName(ObjectName), +} + +impl fmt::Display for UserDefinedTypeRangeOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UserDefinedTypeRangeOption::Subtype(dt) => write!(f, "SUBTYPE = {}", dt), + UserDefinedTypeRangeOption::SubtypeOpClass(name) => write!(f, "SUBTYPE_OPCLASS = {}", name), + UserDefinedTypeRangeOption::Collation(name) => write!(f, "COLLATION = {}", name), + UserDefinedTypeRangeOption::Canonical(name) => write!(f, "CANONICAL = {}", name), + UserDefinedTypeRangeOption::SubtypeDiff(name) => write!(f, "SUBTYPE_DIFF = {}", name), + UserDefinedTypeRangeOption::MultirangeTypeName(name) => write!(f, "MULTIRANGE_TYPE_NAME = {}", name), + } + } +} + +/// Options for PostgreSQL `CREATE TYPE ... ()` statement (base type definition). +/// +/// Base types are the lowest-level data types in PostgreSQL. To define a new base type, +/// you must specify functions that convert it to and from text representation, and optionally +/// binary representation and other properties. +/// +/// Note: This syntax uses parentheses directly after the type name, without the `AS` keyword. +/// +/// # PostgreSQL Documentation +/// See: +/// +/// # Examples +/// ```sql +/// CREATE TYPE complex ( +/// INPUT = complex_in, +/// OUTPUT = complex_out, +/// INTERNALLENGTH = 16, +/// ALIGNMENT = double +/// ); +/// ``` +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum UserDefinedTypeSqlDefinitionOption { + /// Function to convert from external text representation to internal: `INPUT = input_function` + Input(ObjectName), + /// Function to convert from internal to external text representation: `OUTPUT = output_function` + Output(ObjectName), + /// Function to convert from external binary representation to internal: `RECEIVE = receive_function` + Receive(ObjectName), + /// Function to convert from internal to external binary representation: `SEND = send_function` + Send(ObjectName), + /// Function to convert type modifiers from text array to internal form: `TYPMOD_IN = type_modifier_input_function` + TypmodIn(ObjectName), + /// Function to convert type modifiers from internal to text form: `TYPMOD_OUT = type_modifier_output_function` + TypmodOut(ObjectName), + /// Function to compute statistics for the data type: `ANALYZE = analyze_function` + Analyze(ObjectName), + /// Function to handle subscripting operations: `SUBSCRIPT = subscript_function` + Subscript(ObjectName), + /// Internal storage size in bytes, or VARIABLE for variable-length: `INTERNALLENGTH = { internallength | VARIABLE }` + InternalLength(UserDefinedTypeInternalLength), + /// Indicates values are passed by value rather than by reference: `PASSEDBYVALUE` + PassedByValue, + /// Storage alignment requirement (1, 2, 4, or 8 bytes): `ALIGNMENT = alignment` + Alignment(Alignment), + /// Storage strategy for varlena types: `STORAGE = storage` + Storage(UserDefinedTypeStorage), + /// Copy properties from an existing type: `LIKE = like_type` + Like(ObjectName), + /// Type category for implicit casting rules (single char): `CATEGORY = category` + Category(char), + /// Whether this type is preferred within its category: `PREFERRED = preferred` + Preferred(bool), + /// Default value for the type: `DEFAULT = default` + Default(Expr), + /// Element type for array types: `ELEMENT = element` + Element(DataType), + /// Delimiter character for array value display: `DELIMITER = delimiter` + Delimiter(String), + /// Whether the type supports collation: `COLLATABLE = collatable` + Collatable(bool), +} + +impl fmt::Display for UserDefinedTypeSqlDefinitionOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UserDefinedTypeSqlDefinitionOption::Input(name) => write!(f, "INPUT = {}", name), + UserDefinedTypeSqlDefinitionOption::Output(name) => write!(f, "OUTPUT = {}", name), + UserDefinedTypeSqlDefinitionOption::Receive(name) => write!(f, "RECEIVE = {}", name), + UserDefinedTypeSqlDefinitionOption::Send(name) => write!(f, "SEND = {}", name), + UserDefinedTypeSqlDefinitionOption::TypmodIn(name) => write!(f, "TYPMOD_IN = {}", name), + UserDefinedTypeSqlDefinitionOption::TypmodOut(name) => write!(f, "TYPMOD_OUT = {}", name), + UserDefinedTypeSqlDefinitionOption::Analyze(name) => write!(f, "ANALYZE = {}", name), + UserDefinedTypeSqlDefinitionOption::Subscript(name) => write!(f, "SUBSCRIPT = {}", name), + UserDefinedTypeSqlDefinitionOption::InternalLength(len) => write!(f, "INTERNALLENGTH = {}", len), + UserDefinedTypeSqlDefinitionOption::PassedByValue => write!(f, "PASSEDBYVALUE"), + UserDefinedTypeSqlDefinitionOption::Alignment(align) => write!(f, "ALIGNMENT = {}", align), + UserDefinedTypeSqlDefinitionOption::Storage(storage) => write!(f, "STORAGE = {}", storage), + UserDefinedTypeSqlDefinitionOption::Like(name) => write!(f, "LIKE = {}", name), + UserDefinedTypeSqlDefinitionOption::Category(c) => write!(f, "CATEGORY = '{}'", c), + UserDefinedTypeSqlDefinitionOption::Preferred(b) => write!(f, "PREFERRED = {}", b), + UserDefinedTypeSqlDefinitionOption::Default(expr) => write!(f, "DEFAULT = {}", expr), + UserDefinedTypeSqlDefinitionOption::Element(dt) => write!(f, "ELEMENT = {}", dt), + UserDefinedTypeSqlDefinitionOption::Delimiter(s) => write!(f, "DELIMITER = '{}'", escape_single_quote_string(s)), + UserDefinedTypeSqlDefinitionOption::Collatable(b) => write!(f, "COLLATABLE = {}", b), + } + } +} + /// PARTITION statement used in ALTER TABLE et al. such as in Hive and ClickHouse SQL. /// For example, ClickHouse's OPTIMIZE TABLE supports syntax like PARTITION ID 'partition_id' and PARTITION expr. /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/optimize) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 176d36545..97f3c0ee7 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -71,7 +71,9 @@ pub use self::ddl::{ IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption, TriggerObjectKind, Truncate, - UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, + UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, + UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, + ViewColumnDef, Alignment, }; pub use self::dml::{Delete, Insert, Update}; pub use self::operator::{BinaryOperator, UnaryOperator}; @@ -2787,10 +2789,11 @@ impl fmt::Display for Declare { } /// Sql options of a `CREATE TABLE` statement. -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum CreateTableOptions { + #[default] None, /// Options specified using the `WITH` keyword. /// e.g. `WITH (description = "123")` @@ -2819,12 +2822,6 @@ pub enum CreateTableOptions { TableProperties(Vec), } -impl Default for CreateTableOptions { - fn default() -> Self { - Self::None - } -} - impl fmt::Display for CreateTableOptions { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -5644,7 +5641,11 @@ impl fmt::Display for Statement { name, representation, } => { - write!(f, "CREATE TYPE {name} AS {representation}") + write!(f, "CREATE TYPE {name}")?; + match representation { + UserDefinedTypeRepresentation::None => Ok(()), + repr => write!(f, " {repr}"), + } } Statement::Pragma { name, value, is_eq } => { write!(f, "PRAGMA {name}")?; diff --git a/src/keywords.rs b/src/keywords.rs index 319c57827..8616f3fa4 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -91,6 +91,7 @@ define_keywords!( ALERT, ALGORITHM, ALIAS, + ALIGNMENT, ALL, ALLOCATE, ALLOWOVERWRITE, @@ -166,6 +167,7 @@ define_keywords!( CACHE, CALL, CALLED, + CANONICAL, CARDINALITY, CASCADE, CASCADED, @@ -177,6 +179,7 @@ define_keywords!( CATALOG_SYNC_NAMESPACE_FLATTEN_DELIMITER, CATALOG_SYNC_NAMESPACE_MODE, CATCH, + CATEGORY, CEIL, CEILING, CENTURY, @@ -203,6 +206,7 @@ define_keywords!( CLUSTERED, CLUSTERING, COALESCE, + COLLATABLE, COLLATE, COLLATION, COLLECT, @@ -501,6 +505,7 @@ define_keywords!( INT8, INTEGER, INTEGRATION, + INTERNALLENGTH, INTERPOLATE, INTERSECT, INTERSECTION, @@ -563,6 +568,7 @@ define_keywords!( LS, LSEG, MACRO, + MAIN, MANAGE, MANAGED, MANAGEDLOCATION, @@ -615,6 +621,7 @@ define_keywords!( MONTH, MONTHS, MSCK, + MULTIRANGE_TYPE_NAME, MULTISET, MUTATION, NAME, @@ -718,6 +725,7 @@ define_keywords!( PARTITION, PARTITIONED, PARTITIONS, + PASSEDBYVALUE, PASSING, PASSKEY, PASSWORD, @@ -734,6 +742,7 @@ define_keywords!( PERSISTENT, PIVOT, PLACING, + PLAIN, PLAN, PLANS, POINT, @@ -748,6 +757,7 @@ define_keywords!( PRECEDES, PRECEDING, PRECISION, + PREFERRED, PREPARE, PRESERVE, PRESET, @@ -778,6 +788,7 @@ define_keywords!( READS, READ_ONLY, REAL, + RECEIVE, RECLUSTER, RECURSIVE, REF, @@ -867,6 +878,7 @@ define_keywords!( SELECT, SEMANTIC_VIEW, SEMI, + SEND, SENSITIVE, SEPARATOR, SEQUENCE, @@ -935,9 +947,13 @@ define_keywords!( STRING, STRUCT, SUBMULTISET, + SUBSCRIPT, SUBSTR, SUBSTRING, SUBSTRING_REGEX, + SUBTYPE, + SUBTYPE_DIFF, + SUBTYPE_OPCLASS, SUCCEEDS, SUM, SUPER, @@ -1007,6 +1023,8 @@ define_keywords!( TSVECTOR, TUPLE, TYPE, + TYPMOD_IN, + TYPMOD_OUT, UBIGINT, UESCAPE, UHUGEINT, @@ -1056,6 +1074,7 @@ define_keywords!( VARBINARY, VARBIT, VARCHAR, + VARIABLE, VARIABLES, VARYING, VAR_POP, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9a01e510b..f358e05e3 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -17331,20 +17331,62 @@ impl<'a> Parser<'a> { pub fn parse_create_type(&mut self) -> Result { let name = self.parse_object_name(false)?; - self.expect_keyword_is(Keyword::AS)?; + // Check if we have AS keyword + let has_as = self.parse_keyword(Keyword::AS); + + if !has_as { + // Two cases: CREATE TYPE name; or CREATE TYPE name (options); + if self.consume_token(&Token::LParen) { + // CREATE TYPE name (options) - SQL definition without AS + let options = self.parse_create_type_sql_definition_options()?; + self.expect_token(&Token::RParen)?; + return Ok(Statement::CreateType { + name, + representation: UserDefinedTypeRepresentation::SqlDefinition { options }, + }); + } + + // CREATE TYPE name; - no representation + return Ok(Statement::CreateType { + name, + representation: UserDefinedTypeRepresentation::None, + }); + } + + // We have AS keyword if self.parse_keyword(Keyword::ENUM) { - return self.parse_create_type_enum(name); + // CREATE TYPE name AS ENUM (labels) + self.parse_create_type_enum(name) + } else if self.parse_keyword(Keyword::RANGE) { + // CREATE TYPE name AS RANGE (options) + self.parse_create_type_range(name) + } else if self.consume_token(&Token::LParen) { + // CREATE TYPE name AS (attributes) - Composite + self.parse_create_type_composite(name) + } else { + self.expected("ENUM, RANGE, or '(' after AS", self.peek_token()) } + } - let mut attributes = vec![]; - if !self.consume_token(&Token::LParen) || self.consume_token(&Token::RParen) { + /// Parse remainder of `CREATE TYPE AS (attributes)` statement (composite type) + /// + /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtype.html) + pub fn parse_create_type_composite( + &mut self, + name: ObjectName, + ) -> Result { + if self.consume_token(&Token::RParen) { + // Empty composite type return Ok(Statement::CreateType { name, - representation: UserDefinedTypeRepresentation::Composite { attributes }, + representation: UserDefinedTypeRepresentation::Composite { + attributes: vec![], + }, }); } + let mut attributes = vec![]; loop { let attr_name = self.parse_identifier()?; let attr_data_type = self.parse_data_type()?; @@ -17358,14 +17400,12 @@ impl<'a> Parser<'a> { 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 + + if !self.consume_token(&Token::Comma) { break; - } else if !comma { - return self.expected("',' or ')' after attribute definition", self.peek_token()); } } + self.expect_token(&Token::RParen)?; Ok(Statement::CreateType { name, @@ -17387,6 +17427,254 @@ impl<'a> Parser<'a> { }) } + /// Parse remainder of `CREATE TYPE AS RANGE` statement + /// + /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtype.html) + pub fn parse_create_type_range(&mut self, name: ObjectName) -> Result { + self.expect_token(&Token::LParen)?; + let options = self.parse_comma_separated0(|p| p.parse_range_option(), Token::RParen)?; + self.expect_token(&Token::RParen)?; + + Ok(Statement::CreateType { + name, + representation: UserDefinedTypeRepresentation::Range { options }, + }) + } + + /// Parse a single range option for CREATE TYPE AS RANGE + fn parse_range_option(&mut self) -> Result { + let keyword = self.parse_one_of_keywords(&[ + Keyword::SUBTYPE, + Keyword::SUBTYPE_OPCLASS, + Keyword::COLLATION, + Keyword::CANONICAL, + Keyword::SUBTYPE_DIFF, + Keyword::MULTIRANGE_TYPE_NAME, + ]); + + match keyword { + Some(Keyword::SUBTYPE) => { + self.expect_token(&Token::Eq)?; + let data_type = self.parse_data_type()?; + Ok(UserDefinedTypeRangeOption::Subtype(data_type)) + } + Some(Keyword::SUBTYPE_OPCLASS) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeRangeOption::SubtypeOpClass(name)) + } + Some(Keyword::COLLATION) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeRangeOption::Collation(name)) + } + Some(Keyword::CANONICAL) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeRangeOption::Canonical(name)) + } + Some(Keyword::SUBTYPE_DIFF) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeRangeOption::SubtypeDiff(name)) + } + Some(Keyword::MULTIRANGE_TYPE_NAME) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeRangeOption::MultirangeTypeName(name)) + } + _ => self.expected("range option keyword", self.peek_token()), + } + } + + /// Parse SQL definition options for CREATE TYPE (options) + fn parse_create_type_sql_definition_options( + &mut self, + ) -> Result, ParserError> { + self.parse_comma_separated0(|p| p.parse_sql_definition_option(), Token::RParen) + } + + /// Parse a single SQL definition option for CREATE TYPE (options) + fn parse_sql_definition_option( + &mut self, + ) -> Result { + let keyword = self.parse_one_of_keywords(&[ + Keyword::INPUT, + Keyword::OUTPUT, + Keyword::RECEIVE, + Keyword::SEND, + Keyword::TYPMOD_IN, + Keyword::TYPMOD_OUT, + Keyword::ANALYZE, + Keyword::SUBSCRIPT, + Keyword::INTERNALLENGTH, + Keyword::PASSEDBYVALUE, + Keyword::ALIGNMENT, + Keyword::STORAGE, + Keyword::LIKE, + Keyword::CATEGORY, + Keyword::PREFERRED, + Keyword::DEFAULT, + Keyword::ELEMENT, + Keyword::DELIMITER, + Keyword::COLLATABLE, + ]); + + match keyword { + Some(Keyword::INPUT) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeSqlDefinitionOption::Input(name)) + } + Some(Keyword::OUTPUT) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeSqlDefinitionOption::Output(name)) + } + Some(Keyword::RECEIVE) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeSqlDefinitionOption::Receive(name)) + } + Some(Keyword::SEND) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeSqlDefinitionOption::Send(name)) + } + Some(Keyword::TYPMOD_IN) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeSqlDefinitionOption::TypmodIn(name)) + } + Some(Keyword::TYPMOD_OUT) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeSqlDefinitionOption::TypmodOut(name)) + } + Some(Keyword::ANALYZE) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeSqlDefinitionOption::Analyze(name)) + } + Some(Keyword::SUBSCRIPT) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeSqlDefinitionOption::Subscript(name)) + } + Some(Keyword::INTERNALLENGTH) => { + self.expect_token(&Token::Eq)?; + if self.parse_keyword(Keyword::VARIABLE) { + Ok(UserDefinedTypeSqlDefinitionOption::InternalLength( + UserDefinedTypeInternalLength::Variable, + )) + } else { + let value = self.parse_literal_uint()?; + Ok(UserDefinedTypeSqlDefinitionOption::InternalLength( + UserDefinedTypeInternalLength::Fixed(value), + )) + } + } + Some(Keyword::PASSEDBYVALUE) => Ok(UserDefinedTypeSqlDefinitionOption::PassedByValue), + Some(Keyword::ALIGNMENT) => { + self.expect_token(&Token::Eq)?; + let align_keyword = self.parse_one_of_keywords(&[ + Keyword::CHAR, + Keyword::INT2, + Keyword::INT4, + Keyword::DOUBLE, + ]); + match align_keyword { + Some(Keyword::CHAR) => Ok(UserDefinedTypeSqlDefinitionOption::Alignment( + Alignment::Char, + )), + Some(Keyword::INT2) => Ok(UserDefinedTypeSqlDefinitionOption::Alignment( + Alignment::Int2, + )), + Some(Keyword::INT4) => Ok(UserDefinedTypeSqlDefinitionOption::Alignment( + Alignment::Int4, + )), + Some(Keyword::DOUBLE) => Ok(UserDefinedTypeSqlDefinitionOption::Alignment( + Alignment::Double, + )), + _ => self.expected( + "alignment value (char, int2, int4, or double)", + self.peek_token(), + ), + } + } + Some(Keyword::STORAGE) => { + self.expect_token(&Token::Eq)?; + let storage_keyword = self.parse_one_of_keywords(&[ + Keyword::PLAIN, + Keyword::EXTERNAL, + Keyword::EXTENDED, + Keyword::MAIN, + ]); + match storage_keyword { + Some(Keyword::PLAIN) => Ok(UserDefinedTypeSqlDefinitionOption::Storage( + UserDefinedTypeStorage::Plain, + )), + Some(Keyword::EXTERNAL) => Ok(UserDefinedTypeSqlDefinitionOption::Storage( + UserDefinedTypeStorage::External, + )), + Some(Keyword::EXTENDED) => Ok(UserDefinedTypeSqlDefinitionOption::Storage( + UserDefinedTypeStorage::Extended, + )), + Some(Keyword::MAIN) => Ok(UserDefinedTypeSqlDefinitionOption::Storage( + UserDefinedTypeStorage::Main, + )), + _ => self.expected( + "storage value (plain, external, extended, or main)", + self.peek_token(), + ), + } + } + Some(Keyword::LIKE) => { + self.expect_token(&Token::Eq)?; + let name = self.parse_object_name(false)?; + Ok(UserDefinedTypeSqlDefinitionOption::Like(name)) + } + Some(Keyword::CATEGORY) => { + self.expect_token(&Token::Eq)?; + let category_str = self.parse_literal_string()?; + let category_char = category_str.chars().next().ok_or_else(|| { + ParserError::ParserError( + "CATEGORY value must be a single character".to_string(), + ) + })?; + Ok(UserDefinedTypeSqlDefinitionOption::Category(category_char)) + } + Some(Keyword::PREFERRED) => { + self.expect_token(&Token::Eq)?; + let value = + self.parse_keyword(Keyword::TRUE) || !self.parse_keyword(Keyword::FALSE); + Ok(UserDefinedTypeSqlDefinitionOption::Preferred(value)) + } + Some(Keyword::DEFAULT) => { + self.expect_token(&Token::Eq)?; + let expr = self.parse_expr()?; + Ok(UserDefinedTypeSqlDefinitionOption::Default(expr)) + } + Some(Keyword::ELEMENT) => { + self.expect_token(&Token::Eq)?; + let data_type = self.parse_data_type()?; + Ok(UserDefinedTypeSqlDefinitionOption::Element(data_type)) + } + Some(Keyword::DELIMITER) => { + self.expect_token(&Token::Eq)?; + let delimiter = self.parse_literal_string()?; + Ok(UserDefinedTypeSqlDefinitionOption::Delimiter(delimiter)) + } + Some(Keyword::COLLATABLE) => { + self.expect_token(&Token::Eq)?; + let value = + self.parse_keyword(Keyword::TRUE) || !self.parse_keyword(Keyword::FALSE); + Ok(UserDefinedTypeSqlDefinitionOption::Collatable(value)) + } + _ => self.expected("SQL definition option keyword", self.peek_token()), + } + } + fn parse_parenthesized_identifiers(&mut self) -> Result, ParserError> { self.expect_token(&Token::LParen)?; let idents = self.parse_comma_separated0(|p| p.parse_identifier(), Token::RParen)?; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index f1ba5df04..74b5f4917 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -11693,28 +11693,110 @@ fn parse_projection_trailing_comma() { #[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::from(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::from(vec![Ident::with_quote('\"', "de_DE")])), - } - ] + // Test simple type declaration without AS - verify AST + match verified_stmt("CREATE TYPE mytype") { + Statement::CreateType { name, representation } => { + assert_eq!(name.to_string(), "mytype"); + assert_eq!(representation, UserDefinedTypeRepresentation::None); + } + _ => unreachable!(), + } + + // Test composite type - verify AST structure + match verified_stmt("CREATE TYPE address AS (street VARCHAR(100), city TEXT COLLATE \"en_US\")") { + Statement::CreateType { name, representation } => { + assert_eq!(name.to_string(), "address"); + match representation { + UserDefinedTypeRepresentation::Composite { attributes } => { + assert_eq!(attributes.len(), 2); + assert_eq!(attributes[0].name, Ident::new("street")); + assert_eq!(attributes[0].data_type, DataType::Varchar(Some(CharacterLength::IntegerLength { length: 100, unit: None }))); + assert_eq!(attributes[0].collation, None); + + assert_eq!(attributes[1].name, Ident::new("city")); + assert_eq!(attributes[1].data_type, DataType::Text); + assert_eq!(attributes[1].collation.as_ref().map(|n| n.to_string()), Some("\"en_US\"".to_string())); + } + _ => unreachable!(), } - }, - create_type - ); + } + _ => unreachable!(), + } + + verified_stmt("CREATE TYPE empty AS ()"); + + // Test ENUM type - verify AST + match verified_stmt("CREATE TYPE mood AS ENUM ('happy', 'sad')") { + Statement::CreateType { name, representation } => { + assert_eq!(name.to_string(), "mood"); + match representation { + UserDefinedTypeRepresentation::Enum { labels } => { + assert_eq!(labels.len(), 2); + assert_eq!(labels[0], Ident::with_quote('\'', "happy")); + assert_eq!(labels[1], Ident::with_quote('\'', "sad")); + } + _ => unreachable!(), + } + } + _ => unreachable!(), + } + + // Test RANGE type - verify AST structure + match verified_stmt("CREATE TYPE int4range AS RANGE (SUBTYPE = INTEGER, CANONICAL = fn1)") { + Statement::CreateType { name, representation } => { + assert_eq!(name.to_string(), "int4range"); + match representation { + UserDefinedTypeRepresentation::Range { options } => { + assert_eq!(options.len(), 2); + assert!(matches!(options[0], UserDefinedTypeRangeOption::Subtype(DataType::Integer(_)))); + assert!(matches!(options[1], UserDefinedTypeRangeOption::Canonical(_))); + } + _ => unreachable!(), + } + } + _ => unreachable!(), + } + + verified_stmt("CREATE TYPE textrange AS RANGE (SUBTYPE = TEXT, COLLATION = \"en_US\", MULTIRANGE_TYPE_NAME = textmultirange)"); + + // Test SQL definition type - verify AST + match verified_stmt("CREATE TYPE mytype (INPUT = in_fn, OUTPUT = out_fn, INTERNALLENGTH = 16, PASSEDBYVALUE)") { + Statement::CreateType { name, representation } => { + assert_eq!(name.to_string(), "mytype"); + match representation { + UserDefinedTypeRepresentation::SqlDefinition { options } => { + assert_eq!(options.len(), 4); + assert!(matches!(options[0], UserDefinedTypeSqlDefinitionOption::Input(_))); + assert!(matches!(options[1], UserDefinedTypeSqlDefinitionOption::Output(_))); + assert!(matches!( + options[2], + UserDefinedTypeSqlDefinitionOption::InternalLength(UserDefinedTypeInternalLength::Fixed(16)) + )); + assert!(matches!(options[3], UserDefinedTypeSqlDefinitionOption::PassedByValue)); + } + _ => unreachable!(), + } + } + _ => unreachable!(), + } + + verified_stmt("CREATE TYPE mytype (INPUT = in_fn, OUTPUT = out_fn, INTERNALLENGTH = VARIABLE, STORAGE = extended)"); + + // Test all storage variants + for storage in ["plain", "external", "extended", "main"] { + verified_stmt(&format!("CREATE TYPE t (INPUT = f_in, OUTPUT = f_out, STORAGE = {storage})")); + } + + // Test all alignment variants + for align in ["char", "int2", "int4", "double"] { + verified_stmt(&format!("CREATE TYPE t (INPUT = f_in, OUTPUT = f_out, ALIGNMENT = {align})")); + } + + // Test additional function options (PostgreSQL-specific due to ANALYZE keyword) + pg_and_generic().verified_stmt("CREATE TYPE t (INPUT = f_in, OUTPUT = f_out, RECEIVE = f_recv, SEND = f_send, TYPMOD_IN = f_tmin, TYPMOD_OUT = f_tmout, ANALYZE = f_analyze, SUBSCRIPT = f_sub)"); + + // Test advanced options + verified_stmt("CREATE TYPE t (INPUT = f_in, OUTPUT = f_out, LIKE = INT, CATEGORY = 'N', PREFERRED = true, DEFAULT = 0, ELEMENT = INTEGER, DELIMITER = ',', COLLATABLE = false)"); } #[test] From 1b5a0cbadb60ec1898ea62c6bb85c0dc4f5ea35f Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 9 Nov 2025 14:40:22 +0100 Subject: [PATCH 2/3] Formatted code --- src/ast/ddl.rs | 40 +++++++++++------ src/ast/mod.rs | 12 ++--- src/parser/mod.rs | 4 +- tests/sqlparser_common.rs | 94 +++++++++++++++++++++++++++++---------- 4 files changed, 105 insertions(+), 45 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index d039363f6..b5ba8e30a 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2007,17 +2007,15 @@ pub enum UserDefinedTypeRepresentation { attributes: Vec, }, /// Enum type: `CREATE TYPE name AS ENUM (labels)` - /// + /// /// Note: this is PostgreSQL-specific. See - Enum { - labels: Vec - }, + Enum { labels: Vec }, /// Range type: `CREATE TYPE name AS RANGE (options)` Range { options: Vec, }, /// Base type (SQL definition): `CREATE TYPE name (options)` - /// + /// /// Note the lack of `AS` keyword SqlDefinition { options: Vec, @@ -2234,11 +2232,15 @@ impl fmt::Display for UserDefinedTypeRangeOption { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { UserDefinedTypeRangeOption::Subtype(dt) => write!(f, "SUBTYPE = {}", dt), - UserDefinedTypeRangeOption::SubtypeOpClass(name) => write!(f, "SUBTYPE_OPCLASS = {}", name), + UserDefinedTypeRangeOption::SubtypeOpClass(name) => { + write!(f, "SUBTYPE_OPCLASS = {}", name) + } UserDefinedTypeRangeOption::Collation(name) => write!(f, "COLLATION = {}", name), UserDefinedTypeRangeOption::Canonical(name) => write!(f, "CANONICAL = {}", name), UserDefinedTypeRangeOption::SubtypeDiff(name) => write!(f, "SUBTYPE_DIFF = {}", name), - UserDefinedTypeRangeOption::MultirangeTypeName(name) => write!(f, "MULTIRANGE_TYPE_NAME = {}", name), + UserDefinedTypeRangeOption::MultirangeTypeName(name) => { + write!(f, "MULTIRANGE_TYPE_NAME = {}", name) + } } } } @@ -2315,19 +2317,31 @@ impl fmt::Display for UserDefinedTypeSqlDefinitionOption { UserDefinedTypeSqlDefinitionOption::Receive(name) => write!(f, "RECEIVE = {}", name), UserDefinedTypeSqlDefinitionOption::Send(name) => write!(f, "SEND = {}", name), UserDefinedTypeSqlDefinitionOption::TypmodIn(name) => write!(f, "TYPMOD_IN = {}", name), - UserDefinedTypeSqlDefinitionOption::TypmodOut(name) => write!(f, "TYPMOD_OUT = {}", name), + UserDefinedTypeSqlDefinitionOption::TypmodOut(name) => { + write!(f, "TYPMOD_OUT = {}", name) + } UserDefinedTypeSqlDefinitionOption::Analyze(name) => write!(f, "ANALYZE = {}", name), - UserDefinedTypeSqlDefinitionOption::Subscript(name) => write!(f, "SUBSCRIPT = {}", name), - UserDefinedTypeSqlDefinitionOption::InternalLength(len) => write!(f, "INTERNALLENGTH = {}", len), + UserDefinedTypeSqlDefinitionOption::Subscript(name) => { + write!(f, "SUBSCRIPT = {}", name) + } + UserDefinedTypeSqlDefinitionOption::InternalLength(len) => { + write!(f, "INTERNALLENGTH = {}", len) + } UserDefinedTypeSqlDefinitionOption::PassedByValue => write!(f, "PASSEDBYVALUE"), - UserDefinedTypeSqlDefinitionOption::Alignment(align) => write!(f, "ALIGNMENT = {}", align), - UserDefinedTypeSqlDefinitionOption::Storage(storage) => write!(f, "STORAGE = {}", storage), + UserDefinedTypeSqlDefinitionOption::Alignment(align) => { + write!(f, "ALIGNMENT = {}", align) + } + UserDefinedTypeSqlDefinitionOption::Storage(storage) => { + write!(f, "STORAGE = {}", storage) + } UserDefinedTypeSqlDefinitionOption::Like(name) => write!(f, "LIKE = {}", name), UserDefinedTypeSqlDefinitionOption::Category(c) => write!(f, "CATEGORY = '{}'", c), UserDefinedTypeSqlDefinitionOption::Preferred(b) => write!(f, "PREFERRED = {}", b), UserDefinedTypeSqlDefinitionOption::Default(expr) => write!(f, "DEFAULT = {}", expr), UserDefinedTypeSqlDefinitionOption::Element(dt) => write!(f, "ELEMENT = {}", dt), - UserDefinedTypeSqlDefinitionOption::Delimiter(s) => write!(f, "DELIMITER = '{}'", escape_single_quote_string(s)), + UserDefinedTypeSqlDefinitionOption::Delimiter(s) => { + write!(f, "DELIMITER = '{}'", escape_single_quote_string(s)) + } UserDefinedTypeSqlDefinitionOption::Collatable(b) => write!(f, "COLLATABLE = {}", b), } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 97f3c0ee7..a95ab4451 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -59,9 +59,9 @@ pub use self::dcl::{ AlterRoleOperation, CreateRole, ResetConfig, RoleOption, SecondaryRoles, SetConfigValue, Use, }; pub use self::ddl::{ - AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterPolicyOperation, - AlterSchema, AlterSchemaOperation, AlterTable, AlterTableAlgorithm, AlterTableLock, - AlterTableOperation, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition, + Alignment, AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, + AlterPolicyOperation, AlterSchema, AlterSchemaOperation, AlterTable, AlterTableAlgorithm, + AlterTableLock, AlterTableOperation, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain, CreateExtension, CreateFunction, @@ -71,9 +71,9 @@ pub use self::ddl::{ IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption, TriggerObjectKind, Truncate, - UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, - UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, - ViewColumnDef, Alignment, + UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength, + UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption, + UserDefinedTypeStorage, ViewColumnDef, }; pub use self::dml::{Delete, Insert, Update}; pub use self::operator::{BinaryOperator, UnaryOperator}; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f358e05e3..8500cbafe 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -17380,9 +17380,7 @@ impl<'a> Parser<'a> { // Empty composite type return Ok(Statement::CreateType { name, - representation: UserDefinedTypeRepresentation::Composite { - attributes: vec![], - }, + representation: UserDefinedTypeRepresentation::Composite { attributes: vec![] }, }); } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 74b5f4917..57672932e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -11695,7 +11695,10 @@ fn parse_projection_trailing_comma() { fn parse_create_type() { // Test simple type declaration without AS - verify AST match verified_stmt("CREATE TYPE mytype") { - Statement::CreateType { name, representation } => { + Statement::CreateType { + name, + representation, + } => { assert_eq!(name.to_string(), "mytype"); assert_eq!(representation, UserDefinedTypeRepresentation::None); } @@ -11703,31 +11706,47 @@ fn parse_create_type() { } // Test composite type - verify AST structure - match verified_stmt("CREATE TYPE address AS (street VARCHAR(100), city TEXT COLLATE \"en_US\")") { - Statement::CreateType { name, representation } => { + match verified_stmt("CREATE TYPE address AS (street VARCHAR(100), city TEXT COLLATE \"en_US\")") + { + Statement::CreateType { + name, + representation, + } => { assert_eq!(name.to_string(), "address"); match representation { UserDefinedTypeRepresentation::Composite { attributes } => { assert_eq!(attributes.len(), 2); assert_eq!(attributes[0].name, Ident::new("street")); - assert_eq!(attributes[0].data_type, DataType::Varchar(Some(CharacterLength::IntegerLength { length: 100, unit: None }))); + assert_eq!( + attributes[0].data_type, + DataType::Varchar(Some(CharacterLength::IntegerLength { + length: 100, + unit: None + })) + ); assert_eq!(attributes[0].collation, None); - + assert_eq!(attributes[1].name, Ident::new("city")); assert_eq!(attributes[1].data_type, DataType::Text); - assert_eq!(attributes[1].collation.as_ref().map(|n| n.to_string()), Some("\"en_US\"".to_string())); + assert_eq!( + attributes[1].collation.as_ref().map(|n| n.to_string()), + Some("\"en_US\"".to_string()) + ); } _ => unreachable!(), } } _ => unreachable!(), } - + verified_stmt("CREATE TYPE empty AS ()"); // Test ENUM type - verify AST match verified_stmt("CREATE TYPE mood AS ENUM ('happy', 'sad')") { - Statement::CreateType { name, representation } => { + Statement::CreateType { + name, + representation, + } => { assert_eq!(name.to_string(), "mood"); match representation { UserDefinedTypeRepresentation::Enum { labels } => { @@ -11743,53 +11762,82 @@ fn parse_create_type() { // Test RANGE type - verify AST structure match verified_stmt("CREATE TYPE int4range AS RANGE (SUBTYPE = INTEGER, CANONICAL = fn1)") { - Statement::CreateType { name, representation } => { + Statement::CreateType { + name, + representation, + } => { assert_eq!(name.to_string(), "int4range"); match representation { UserDefinedTypeRepresentation::Range { options } => { assert_eq!(options.len(), 2); - assert!(matches!(options[0], UserDefinedTypeRangeOption::Subtype(DataType::Integer(_)))); - assert!(matches!(options[1], UserDefinedTypeRangeOption::Canonical(_))); + assert!(matches!( + options[0], + UserDefinedTypeRangeOption::Subtype(DataType::Integer(_)) + )); + assert!(matches!( + options[1], + UserDefinedTypeRangeOption::Canonical(_) + )); } _ => unreachable!(), } } _ => unreachable!(), } - + verified_stmt("CREATE TYPE textrange AS RANGE (SUBTYPE = TEXT, COLLATION = \"en_US\", MULTIRANGE_TYPE_NAME = textmultirange)"); // Test SQL definition type - verify AST - match verified_stmt("CREATE TYPE mytype (INPUT = in_fn, OUTPUT = out_fn, INTERNALLENGTH = 16, PASSEDBYVALUE)") { - Statement::CreateType { name, representation } => { + match verified_stmt( + "CREATE TYPE mytype (INPUT = in_fn, OUTPUT = out_fn, INTERNALLENGTH = 16, PASSEDBYVALUE)", + ) { + Statement::CreateType { + name, + representation, + } => { assert_eq!(name.to_string(), "mytype"); match representation { UserDefinedTypeRepresentation::SqlDefinition { options } => { assert_eq!(options.len(), 4); - assert!(matches!(options[0], UserDefinedTypeSqlDefinitionOption::Input(_))); - assert!(matches!(options[1], UserDefinedTypeSqlDefinitionOption::Output(_))); assert!(matches!( - options[2], - UserDefinedTypeSqlDefinitionOption::InternalLength(UserDefinedTypeInternalLength::Fixed(16)) + options[0], + UserDefinedTypeSqlDefinitionOption::Input(_) + )); + assert!(matches!( + options[1], + UserDefinedTypeSqlDefinitionOption::Output(_) + )); + assert!(matches!( + options[2], + UserDefinedTypeSqlDefinitionOption::InternalLength( + UserDefinedTypeInternalLength::Fixed(16) + ) + )); + assert!(matches!( + options[3], + UserDefinedTypeSqlDefinitionOption::PassedByValue )); - assert!(matches!(options[3], UserDefinedTypeSqlDefinitionOption::PassedByValue)); } _ => unreachable!(), } } _ => unreachable!(), } - + verified_stmt("CREATE TYPE mytype (INPUT = in_fn, OUTPUT = out_fn, INTERNALLENGTH = VARIABLE, STORAGE = extended)"); - + // Test all storage variants for storage in ["plain", "external", "extended", "main"] { - verified_stmt(&format!("CREATE TYPE t (INPUT = f_in, OUTPUT = f_out, STORAGE = {storage})")); + verified_stmt(&format!( + "CREATE TYPE t (INPUT = f_in, OUTPUT = f_out, STORAGE = {storage})" + )); } // Test all alignment variants for align in ["char", "int2", "int4", "double"] { - verified_stmt(&format!("CREATE TYPE t (INPUT = f_in, OUTPUT = f_out, ALIGNMENT = {align})")); + verified_stmt(&format!( + "CREATE TYPE t (INPUT = f_in, OUTPUT = f_out, ALIGNMENT = {align})" + )); } // Test additional function options (PostgreSQL-specific due to ANALYZE keyword) From 3c76295e7e062ccbea1aeeb776cbde5b04fd2fa2 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 9 Nov 2025 15:25:15 +0100 Subject: [PATCH 3/3] =?UTF-8?q?Added=20missing=C2=A0`Copy`=20derive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ast/ddl.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index b5ba8e30a..b53a7105d 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2087,7 +2087,7 @@ impl fmt::Display for UserDefinedTypeCompositeAttributeDef { /// INTERNALLENGTH = VARIABLE -- Variable length /// ); /// ``` -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum UserDefinedTypeInternalLength { @@ -2124,7 +2124,7 @@ impl fmt::Display for UserDefinedTypeInternalLength { /// ALIGNMENT = int4 -- 4-byte alignment /// ); /// ``` -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum Alignment { @@ -2168,7 +2168,7 @@ impl fmt::Display for Alignment { /// STORAGE = plain /// ); /// ``` -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum UserDefinedTypeStorage {