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

iffyio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new c75a9926 Add support for Postgres `ALTER TYPE` (#1727)
c75a9926 is described below

commit c75a99262102da1ac795c4272a640d7e36b0e157
Author: Jesse Stuart <[email protected]>
AuthorDate: Mon Feb 17 14:12:59 2025 -0500

    Add support for Postgres `ALTER TYPE` (#1727)
---
 src/ast/ddl.rs              |  89 +++++++++++++++++++++++++++++++++++++
 src/ast/mod.rs              |  24 +++++++---
 src/ast/spans.rs            |   2 +
 src/dialect/postgresql.rs   |  44 -------------------
 src/parser/mod.rs           |  69 +++++++++++++++++++++++++++++
 tests/sqlparser_postgres.rs | 104 +++++++++++++++++++++++++++++++++++++++-----
 6 files changed, 271 insertions(+), 61 deletions(-)

diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index 35e01e61..f9025200 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -640,6 +640,95 @@ impl fmt::Display for AlterIndexOperation {
     }
 }
 
+/// An `ALTER TYPE` statement (`Statement::AlterType`)
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct AlterType {
+    pub name: ObjectName,
+    pub operation: AlterTypeOperation,
+}
+
+/// An [AlterType] operation
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum AlterTypeOperation {
+    Rename(AlterTypeRename),
+    AddValue(AlterTypeAddValue),
+    RenameValue(AlterTypeRenameValue),
+}
+
+/// See [AlterTypeOperation::Rename]
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct AlterTypeRename {
+    pub new_name: Ident,
+}
+
+/// See [AlterTypeOperation::AddValue]
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct AlterTypeAddValue {
+    pub if_not_exists: bool,
+    pub value: Ident,
+    pub position: Option<AlterTypeAddValuePosition>,
+}
+
+/// See [AlterTypeAddValue]
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum AlterTypeAddValuePosition {
+    Before(Ident),
+    After(Ident),
+}
+
+/// See [AlterTypeOperation::RenameValue]
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct AlterTypeRenameValue {
+    pub from: Ident,
+    pub to: Ident,
+}
+
+impl fmt::Display for AlterTypeOperation {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::Rename(AlterTypeRename { new_name }) => {
+                write!(f, "RENAME TO {new_name}")
+            }
+            Self::AddValue(AlterTypeAddValue {
+                if_not_exists,
+                value,
+                position,
+            }) => {
+                write!(f, "ADD VALUE")?;
+                if *if_not_exists {
+                    write!(f, " IF NOT EXISTS")?;
+                }
+                write!(f, " {value}")?;
+                match position {
+                    Some(AlterTypeAddValuePosition::Before(neighbor_value)) => 
{
+                        write!(f, " BEFORE {neighbor_value}")?;
+                    }
+                    Some(AlterTypeAddValuePosition::After(neighbor_value)) => {
+                        write!(f, " AFTER {neighbor_value}")?;
+                    }
+                    None => {}
+                };
+                Ok(())
+            }
+            Self::RenameValue(AlterTypeRenameValue { from, to }) => {
+                write!(f, "RENAME VALUE {from} TO {to}")
+            }
+        }
+    }
+}
+
 /// An `ALTER COLUMN` (`Statement::AlterTable`) operation
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 5835447d..49c7b6fd 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -48,13 +48,15 @@ pub use self::dcl::{
 };
 pub use self::ddl::{
     AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, 
AlterPolicyOperation,
-    AlterTableOperation, ClusteredBy, ColumnDef, ColumnOption, 
ColumnOptionDef, ColumnPolicy,
-    ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, 
CreateFunction, Deduplicate,
-    DeferrableInitial, DropBehavior, GeneratedAs, GeneratedExpressionMode, 
IdentityParameters,
-    IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, 
IdentityPropertyOrder,
-    IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, 
Partition,
-    ProcedureParam, ReferentialAction, TableConstraint, TagsColumnOption,
-    UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, 
ViewColumnDef,
+    AlterTableOperation, AlterType, AlterTypeAddValue, 
AlterTypeAddValuePosition,
+    AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, ClusteredBy, 
ColumnDef,
+    ColumnOption, ColumnOptionDef, ColumnPolicy, ColumnPolicyProperty, 
ConstraintCharacteristics,
+    CreateConnector, CreateFunction, Deduplicate, DeferrableInitial, 
DropBehavior, GeneratedAs,
+    GeneratedExpressionMode, IdentityParameters, IdentityProperty, 
IdentityPropertyFormatKind,
+    IdentityPropertyKind, IdentityPropertyOrder, IndexOption, IndexType, 
KeyOrIndexDisplay,
+    NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction, 
TableConstraint,
+    TagsColumnOption, UserDefinedTypeCompositeAttributeDef, 
UserDefinedTypeRepresentation,
+    ViewColumnDef,
 };
 pub use self::dml::{CreateIndex, CreateTable, Delete, Insert};
 pub use self::operator::{BinaryOperator, UnaryOperator};
