diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index afe9f228c..6f29d74ec 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -893,7 +893,10 @@ pub enum AlterColumnOperation { data_type: DataType, /// PostgreSQL specific using: Option, + /// Whether the statement included the optional `SET DATA` keywords + had_set: bool, }, + /// `ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ]` /// /// Note: this is a PostgreSQL-specific operation. @@ -914,12 +917,19 @@ impl fmt::Display for AlterColumnOperation { AlterColumnOperation::DropDefault => { write!(f, "DROP DEFAULT") } - AlterColumnOperation::SetDataType { data_type, using } => { + AlterColumnOperation::SetDataType { + data_type, + using, + had_set, + } => { + if *had_set { + write!(f, "SET DATA ")?; + } + write!(f, "TYPE {data_type}")?; if let Some(expr) = using { - write!(f, "SET DATA TYPE {data_type} USING {expr}") - } else { - write!(f, "SET DATA TYPE {data_type}") + write!(f, " USING {expr}")?; } + Ok(()) } AlterColumnOperation::AddGenerated { generated_as, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 78ed772bd..00882602c 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -924,6 +924,7 @@ impl Spanned for AlterColumnOperation { AlterColumnOperation::SetDataType { data_type: _, using, + had_set: _, } => using.as_ref().map_or(Span::empty(), |u| u.span()), AlterColumnOperation::AddGenerated { .. } => Span::empty(), } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 028aa58a7..354f134cf 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1060,6 +1060,22 @@ pub trait Dialect: Debug + Any { fn supports_space_separated_column_options(&self) -> bool { false } + + /// Returns true if the dialect supports `ALTER TABLE tbl ALTER COLUMN col TYPE ` + /// without specifying `SET DATA` before `TYPE`. + /// + /// - [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_ALTER_TABLE.html#r_ALTER_TABLE-synopsis) + /// - [PostgreSQL](https://www.postgresql.org/docs/current/sql-altertable.html) + fn supports_alter_column_type_without_set(&self) -> bool { + false + } + + /// Returns true if the dialect supports `ALTER TABLE tbl ALTER COLUMN col SET DATA TYPE USING ` + /// + /// - [PostgreSQL](https://www.postgresql.org/docs/current/sql-altertable.html) + fn supports_alter_column_type_using(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index a91ab598b..d156e9941 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -258,4 +258,12 @@ impl Dialect for PostgreSqlDialect { fn supports_set_names(&self) -> bool { true } + + fn supports_alter_column_type_without_set(&self) -> bool { + true + } + + fn supports_alter_column_type_using(&self) -> bool { + true + } } diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs index feccca5dd..b3eb78984 100644 --- a/src/dialect/redshift.rs +++ b/src/dialect/redshift.rs @@ -129,4 +129,8 @@ impl Dialect for RedshiftSqlDialect { fn supports_string_literal_backslash_escape(&self) -> bool { true } + + fn supports_alter_column_type_without_set(&self) -> bool { + true + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index a079488f8..f6b92aac1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8734,16 +8734,12 @@ impl<'a> Parser<'a> { } } else if self.parse_keywords(&[Keyword::DROP, Keyword::DEFAULT]) { AlterColumnOperation::DropDefault {} - } else if self.parse_keywords(&[Keyword::SET, Keyword::DATA, Keyword::TYPE]) - || (is_postgresql && self.parse_keyword(Keyword::TYPE)) + } else if self.parse_keywords(&[Keyword::SET, Keyword::DATA, Keyword::TYPE]) { + self.parse_set_data_type(true)? + } else if self.dialect.supports_alter_column_type_without_set() + && self.parse_keyword(Keyword::TYPE) { - let data_type = self.parse_data_type()?; - let using = if is_postgresql && self.parse_keyword(Keyword::USING) { - Some(self.parse_expr()?) - } else { - None - }; - AlterColumnOperation::SetDataType { data_type, using } + self.parse_set_data_type(false)? } else if self.parse_keywords(&[Keyword::ADD, Keyword::GENERATED]) { let generated_as = if self.parse_keyword(Keyword::ALWAYS) { Some(GeneratedAs::Always) @@ -8909,6 +8905,22 @@ impl<'a> Parser<'a> { Ok(operation) } + fn parse_set_data_type(&mut self, had_set: bool) -> Result { + let data_type = self.parse_data_type()?; + let using = if self.dialect.supports_alter_column_type_using() + && self.parse_keyword(Keyword::USING) + { + Some(self.parse_expr()?) + } else { + None + }; + Ok(AlterColumnOperation::SetDataType { + data_type, + using, + had_set, + }) + } + fn parse_part_or_partition(&mut self) -> Result { let keyword = self.expect_one_of_keywords(&[Keyword::PART, Keyword::PARTITION])?; match keyword { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index f6f51459c..379e8140e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -5057,22 +5057,31 @@ fn parse_alter_table_alter_column_type() { AlterColumnOperation::SetDataType { data_type: DataType::Text, using: None, + had_set: true, } ); } _ => unreachable!(), } - let dialect = TestedDialects::new(vec![Box::new(GenericDialect {})]); + let dialects = all_dialects_where(|d| d.supports_alter_column_type_without_set()); + dialects.verified_stmt(&format!("{alter_stmt} ALTER COLUMN is_active TYPE TEXT")); + let dialects = all_dialects_except(|d| d.supports_alter_column_type_without_set()); let res = - dialect.parse_sql_statements(&format!("{alter_stmt} ALTER COLUMN is_active TYPE TEXT")); + dialects.parse_sql_statements(&format!("{alter_stmt} ALTER COLUMN is_active TYPE TEXT")); assert_eq!( ParserError::ParserError("Expected: SET/DROP NOT NULL, SET DEFAULT, or SET DATA TYPE after ALTER COLUMN, found: TYPE".to_string()), res.unwrap_err() ); - let res = dialect.parse_sql_statements(&format!( + let dialects = all_dialects_where(|d| d.supports_alter_column_type_using()); + dialects.verified_stmt(&format!( + "{alter_stmt} ALTER COLUMN is_active SET DATA TYPE TEXT USING 'text'" + )); + + let dialects = all_dialects_except(|d| d.supports_alter_column_type_using()); + let res = dialects.parse_sql_statements(&format!( "{alter_stmt} ALTER COLUMN is_active SET DATA TYPE TEXT USING 'text'" )); assert_eq!( diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 7b0a8c5da..e1a49c69c 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -764,10 +764,7 @@ fn parse_drop_extension() { #[test] fn parse_alter_table_alter_column() { - pg().one_statement_parses_to( - "ALTER TABLE tab ALTER COLUMN is_active TYPE TEXT USING 'text'", - "ALTER TABLE tab ALTER COLUMN is_active SET DATA TYPE TEXT USING 'text'", - ); + pg().verified_stmt("ALTER TABLE tab ALTER COLUMN is_active TYPE TEXT USING 'text'"); match alter_table_op( pg().verified_stmt( @@ -783,6 +780,7 @@ fn parse_alter_table_alter_column() { AlterColumnOperation::SetDataType { data_type: DataType::Text, using: Some(using_expr), + had_set: true, } ); }