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]