@@ -2691,6 +2693,11 @@ pub enum Statement {
         with_options: Vec<SqlOption>,
     },
     /// ```sql
+    /// ALTER TYPE
+    /// See 
[PostgreSQL](https://www.postgresql.org/docs/current/sql-altertype.html)
+    /// ```
+    AlterType(AlterType),
+    /// ```sql
     /// ALTER ROLE
     /// ```
     AlterRole {
@@ -4438,6 +4445,9 @@ impl fmt::Display for Statement {
                 }
                 write!(f, " AS {query}")
             }
+            Statement::AlterType(AlterType { name, operation }) => {
+                write!(f, "ALTER TYPE {name} {operation}")
+            }
             Statement::AlterRole { name, operation } => {
                 write!(f, "ALTER ROLE {name} {operation}")
             }
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 57447869..19147167 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -215,6 +215,7 @@ impl Spanned for Values {
 /// - [Statement::CopyIntoSnowflake]
 /// - [Statement::CreateSecret]
 /// - [Statement::CreateRole]
+/// - [Statement::AlterType]
 /// - [Statement::AlterRole]
 /// - [Statement::AttachDatabase]
 /// - [Statement::AttachDuckDBDatabase]
@@ -427,6 +428,7 @@ impl Spanned for Statement {
                     .chain(with_options.iter().map(|i| i.span())),
             ),
             // These statements need to be implemented
+            Statement::AlterType { .. } => Span::empty(),
             Statement::AlterRole { .. } => Span::empty(),
             Statement::AttachDatabase { .. } => Span::empty(),
             Statement::AttachDuckDBDatabase { .. } => Span::empty(),
diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs
index 74b963e8..60284364 100644
--- a/src/dialect/postgresql.rs
+++ b/src/dialect/postgresql.rs
@@ -28,7 +28,6 @@
 // limitations under the License.
 use log::debug;
 
-use crate::ast::{ObjectName, Statement, UserDefinedTypeRepresentation};
 use crate::dialect::{Dialect, Precedence};
 use crate::keywords::Keyword;
 use crate::parser::{Parser, ParserError};
@@ -135,15 +134,6 @@ impl Dialect for PostgreSqlDialect {
         }
     }
 
-    fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, 
ParserError>> {
-        if parser.parse_keyword(Keyword::CREATE) {
-            parser.prev_token(); // unconsume the CREATE in case we don't end 
up parsing anything
-            parse_create(parser)
-        } else {
-            None
-        }
-    }
-
     fn supports_filter_during_aggregation(&self) -> bool {
         true
     }
@@ -259,37 +249,3 @@ impl Dialect for PostgreSqlDialect {
         true
     }
 }
