This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch 
gh-readonly-queue/main/pr-2094-a00d5cd9673cc393e17135b5f710b5694005e050
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

commit 78be8b178b53e5999ebcb27713fb9f05e8ece156
Author: Luca Cappelletti <[email protected]>
AuthorDate: Thu Nov 13 10:25:27 2025 +0100

    Complete PostgreSQL `CREATE TYPE` Support (#2094)
    
    Co-authored-by: Ifeanyi Ubah <[email protected]>
---
 src/ast/ddl.rs              | 313 +++++++++++++++++++++++++++++++++++++++++++-
 src/ast/mod.rs              |  38 +++---
 src/keywords.rs             |  19 +++
 src/parser/mod.rs           | 309 +++++++++++++++++++++++++++++++++++++++++--
 tests/sqlparser_common.rs   | 257 +++++++++++++++++++++++++++++++++---
 tests/sqlparser_postgres.rs |   2 +-
 6 files changed, 886 insertions(+), 52 deletions(-)

diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index 1ae24a7f..286b16a4 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -2023,21 +2023,44 @@ 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<UserDefinedTypeCompositeAttributeDef>,
     },
+    /// Enum type: `CREATE TYPE name AS ENUM (labels)`
+    ///
     /// Note: this is PostgreSQL-specific. See 
<https://www.postgresql.org/docs/current/sql-createtype.html>
     Enum { labels: Vec<Ident> },
+    /// Range type: `CREATE TYPE name AS RANGE (options)`
+    ///
+    /// Note: this is PostgreSQL-specific. See 
<https://www.postgresql.org/docs/current/sql-createtype.html>
+    Range {
+        options: Vec<UserDefinedTypeRangeOption>,
+    },
+    /// Base type (SQL definition): `CREATE TYPE name (options)`
+    ///
+    /// Note the lack of `AS` keyword
+    ///
+    /// Note: this is PostgreSQL-specific. See 
<https://www.postgresql.org/docs/current/sql-createtype.html>
+    SqlDefinition {
+        options: Vec<UserDefinedTypeSqlDefinitionOption>,
+    },
 }
 
 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))
             }
-            UserDefinedTypeRepresentation::Enum { labels } => {
-                write!(f, "ENUM ({})", display_comma_separated(labels))
+            Self::Range { options } => {
+                write!(f, "AS RANGE ({})", display_comma_separated(options))
+            }
+            Self::SqlDefinition { options } => {
+                write!(f, "({})", display_comma_separated(options))
             }
         }
     }
@@ -2063,6 +2086,288 @@ 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: <https://www.postgresql.org/docs/current/sql-createtype.html>
+///
+/// # 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, Copy, 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 = <number>`
+    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: <https://www.postgresql.org/docs/current/sql-createtype.html>
+///
+/// # Examples
+/// ```sql
+/// CREATE TYPE mytype (
+///     INPUT = in_func,
+///     OUTPUT = out_func,
+///     ALIGNMENT = int4  -- 4-byte alignment
+/// );
+/// ```
+#[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 {
+    /// 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: <https://www.postgresql.org/docs/current/sql-createtype.html>
+///
+/// # Examples
+/// ```sql
+/// CREATE TYPE mytype (
+///     INPUT = in_func,
+///     OUTPUT = out_func,
+///     STORAGE = plain
+/// );
+/// ```
+#[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 {
+    /// 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: <https://www.postgresql.org/docs/current/sql-createtype.html>
+///
+/// # 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 ... (<options>)` 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: <https://www.postgresql.org/docs/current/sql-createtype.html>
+///
+/// # 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 9a62b71d..aa3fb082 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -59,19 +59,21 @@ 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, AlterTableType, AlterType, AlterTypeAddValue, 
AlterTypeAddValuePosition,
-    AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, ClusteredBy, 
ColumnDef,
-    ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, 
ColumnPolicyProperty,
-    ConstraintCharacteristics, CreateConnector, CreateDomain, CreateExtension, 
CreateFunction,
-    CreateIndex, CreateTable, CreateTrigger, CreateView, Deduplicate, 
DeferrableInitial,
-    DropBehavior, DropExtension, DropFunction, DropTrigger, GeneratedAs, 
GeneratedExpressionMode,
-    IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, 
IdentityPropertyKind,
-    IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, 
KeyOrIndexDisplay, Msck,
-    NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction, 
RenameTableNameKind,
-    ReplicaIdentity, TagsColumnOption, TriggerObjectKind, Truncate,
-    UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, 
ViewColumnDef,
+    Alignment, AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation,
+    AlterPolicyOperation, AlterSchema, AlterSchemaOperation, AlterTable, 
AlterTableAlgorithm,
+    AlterTableLock, AlterTableOperation, AlterTableType, AlterType, 
AlterTypeAddValue,
+    AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, 
AlterTypeRenameValue,
+    ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, 
ColumnPolicy,
+    ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, 
CreateDomain,
+    CreateExtension, CreateFunction, CreateIndex, CreateTable, CreateTrigger, 
CreateView,
+    Deduplicate, DeferrableInitial, DropBehavior, DropExtension, DropFunction, 
DropTrigger,
+    GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty,
+    IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, 
IndexColumn,
+    IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, 
Owner, Partition,
+    ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, 
TagsColumnOption,
+    TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
+    UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, 
UserDefinedTypeRepresentation,
+    UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef,
 };
 pub use self::dml::{Delete, Insert, Update};
 pub use self::operator::{BinaryOperator, UnaryOperator};
