diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 0c4f93e64..d01d61515 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -4402,6 +4402,142 @@ impl Spanned for DropExtension { } } +/// CREATE COLLATION statement. +/// Note: this is a PostgreSQL-specific statement. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct CreateCollation { + /// Whether `IF NOT EXISTS` was specified. + pub if_not_exists: bool, + /// Name of the collation being created. + pub name: ObjectName, + /// Source definition for the collation. + pub definition: CreateCollationDefinition, +} + +/// Definition forms supported by `CREATE COLLATION`. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum CreateCollationDefinition { + /// Create from an existing collation. + /// + /// ```sql + /// CREATE COLLATION name FROM existing_collation + /// ``` + From(ObjectName), + /// Create with an option list. + /// + /// ```sql + /// CREATE COLLATION name (key = value, ...) + /// ``` + Options(Vec), +} + +impl fmt::Display for CreateCollation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "CREATE COLLATION {if_not_exists}{name}", + if_not_exists = if self.if_not_exists { + "IF NOT EXISTS " + } else { + "" + }, + name = self.name + )?; + match &self.definition { + CreateCollationDefinition::From(existing_collation) => { + write!(f, " FROM {existing_collation}") + } + CreateCollationDefinition::Options(options) => { + write!(f, " ({})", display_comma_separated(options)) + } + } + } +} + +impl Spanned for CreateCollation { + fn span(&self) -> Span { + Span::empty() + } +} + +/// ALTER COLLATION statement. +/// Note: this is a PostgreSQL-specific statement. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct AlterCollation { + /// Name of the collation being altered. + pub name: ObjectName, + /// The operation to perform on the collation. + pub operation: AlterCollationOperation, +} + +/// Operations supported by `ALTER COLLATION`. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum AlterCollationOperation { + /// Rename the collation. + /// + /// ```sql + /// ALTER COLLATION name RENAME TO new_name + /// ``` + RenameTo { + /// New collation name. + new_name: Ident, + }, + /// Change the collation owner. + /// + /// ```sql + /// ALTER COLLATION name OWNER TO role_name + /// ``` + OwnerTo(Owner), + /// Move the collation to another schema. + /// + /// ```sql + /// ALTER COLLATION name SET SCHEMA new_schema + /// ``` + SetSchema { + /// Target schema name. + schema_name: ObjectName, + }, + /// Refresh collation version metadata. + /// + /// ```sql + /// ALTER COLLATION name REFRESH VERSION + /// ``` + RefreshVersion, +} + +impl fmt::Display for AlterCollationOperation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AlterCollationOperation::RenameTo { new_name } => write!(f, "RENAME TO {new_name}"), + AlterCollationOperation::OwnerTo(owner) => write!(f, "OWNER TO {owner}"), + AlterCollationOperation::SetSchema { schema_name } => { + write!(f, "SET SCHEMA {schema_name}") + } + AlterCollationOperation::RefreshVersion => write!(f, "REFRESH VERSION"), + } + } +} + +impl fmt::Display for AlterCollation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ALTER COLLATION {} {}", self.name, self.operation) + } +} + +impl Spanned for AlterCollation { + fn span(&self) -> Span { + Span::empty() + } +} + /// Table type for ALTER TABLE statements. /// Used to distinguish between regular tables, Iceberg tables, and Dynamic tables. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 1e430171e..24df862e9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -60,18 +60,19 @@ pub use self::dcl::{ SetConfigValue, Use, }; pub use self::ddl::{ - Alignment, AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterOperator, - AlterOperatorClass, AlterOperatorClassOperation, AlterOperatorFamily, - AlterOperatorFamilyOperation, AlterOperatorOperation, AlterPolicy, 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, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreatePolicy, - CreatePolicyCommand, CreatePolicyType, CreateTable, CreateTrigger, CreateView, Deduplicate, - DeferrableInitial, DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, - DropOperatorFamily, DropOperatorSignature, DropPolicy, DropTrigger, ForValues, GeneratedAs, + Alignment, AlterCollation, AlterCollationOperation, AlterColumnOperation, AlterConnectorOwner, + AlterIndexOperation, AlterOperator, AlterOperatorClass, AlterOperatorClassOperation, + AlterOperatorFamily, AlterOperatorFamilyOperation, AlterOperatorOperation, AlterPolicy, + AlterPolicyOperation, AlterSchema, AlterSchemaOperation, AlterTable, AlterTableAlgorithm, + AlterTableLock, AlterTableOperation, AlterTableType, AlterType, AlterTypeAddValue, + AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, + ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, + ColumnPolicyProperty, ConstraintCharacteristics, CreateCollation, CreateCollationDefinition, + CreateConnector, CreateDomain, CreateExtension, CreateFunction, CreateIndex, CreateOperator, + CreateOperatorClass, CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType, + CreateTable, CreateTrigger, CreateView, Deduplicate, DeferrableInitial, DropBehavior, + DropExtension, DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily, + DropOperatorSignature, DropPolicy, DropTrigger, ForValues, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes, OperatorClassItem, @@ -2437,6 +2438,8 @@ impl fmt::Display for ShowCreateObject { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] /// Objects that can be targeted by a `COMMENT` statement. pub enum CommentObject { + /// A collation. + Collation, /// A table column. Column, /// A database. @@ -2472,6 +2475,7 @@ pub enum CommentObject { impl fmt::Display for CommentObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { + CommentObject::Collation => f.write_str("COLLATION"), CommentObject::Column => f.write_str("COLUMN"), CommentObject::Database => f.write_str("DATABASE"), CommentObject::Domain => f.write_str("DOMAIN"), @@ -3744,6 +3748,11 @@ pub enum Statement { /// ``` AlterType(AlterType), /// ```sql + /// ALTER COLLATION + /// ``` + /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-altercollation.html) + AlterCollation(AlterCollation), + /// ```sql /// ALTER OPERATOR /// ``` /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-alteroperator.html) @@ -3940,6 +3949,12 @@ pub enum Statement { /// Note: this is a PostgreSQL-specific statement, CreateExtension(CreateExtension), /// ```sql + /// CREATE COLLATION + /// ``` + /// Note: this is a PostgreSQL-specific statement. + /// + CreateCollation(CreateCollation), + /// ```sql /// DROP EXTENSION [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ] /// ``` /// Note: this is a PostgreSQL-specific statement. @@ -5389,6 +5404,7 @@ impl fmt::Display for Statement { } Statement::CreateIndex(create_index) => create_index.fmt(f), Statement::CreateExtension(create_extension) => write!(f, "{create_extension}"), + Statement::CreateCollation(create_collation) => write!(f, "{create_collation}"), Statement::DropExtension(drop_extension) => write!(f, "{drop_extension}"), Statement::DropOperator(drop_operator) => write!(f, "{drop_operator}"), Statement::DropOperatorFamily(drop_operator_family) => { @@ -5465,6 +5481,7 @@ impl fmt::Display for Statement { Statement::AlterType(AlterType { name, operation }) => { write!(f, "ALTER TYPE {name} {operation}") } + Statement::AlterCollation(alter_collation) => write!(f, "{alter_collation}"), Statement::AlterOperator(alter_operator) => write!(f, "{alter_operator}"), Statement::AlterOperatorFamily(alter_operator_family) => { write!(f, "{alter_operator_family}") @@ -8230,6 +8247,8 @@ impl fmt::Display for HavingBoundKind { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] /// Types of database objects referenced by DDL statements. pub enum ObjectType { + /// A collation. + Collation, /// A table. Table, /// A view. @@ -8259,6 +8278,7 @@ pub enum ObjectType { impl fmt::Display for ObjectType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match self { + ObjectType::Collation => "COLLATION", ObjectType::Table => "TABLE", ObjectType::View => "VIEW", ObjectType::MaterializedView => "MATERIALIZED VIEW", @@ -11816,6 +11836,12 @@ impl From for Statement { } } +impl From for Statement { + fn from(c: CreateCollation) -> Self { + Self::CreateCollation(c) + } +} + impl From for Statement { fn from(de: DropExtension) -> Self { Self::DropExtension(de) @@ -11924,6 +11950,12 @@ impl From for Statement { } } +impl From for Statement { + fn from(a: AlterCollation) -> Self { + Self::AlterCollation(a) + } +} + impl From for Statement { fn from(a: AlterOperator) -> Self { Self::AlterOperator(a) diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 0b95c3ed7..3b34ebc85 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -264,6 +264,8 @@ impl Spanned for Values { /// - [Statement::DropSecret] /// - [Statement::Declare] /// - [Statement::CreateExtension] +/// - [Statement::CreateCollation] +/// - [Statement::AlterCollation] /// - [Statement::Fetch] /// - [Statement::Flush] /// - [Statement::Discard] @@ -376,6 +378,7 @@ impl Spanned for Statement { Statement::CreateIndex(create_index) => create_index.span(), Statement::CreateRole(create_role) => create_role.span(), Statement::CreateExtension(create_extension) => create_extension.span(), + Statement::CreateCollation(create_collation) => create_collation.span(), Statement::DropExtension(drop_extension) => drop_extension.span(), Statement::DropOperator(drop_operator) => drop_operator.span(), Statement::DropOperatorFamily(drop_operator_family) => drop_operator_family.span(), @@ -403,6 +406,7 @@ impl Spanned for Statement { ), // These statements need to be implemented Statement::AlterType { .. } => Span::empty(), + Statement::AlterCollation { .. } => Span::empty(), Statement::AlterOperator { .. } => Span::empty(), Statement::AlterOperatorFamily { .. } => Span::empty(), Statement::AlterOperatorClass { .. } => Span::empty(), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index bea566bbe..53602b2b8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -898,6 +898,9 @@ impl<'a> Parser<'a> { let token = self.next_token(); let (object_type, object_name) = match token.token { + Token::Word(w) if w.keyword == Keyword::COLLATION => { + (CommentObject::Collation, self.parse_object_name(false)?) + } Token::Word(w) if w.keyword == Keyword::COLUMN => { (CommentObject::Column, self.parse_object_name(false)?) } @@ -5151,6 +5154,8 @@ impl<'a> Parser<'a> { self.parse_create_role().map(Into::into) } else if self.parse_keyword(Keyword::SEQUENCE) { self.parse_create_sequence(temporary) + } else if self.parse_keyword(Keyword::COLLATION) { + self.parse_create_collation().map(Into::into) } else if self.parse_keyword(Keyword::TYPE) { self.parse_create_type() } else if self.parse_keyword(Keyword::PROCEDURE) { @@ -7200,6 +7205,8 @@ impl<'a> Parser<'a> { let object_type = if self.parse_keyword(Keyword::TABLE) { ObjectType::Table + } else if self.parse_keyword(Keyword::COLLATION) { + ObjectType::Collation } else if self.parse_keyword(Keyword::VIEW) { ObjectType::View } else if self.parse_keywords(&[Keyword::MATERIALIZED, Keyword::VIEW]) { @@ -7249,7 +7256,7 @@ impl<'a> Parser<'a> { }; } else { return self.expected_ref( - "CONNECTOR, DATABASE, EXTENSION, FUNCTION, INDEX, OPERATOR, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW or USER after DROP", + "COLLATION, CONNECTOR, DATABASE, EXTENSION, FUNCTION, INDEX, OPERATOR, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW or USER after DROP", self.peek_token_ref(), ); }; @@ -7990,6 +7997,31 @@ impl<'a> Parser<'a> { }) } + /// Parse a PostgreSQL-specific [Statement::CreateCollation] statement. + pub fn parse_create_collation(&mut self) -> Result { + let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); + let name = self.parse_object_name(false)?; + + let definition = if self.parse_keyword(Keyword::FROM) { + CreateCollationDefinition::From(self.parse_object_name(false)?) + } else if self.consume_token(&Token::LParen) { + let options = self.parse_comma_separated(Parser::parse_sql_option)?; + self.expect_token(&Token::RParen)?; + CreateCollationDefinition::Options(options) + } else { + return self.expected_ref( + "FROM or parenthesized option list after CREATE COLLATION name", + self.peek_token_ref(), + ); + }; + + Ok(CreateCollation { + if_not_exists, + name, + definition, + }) + } + /// Parse a PostgreSQL-specific [Statement::DropExtension] statement. pub fn parse_drop_extension(&mut self) -> Result { let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); @@ -10431,6 +10463,7 @@ impl<'a> Parser<'a> { let object_type = self.expect_one_of_keywords(&[ Keyword::VIEW, Keyword::TYPE, + Keyword::COLLATION, Keyword::TABLE, Keyword::INDEX, Keyword::ROLE, @@ -10449,6 +10482,7 @@ impl<'a> Parser<'a> { } Keyword::VIEW => self.parse_alter_view(), Keyword::TYPE => self.parse_alter_type(), + Keyword::COLLATION => self.parse_alter_collation().map(Into::into), Keyword::TABLE => self.parse_alter_table(false), Keyword::ICEBERG => { self.expect_keyword(Keyword::TABLE)?; @@ -10487,7 +10521,7 @@ impl<'a> Parser<'a> { Keyword::USER => self.parse_alter_user().map(Into::into), // unreachable because expect_one_of_keywords used above unexpected_keyword => Err(ParserError::ParserError( - format!("Internal parser error: expected any of {{VIEW, TYPE, TABLE, INDEX, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"), + format!("Internal parser error: expected any of {{VIEW, TYPE, COLLATION, TABLE, INDEX, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"), )), } } @@ -10603,6 +10637,33 @@ impl<'a> Parser<'a> { } } + /// Parse a [Statement::AlterCollation]. + /// + /// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-altercollation.html) + pub fn parse_alter_collation(&mut self) -> Result { + let name = self.parse_object_name(false)?; + let operation = if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) { + AlterCollationOperation::RenameTo { + new_name: self.parse_identifier()?, + } + } else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) { + AlterCollationOperation::OwnerTo(self.parse_owner()?) + } else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) { + AlterCollationOperation::SetSchema { + schema_name: self.parse_object_name(false)?, + } + } else if self.parse_keywords(&[Keyword::REFRESH, Keyword::VERSION]) { + AlterCollationOperation::RefreshVersion + } else { + return self.expected_ref( + "RENAME TO, OWNER TO, SET SCHEMA, or REFRESH VERSION after ALTER COLLATION", + self.peek_token_ref(), + ); + }; + + Ok(AlterCollation { name, operation }) + } + /// Parse a [Statement::AlterOperator] /// /// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-alteroperator.html) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 982bf1088..97eb40472 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -15207,6 +15207,7 @@ fn parse_comments() { // https://www.postgresql.org/docs/current/sql-comment.html let object_types = [ + ("COLLATION", CommentObject::Collation), ("COLUMN", CommentObject::Column), ("DATABASE", CommentObject::Database), ("DOMAIN", CommentObject::Domain), diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 7c19f51e5..c105cd0e8 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -811,6 +811,171 @@ fn parse_drop_extension() { ); } +#[test] +fn parse_create_collation() { + assert_eq!( + pg_and_generic() + .verified_stmt("CREATE COLLATION test3 (provider = icu, lc_collate = 'en_US.utf8')",), + Statement::CreateCollation(CreateCollation { + if_not_exists: false, + name: ObjectName::from(vec![Ident::new("test3")]), + definition: CreateCollationDefinition::Options(vec![ + SqlOption::KeyValue { + key: Ident::new("provider"), + value: Expr::Identifier(Ident::new("icu")), + }, + SqlOption::KeyValue { + key: Ident::new("lc_collate"), + value: Expr::Value( + Value::SingleQuotedString("en_US.utf8".to_string()).with_empty_span(), + ), + }, + ]), + }) + ); + + assert_eq!( + pg_and_generic().verified_stmt("CREATE COLLATION test4 FROM nonsense"), + Statement::CreateCollation(CreateCollation { + if_not_exists: false, + name: ObjectName::from(vec![Ident::new("test4")]), + definition: CreateCollationDefinition::From(ObjectName::from(vec![Ident::new( + "nonsense", + )])), + }) + ); + + assert_eq!( + pg_and_generic() + .verified_stmt("CREATE COLLATION testx (provider = icu, locale = 'nonsense-nowhere')"), + Statement::CreateCollation(CreateCollation { + if_not_exists: false, + name: ObjectName::from(vec![Ident::new("testx")]), + definition: CreateCollationDefinition::Options(vec![ + SqlOption::KeyValue { + key: Ident::new("provider"), + value: Expr::Identifier(Ident::new("icu")), + }, + SqlOption::KeyValue { + key: Ident::new("locale"), + value: Expr::Value( + Value::SingleQuotedString("nonsense-nowhere".to_string()).with_empty_span(), + ), + }, + ]), + }) + ); +} + +#[test] +fn parse_alter_collation() { + assert_eq!( + pg_and_generic().verified_stmt("ALTER COLLATION test1 RENAME TO test11"), + Statement::AlterCollation(AlterCollation { + name: ObjectName::from(vec![Ident::new("test1")]), + operation: AlterCollationOperation::RenameTo { + new_name: Ident::new("test11"), + }, + }) + ); + + assert_eq!( + pg_and_generic().verified_stmt("ALTER COLLATION test11 OWNER TO regress_test_role"), + Statement::AlterCollation(AlterCollation { + name: ObjectName::from(vec![Ident::new("test11")]), + operation: AlterCollationOperation::OwnerTo(Owner::Ident(Ident::new( + "regress_test_role", + ))), + }) + ); + + assert_eq!( + pg_and_generic().verified_stmt("ALTER COLLATION test11 SET SCHEMA test_schema"), + Statement::AlterCollation(AlterCollation { + name: ObjectName::from(vec![Ident::new("test11")]), + operation: AlterCollationOperation::SetSchema { + schema_name: ObjectName::from(vec![Ident::new("test_schema")]), + }, + }) + ); + + assert_eq!( + pg_and_generic().verified_stmt("ALTER COLLATION \"en-x-icu\" REFRESH VERSION"), + Statement::AlterCollation(AlterCollation { + name: ObjectName::from(vec![Ident::with_quote('"', "en-x-icu")]), + operation: AlterCollationOperation::RefreshVersion, + }) + ); +} + +#[test] +fn parse_drop_and_comment_collation_ast() { + assert_eq!( + pg_and_generic().verified_stmt("DROP COLLATION test0"), + Statement::Drop { + object_type: ObjectType::Collation, + if_exists: false, + names: vec![ObjectName::from(vec![Ident::new("test0")])], + cascade: false, + restrict: false, + purge: false, + temporary: false, + table: None, + } + ); + + assert_eq!( + pg_and_generic().verified_stmt("DROP COLLATION IF EXISTS test0"), + Statement::Drop { + object_type: ObjectType::Collation, + if_exists: true, + names: vec![ObjectName::from(vec![Ident::new("test0")])], + cascade: false, + restrict: false, + purge: false, + temporary: false, + table: None, + } + ); + + assert_eq!( + pg_and_generic().verified_stmt("COMMENT ON COLLATION test0 IS 'US English'"), + Statement::Comment { + object_type: CommentObject::Collation, + object_name: ObjectName::from(vec![Ident::new("test0")]), + comment: Some("US English".to_string()), + if_exists: false, + } + ); +} + +#[test] +fn parse_collation_statements_roundtrip() { + let statements = [ + "CREATE COLLATION test3 (provider = icu, lc_collate = 'en_US.utf8')", + "CREATE COLLATION testx (provider = icu, locale = 'nonsense-nowhere')", + "CREATE COLLATION testx (provider = icu, locale = '@colStrength=primary;nonsense=yes')", + "DROP COLLATION testx", + "CREATE COLLATION test4 FROM nonsense", + "CREATE COLLATION test5 FROM test0", + "ALTER COLLATION test1 RENAME TO test11", + "ALTER COLLATION test0 RENAME TO test11", + "ALTER COLLATION test1 RENAME TO test22", + "ALTER COLLATION test11 OWNER TO regress_test_role", + "ALTER COLLATION test11 OWNER TO nonsense", + "ALTER COLLATION test11 SET SCHEMA test_schema", + "COMMENT ON COLLATION test0 IS 'US English'", + "DROP COLLATION test0, test_schema.test11, test5", + "DROP COLLATION test0", + "DROP COLLATION IF EXISTS test0", + "ALTER COLLATION \"en-x-icu\" REFRESH VERSION", + ]; + + for sql in statements { + pg_and_generic().verified_stmt(sql); + } +} + #[test] fn parse_alter_table_alter_column() { pg().verified_stmt("ALTER TABLE tab ALTER COLUMN is_active TYPE TEXT USING 'text'");