-
-pub fn parse_create(parser: &mut Parser) -> Option<Result<Statement, 
ParserError>> {
-    let name = parser.maybe_parse(|parser| -> Result<ObjectName, ParserError> {
-        parser.expect_keyword_is(Keyword::CREATE)?;
-        parser.expect_keyword_is(Keyword::TYPE)?;
-        let name = parser.parse_object_name(false)?;
-        parser.expect_keyword_is(Keyword::AS)?;
-        parser.expect_keyword_is(Keyword::ENUM)?;
-        Ok(name)
-    });
-
-    match name {
-        Ok(name) => name.map(|name| parse_create_type_as_enum(parser, name)),
-        Err(e) => Some(Err(e)),
-    }
-}
-
-// https://www.postgresql.org/docs/current/sql-createtype.html
-pub fn parse_create_type_as_enum(
-    parser: &mut Parser,
-    name: ObjectName,
-) -> Result<Statement, ParserError> {
-    if !parser.consume_token(&Token::LParen) {
-        return parser.expected("'(' after CREATE TYPE AS ENUM", 
parser.peek_token());
-    }
-
-    let labels = parser.parse_comma_separated0(|p| p.parse_identifier(), 
Token::RParen)?;
-    parser.expect_token(&Token::RParen)?;
-
-    Ok(Statement::CreateType {
-        name,
-        representation: UserDefinedTypeRepresentation::Enum { labels },
-    })
-}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 903fadaa..88a9281f 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -8042,6 +8042,7 @@ impl<'a> Parser<'a> {
     pub fn parse_alter(&mut self) -> Result<Statement, ParserError> {
         let object_type = self.expect_one_of_keywords(&[
             Keyword::VIEW,
+            Keyword::TYPE,
             Keyword::TABLE,
             Keyword::INDEX,
             Keyword::ROLE,
@@ -8050,6 +8051,7 @@ impl<'a> Parser<'a> {
         ])?;
         match object_type {
             Keyword::VIEW => self.parse_alter_view(),
+            Keyword::TYPE => self.parse_alter_type(),
             Keyword::TABLE => {
                 let if_exists = self.parse_keywords(&[Keyword::IF, 
Keyword::EXISTS]);
                 let only = self.parse_keyword(Keyword::ONLY); // [ ONLY ]
@@ -8122,6 +8124,55 @@ impl<'a> Parser<'a> {
         })
     }
 
+    /// Parse a [Statement::AlterType]
+    pub fn parse_alter_type(&mut self) -> Result<Statement, ParserError> {
+        let name = self.parse_object_name(false)?;
+
+        if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
+            let new_name = self.parse_identifier()?;
+            Ok(Statement::AlterType(AlterType {
+                name,
+                operation: AlterTypeOperation::Rename(AlterTypeRename { 
new_name }),
+            }))
+        } else if self.parse_keywords(&[Keyword::ADD, Keyword::VALUE]) {
+            let if_not_exists = self.parse_keywords(&[Keyword::IF, 
Keyword::NOT, Keyword::EXISTS]);
+            let new_enum_value = self.parse_identifier()?;
+            let position = if self.parse_keyword(Keyword::BEFORE) {
+                
Some(AlterTypeAddValuePosition::Before(self.parse_identifier()?))
+            } else if self.parse_keyword(Keyword::AFTER) {
+                
Some(AlterTypeAddValuePosition::After(self.parse_identifier()?))
+            } else {
+                None
+            };
+
+            Ok(Statement::AlterType(AlterType {
+                name,
+                operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
+                    if_not_exists,
+                    value: new_enum_value,
+                    position,
+                }),
+            }))
+        } else if self.parse_keywords(&[Keyword::RENAME, Keyword::VALUE]) {
+            let existing_enum_value = self.parse_identifier()?;
+            self.expect_keyword(Keyword::TO)?;
+            let new_enum_value = self.parse_identifier()?;
+
+            Ok(Statement::AlterType(AlterType {
+                name,
+                operation: 
AlterTypeOperation::RenameValue(AlterTypeRenameValue {
+                    from: existing_enum_value,
+                    to: new_enum_value,
+                }),
+            }))
+        } else {
+            return self.expected_ref(
+                "{RENAME TO | { RENAME | ADD } VALUE}",
+                self.peek_token_ref(),
+            );
+        }
+    }
+
     /// Parse a `CALL procedure_name(arg1, arg2, ...)`
     /// or `CALL procedure_name` statement
     pub fn parse_call(&mut self) -> Result<Statement, ParserError> {
@@ -14222,6 +14273,10 @@ impl<'a> Parser<'a> {
         let name = self.parse_object_name(false)?;
         self.expect_keyword_is(Keyword::AS)?;
 
+        if self.parse_keyword(Keyword::ENUM) {
+            return self.parse_create_type_enum(name);
+        }
+
         let mut attributes = vec![];
         if !self.consume_token(&Token::LParen) || 
self.consume_token(&Token::RParen) {
             return Ok(Statement::CreateType {
@@ -14258,6 +14313,20 @@ impl<'a> Parser<'a> {
         })
     }
 
+    /// Parse remainder of `CREATE TYPE AS ENUM` statement (see 
[Statement::CreateType] and [Self::parse_create_type])
+    ///
+    /// See 
[PostgreSQL](https://www.postgresql.org/docs/current/sql-createtype.html)
+    pub fn parse_create_type_enum(&mut self, name: ObjectName) -> 
Result<Statement, ParserError> {
+        self.expect_token(&Token::LParen)?;
+        let labels = self.parse_comma_separated0(|p| p.parse_identifier(), 
Token::RParen)?;
+        self.expect_token(&Token::RParen)?;
+
+        Ok(Statement::CreateType {
+            name,
+            representation: UserDefinedTypeRepresentation::Enum { labels },
+        })
+    }
+
     fn parse_parenthesized_identifiers(&mut self) -> Result<Vec<Ident>, 
ParserError> {
         self.expect_token(&Token::LParen)?;
         let partitions = self.parse_comma_separated(|p| p.parse_identifier())?;
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 3a4504eb..b9a5202f 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -5293,15 +5293,8 @@ fn arrow_cast_precedence() {
 
 #[test]
 fn parse_create_type_as_enum() {
-    let statement = pg().one_statement_parses_to(
-        r#"CREATE TYPE public.my_type AS ENUM (
-            'label1',
-            'label2',
-            'label3',
-            'label4'
-        );"#,
-        "CREATE TYPE public.my_type AS ENUM ('label1', 'label2', 'label3', 
'label4')",
-    );
+    let sql = "CREATE TYPE public.my_type AS ENUM ('label1', 'label2', 
'label3', 'label4')";
+    let statement = pg_and_generic().verified_stmt(sql);
     match statement {
         Statement::CreateType {
             name,
@@ -5316,10 +5309,101 @@ fn parse_create_type_as_enum() {
                 labels
             );
         }
-        _ => unreachable!(),
+        _ => unreachable!("{:?} should parse to Statement::CreateType", sql),
     }
 }
 
+#[test]
+fn parse_alter_type() {
+    struct TestCase {
+        sql: &'static str,
+        name: &'static str,
+        operation: AlterTypeOperation,
+    }
+    vec![
+        TestCase {
+            sql: "ALTER TYPE public.my_type RENAME TO my_new_type",
+            name: "public.my_type",
+            operation: AlterTypeOperation::Rename(AlterTypeRename {
+                new_name: Ident::new("my_new_type"),
+            }),
+        },
+        TestCase {
+            sql: "ALTER TYPE public.my_type ADD VALUE IF NOT EXISTS 'label3.5' 
BEFORE 'label4'",
+            name: "public.my_type",
+            operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
+                if_not_exists: true,
+                value: Ident::with_quote('\'', "label3.5"),
+                position: 
Some(AlterTypeAddValuePosition::Before(Ident::with_quote(
+                    '\'', "label4",
+                ))),
+            }),
+        },
+        TestCase {
+            sql: "ALTER TYPE public.my_type ADD VALUE 'label3.5' BEFORE 
'label4'",
+            name: "public.my_type",
+            operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
+                if_not_exists: false,
+                value: Ident::with_quote('\'', "label3.5"),
+                position: 
Some(AlterTypeAddValuePosition::Before(Ident::with_quote(
+                    '\'', "label4",
+                ))),
+            }),
+        },
+        TestCase {
+            sql: "ALTER TYPE public.my_type ADD VALUE IF NOT EXISTS 'label3.5' 
AFTER 'label3'",
+            name: "public.my_type",
+            operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
+                if_not_exists: true,
+                value: Ident::with_quote('\'', "label3.5"),
+                position: 
Some(AlterTypeAddValuePosition::After(Ident::with_quote(
+                    '\'', "label3",
+                ))),
+            }),
+        },
+        TestCase {
+            sql: "ALTER TYPE public.my_type ADD VALUE 'label3.5' AFTER 
'label3'",
+            name: "public.my_type",
+            operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
+                if_not_exists: false,
+                value: Ident::with_quote('\'', "label3.5"),
+                position: 
Some(AlterTypeAddValuePosition::After(Ident::with_quote(
+                    '\'', "label3",
+                ))),
+            }),
+        },
+        TestCase {
+            sql: "ALTER TYPE public.my_type ADD VALUE IF NOT EXISTS 'label5'",
+            name: "public.my_type",
+            operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
+                if_not_exists: true,
+                value: Ident::with_quote('\'', "label5"),
+                position: None,
+            }),
+        },
+        TestCase {
+            sql: "ALTER TYPE public.my_type ADD VALUE 'label5'",
+            name: "public.my_type",
+            operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
+                if_not_exists: false,
+                value: Ident::with_quote('\'', "label5"),
+                position: None,
+            }),
+        },
+    ]
+    .into_iter()
+    .enumerate()
+    .for_each(|(index, tc)| {
+        let statement = pg_and_generic().verified_stmt(tc.sql);
+        if let Statement::AlterType(AlterType { name, operation }) = statement 
{
+            assert_eq!(tc.name, name.to_string(), "TestCase[{index}].name");
+            assert_eq!(tc.operation, operation, "TestCase[{index}].operation");
+        } else {
+            unreachable!("{:?} should parse to Statement::AlterType", tc.sql);
+        }
+    });
+}
+
 #[test]
 fn parse_bitstring_literal() {
     let select = pg_and_generic().verified_only_select("SELECT B'111'");


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

Reply via email to