@@ -2787,7 +2789,7 @@ impl fmt::Display for Declare {
 }
 
 /// Sql options of a `CREATE TABLE` statement.
-#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
+#[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 {
@@ -4107,7 +4109,7 @@ pub enum Statement {
     /// ```
     CreateType {
         name: ObjectName,
-        representation: UserDefinedTypeRepresentation,
+        representation: Option<UserDefinedTypeRepresentation>,
     },
     /// ```sql
     /// PRAGMA <schema-name>.<pragma-name> = <pragma-value>
@@ -5657,7 +5659,11 @@ impl fmt::Display for Statement {
                 name,
                 representation,
             } => {
-                write!(f, "CREATE TYPE {name} AS {representation}")
+                write!(f, "CREATE TYPE {name}")?;
+                if let Some(repr) = representation {
+                    write!(f, " {repr}")?;
+                }
+                Ok(())
             }
             Statement::Pragma { name, value, is_eq } => {
                 write!(f, "PRAGMA {name}")?;
diff --git a/src/keywords.rs b/src/keywords.rs
index dc4ecd2f..7ff42b41 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,
@@ -868,6 +879,7 @@ define_keywords!(
     SELECT,
     SEMANTIC_VIEW,
     SEMI,
+    SEND,
     SENSITIVE,
     SEPARATOR,
     SEQUENCE,
@@ -936,9 +948,13 @@ define_keywords!(
     STRING,
     STRUCT,
     SUBMULTISET,
+    SUBSCRIPT,
     SUBSTR,
     SUBSTRING,
     SUBSTRING_REGEX,
+    SUBTYPE,
+    SUBTYPE_DIFF,
+    SUBTYPE_OPCLASS,
     SUCCEEDS,
     SUM,
     SUPER,
@@ -1008,6 +1024,8 @@ define_keywords!(
     TSVECTOR,
     TUPLE,
     TYPE,
+    TYPMOD_IN,
+    TYPMOD_OUT,
     UBIGINT,
     UESCAPE,
     UHUGEINT,
@@ -1057,6 +1075,7 @@ define_keywords!(
     VARBINARY,
     VARBIT,
     VARCHAR,
+    VARIABLE,
     VARIABLES,
     VARYING,
     VAR_POP,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index e4a5af72..2744a967 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -17359,20 +17359,59 @@ impl<'a> Parser<'a> {
 
     pub fn parse_create_type(&mut self) -> Result<Statement, ParserError> {
         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: 
Some(UserDefinedTypeRepresentation::SqlDefinition { options }),
+                });
+            }
+
+            // CREATE TYPE name; - no representation
+            return Ok(Statement::CreateType {
+                name,
+                representation: 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)
+    fn parse_create_type_composite(&mut self, name: ObjectName) -> 
Result<Statement, ParserError> {
+        if self.consume_token(&Token::RParen) {
+            // Empty composite type
             return Ok(Statement::CreateType {
                 name,
-                representation: UserDefinedTypeRepresentation::Composite { 
attributes },
+                representation: Some(UserDefinedTypeRepresentation::Composite {
+                    attributes: vec![],
+                }),
             });
         }
 
+        let mut attributes = vec![];
         loop {
             let attr_name = self.parse_identifier()?;
             let attr_data_type = self.parse_data_type()?;
@@ -17386,18 +17425,16 @@ 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,
-            representation: UserDefinedTypeRepresentation::Composite { 
attributes },
+            representation: Some(UserDefinedTypeRepresentation::Composite { 
attributes }),
         })
     }
 
@@ -17411,10 +17448,258 @@ impl<'a> Parser<'a> {
 
         Ok(Statement::CreateType {
             name,
-            representation: UserDefinedTypeRepresentation::Enum { labels },
+            representation: Some(UserDefinedTypeRepresentation::Enum { labels 
}),
+        })
+    }
+
+    /// Parse remainder of `CREATE TYPE AS RANGE` statement
+    ///
+    /// See 
[PostgreSQL](https://www.postgresql.org/docs/current/sql-createtype.html)
+    fn parse_create_type_range(&mut self, name: ObjectName) -> 
Result<Statement, ParserError> {
+        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: Some(UserDefinedTypeRepresentation::Range { 
options }),
         })
     }
 
+    /// Parse a single range option for a `CREATE TYPE AS RANGE` statement
+    fn parse_range_option(&mut self) -> Result<UserDefinedTypeRangeOption, 
ParserError> {
+        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<Vec<UserDefinedTypeSqlDefinitionOption>, 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<UserDefinedTypeSqlDefinitionOption, ParserError> {
+        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<Vec<Ident>, 
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 e85a8ec6..b360f751 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -11710,28 +11710,247 @@ 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!(
+    match verified_stmt("CREATE TYPE mytype") {
         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")])),
+            name,
+            representation,
+        } => {
+            assert_eq!(name.to_string(), "mytype");
+            assert!(representation.is_none());
+        }
+        _ => unreachable!(),
+    }
+
+    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 {
+                Some(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!(),
+            }
+        }
+        _ => unreachable!(),
+    }
+
+    verified_stmt("CREATE TYPE empty AS ()");
+
+    match verified_stmt("CREATE TYPE mood AS ENUM ('happy', 'sad')") {
+        Statement::CreateType {
+            name,
+            representation,
+        } => {
+            assert_eq!(name.to_string(), "mood");
+            match representation {
+                Some(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!(),
+    }
+
+    match verified_stmt("CREATE TYPE int4range AS RANGE (SUBTYPE = INTEGER, 
CANONICAL = fn1)") {
+        Statement::CreateType {
+            name,
+            representation,
+        } => {
+            assert_eq!(name.to_string(), "int4range");
+            match representation {
+                Some(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)");
+
+    match verified_stmt(
+        "CREATE TYPE int4range AS RANGE (SUBTYPE = INTEGER, SUBTYPE_OPCLASS = 
int4_ops)",
+    ) {
+        Statement::CreateType {
+            name,
+            representation,
+        } => {
+            assert_eq!(name.to_string(), "int4range");
+            match representation {
+                Some(UserDefinedTypeRepresentation::Range { options }) => {
+                    assert_eq!(options.len(), 2);
+                    assert!(matches!(
+                        options[0],
+                        
UserDefinedTypeRangeOption::Subtype(DataType::Integer(_))
+                    ));
+                    match &options[1] {
+                        UserDefinedTypeRangeOption::SubtypeOpClass(name) => {
+                            assert_eq!(name.to_string(), "int4_ops");
+                        }
+                        _ => unreachable!("Expected SubtypeOpClass"),
                     }
-                ]
+                }
+                _ => unreachable!(),
             }
-        },
-        create_type
-    );
+        }
+        _ => unreachable!(),
+    }
+
+    match verified_stmt(
+        "CREATE TYPE int4range AS RANGE (SUBTYPE = INTEGER, SUBTYPE_DIFF = 
int4range_subdiff)",
+    ) {
+        Statement::CreateType {
+            name,
+            representation,
+        } => {
+            assert_eq!(name.to_string(), "int4range");
+            match representation {
+                Some(UserDefinedTypeRepresentation::Range { options }) => {
+                    assert_eq!(options.len(), 2);
+                    assert!(matches!(
+                        options[0],
+                        
UserDefinedTypeRangeOption::Subtype(DataType::Integer(_))
+                    ));
+                    match &options[1] {
+                        UserDefinedTypeRangeOption::SubtypeDiff(name) => {
+                            assert_eq!(name.to_string(), "int4range_subdiff");
+                        }
+                        _ => unreachable!("Expected SubtypeDiff"),
+                    }
+                }
+                _ => unreachable!(),
+            }
+        }
+        _ => unreachable!(),
+    }
+
+    match verified_stmt(
+        "CREATE TYPE int4range AS RANGE (SUBTYPE = INTEGER, SUBTYPE_OPCLASS = 
int4_ops, CANONICAL = int4range_canonical, SUBTYPE_DIFF = int4range_subdiff, 
MULTIRANGE_TYPE_NAME = int4multirange)",
+    ) {
+        Statement::CreateType {
+            name,
+            representation,
+        } => {
+            assert_eq!(name.to_string(), "int4range");
+            match representation {
+                Some(UserDefinedTypeRepresentation::Range { options }) => {
+                    assert_eq!(options.len(), 5);
+                    assert!(matches!(
+                        options[0],
+                        
UserDefinedTypeRangeOption::Subtype(DataType::Integer(_))
+                    ));
+                    assert!(matches!(
+                        options[1],
+                        UserDefinedTypeRangeOption::SubtypeOpClass(_)
+                    ));
+                    assert!(matches!(
+                        options[2],
+                        UserDefinedTypeRangeOption::Canonical(_)
+                    ));
+                    assert!(matches!(
+                        options[3],
+                        UserDefinedTypeRangeOption::SubtypeDiff(_)
+                    ));
+                    assert!(matches!(
+                        options[4],
+                        UserDefinedTypeRangeOption::MultirangeTypeName(_)
+                    ));
+                }
+                _ => unreachable!(),
+            }
+        }
+        _ => unreachable!(),
+    }
+
+    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 {
+                Some(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]
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 87cb43ed..4edab706 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -6223,7 +6223,7 @@ fn parse_create_type_as_enum() {
     match statement {
         Statement::CreateType {
             name,
-            representation: UserDefinedTypeRepresentation::Enum { labels },
+            representation: Some(UserDefinedTypeRepresentation::Enum { labels 
}),
         } => {
             assert_eq!("public.my_type", name.to_string());
             assert_eq!(


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to