Skip to content
56 changes: 56 additions & 0 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4599,3 +4599,59 @@ impl Spanned for AlterOperatorFamily {
Span::empty()
}
}

/// `ALTER OPERATOR CLASS` statement
/// See <https://www.postgresql.org/docs/current/sql-alteropclass.html>
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct AlterOperatorClass {
/// Operator class name (can be schema-qualified)
pub name: ObjectName,
/// Index method (btree, hash, gist, gin, etc.)
pub using: Ident,
/// The operation to perform
pub operation: AlterOperatorClassOperation,
}

/// An [AlterOperatorClass] 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 AlterOperatorClassOperation {
/// `RENAME TO new_name`
RenameTo { new_name: ObjectName },
/// `OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER }`
OwnerTo(Owner),
/// `SET SCHEMA new_schema`
SetSchema { schema_name: ObjectName },
}

impl fmt::Display for AlterOperatorClass {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ALTER OPERATOR CLASS {} USING {}", self.name, self.using)?;
write!(f, " {}", self.operation)
}
}

impl fmt::Display for AlterOperatorClassOperation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AlterOperatorClassOperation::RenameTo { new_name } => {
write!(f, "RENAME TO {new_name}")
}
AlterOperatorClassOperation::OwnerTo(owner) => {
write!(f, "OWNER TO {owner}")
}
AlterOperatorClassOperation::SetSchema { schema_name } => {
write!(f, "SET SCHEMA {schema_name}")
}
}
}
}

impl Spanned for AlterOperatorClass {
fn span(&self) -> Span {
Span::empty()
}
}
40 changes: 24 additions & 16 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,22 @@ pub use self::dcl::{
};
pub use self::ddl::{
Alignment, AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterOperator,
AlterOperatorFamily, AlterOperatorFamilyOperation, AlterOperatorOperation,
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, CreateTable, CreateTrigger, CreateView, Deduplicate, DeferrableInitial,
DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily,
DropOperatorSignature, DropTrigger, GeneratedAs, GeneratedExpressionMode, IdentityParameters,
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption,
OperatorArgTypes, OperatorClassItem, OperatorFamilyDropItem, OperatorFamilyItem,
OperatorOption, OperatorPurpose, Owner, Partition, ProcedureParam, ReferentialAction,
RenameTableNameKind, ReplicaIdentity, TagsColumnOption, TriggerObjectKind, Truncate,
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength,
AlterOperatorClass, AlterOperatorClassOperation, AlterOperatorFamily,
AlterOperatorFamilyOperation, AlterOperatorOperation, 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, CreateTable, CreateTrigger, CreateView, Deduplicate,
DeferrableInitial, DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass,
DropOperatorFamily, DropOperatorSignature, DropTrigger, GeneratedAs, GeneratedExpressionMode,
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck,
NullsDistinctOption, OperatorArgTypes, OperatorClassItem, OperatorFamilyDropItem,
OperatorFamilyItem, OperatorOption, OperatorPurpose, Owner, Partition, ProcedureParam,
ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption, TriggerObjectKind,
Truncate, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength,
UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption,
UserDefinedTypeStorage, ViewColumnDef,
};
Expand Down Expand Up @@ -3418,6 +3418,11 @@ pub enum Statement {
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-alteropfamily.html)
AlterOperatorFamily(AlterOperatorFamily),
/// ```sql
/// ALTER OPERATOR CLASS
/// ```
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-alteropclass.html)
AlterOperatorClass(AlterOperatorClass),
/// ```sql
/// ALTER ROLE
/// ```
AlterRole {
Expand Down Expand Up @@ -4982,6 +4987,9 @@ impl fmt::Display for Statement {
Statement::AlterOperatorFamily(alter_operator_family) => {
write!(f, "{alter_operator_family}")
}
Statement::AlterOperatorClass(alter_operator_class) => {
write!(f, "{alter_operator_class}")
}
Statement::AlterRole { name, operation } => {
write!(f, "ALTER ROLE {name} {operation}")
}
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ impl Spanned for Statement {
Statement::AlterType { .. } => Span::empty(),
Statement::AlterOperator { .. } => Span::empty(),
Statement::AlterOperatorFamily { .. } => Span::empty(),
Statement::AlterOperatorClass { .. } => Span::empty(),
Statement::AlterRole { .. } => Span::empty(),
Statement::AlterSession { .. } => Span::empty(),
Statement::AttachDatabase { .. } => Span::empty(),
Expand Down
30 changes: 30 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9901,6 +9901,8 @@ impl<'a> Parser<'a> {
Keyword::OPERATOR => {
if self.parse_keyword(Keyword::FAMILY) {
self.parse_alter_operator_family()
} else if self.parse_keyword(Keyword::CLASS) {
self.parse_alter_operator_class()
} else {
self.parse_alter_operator()
}
Expand Down Expand Up @@ -10300,6 +10302,34 @@ impl<'a> Parser<'a> {
}))
}

pub fn parse_alter_operator_class(&mut self) -> Result<Statement, ParserError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add documentation for the function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I add the deny missing doc to clippy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added the deny missing doc, but there is an impressive amount of missing doc. I am trying to add the missing documentation so to make it impossible for missing documentation in future development.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, it was quite an effort but now #![forbid(missing_docs)] is in place and everything public is documented

let name = self.parse_object_name(false)?;
self.expect_keyword(Keyword::USING)?;
let using = self.parse_identifier()?;

let operation = if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
let new_name = self.parse_object_name(false)?;
AlterOperatorClassOperation::RenameTo { new_name }
} else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) {
let owner = self.parse_owner()?;
AlterOperatorClassOperation::OwnerTo(owner)
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
let schema_name = self.parse_object_name(false)?;
AlterOperatorClassOperation::SetSchema { schema_name }
} else {
return self.expected_ref(
"RENAME TO, OWNER TO, or SET SCHEMA after ALTER OPERATOR CLASS",
self.peek_token_ref(),
);
};

Ok(Statement::AlterOperatorClass(AlterOperatorClass {
name,
using,
operation,
}))
}

// Parse a [Statement::AlterSchema]
// ALTER SCHEMA [ IF EXISTS ] schema_name
pub fn parse_alter_schema(&mut self) -> Result<Statement, ParserError> {
Expand Down
150 changes: 150 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7531,6 +7531,156 @@ fn parse_alter_operator_family() {
.is_err());
}

#[test]
fn parse_alter_operator_class() {
// Test ALTER OPERATOR CLASS ... RENAME TO
let sql = "ALTER OPERATOR CLASS int_ops USING btree RENAME TO integer_ops";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::AlterOperatorClass(AlterOperatorClass {
name: ObjectName::from(vec![Ident::new("int_ops")]),
using: Ident::new("btree"),
operation: AlterOperatorClassOperation::RenameTo {
new_name: ObjectName::from(vec![Ident::new("integer_ops")]),
},
})
);

// Test ALTER OPERATOR CLASS ... OWNER TO
let sql = "ALTER OPERATOR CLASS int_ops USING btree OWNER TO joe";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::AlterOperatorClass(AlterOperatorClass {
name: ObjectName::from(vec![Ident::new("int_ops")]),
using: Ident::new("btree"),
operation: AlterOperatorClassOperation::OwnerTo(Owner::Ident(Ident::new("joe"))),
})
);

// Test ALTER OPERATOR CLASS ... OWNER TO CURRENT_USER
let sql = "ALTER OPERATOR CLASS int_ops USING btree OWNER TO CURRENT_USER";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::AlterOperatorClass(AlterOperatorClass {
name: ObjectName::from(vec![Ident::new("int_ops")]),
using: Ident::new("btree"),
operation: AlterOperatorClassOperation::OwnerTo(Owner::CurrentUser),
})
);

// Test ALTER OPERATOR CLASS ... SET SCHEMA
let sql = "ALTER OPERATOR CLASS int_ops USING btree SET SCHEMA new_schema";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::AlterOperatorClass(AlterOperatorClass {
name: ObjectName::from(vec![Ident::new("int_ops")]),
using: Ident::new("btree"),
operation: AlterOperatorClassOperation::SetSchema {
schema_name: ObjectName::from(vec![Ident::new("new_schema")]),
},
})
);

// Test with schema-qualified operator class name
let sql = "ALTER OPERATOR CLASS myschema.int_ops USING btree RENAME TO integer_ops";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::AlterOperatorClass(AlterOperatorClass {
name: ObjectName::from(vec![Ident::new("myschema"), Ident::new("int_ops")]),
using: Ident::new("btree"),
operation: AlterOperatorClassOperation::RenameTo {
new_name: ObjectName::from(vec![Ident::new("integer_ops")]),
},
})
);

// Test with different index methods
for index_method in &["hash", "gist", "gin", "spgist", "brin"] {
let sql = format!(
"ALTER OPERATOR CLASS int_ops USING {} RENAME TO integer_ops",
index_method
);
pg_and_generic().verified_stmt(&sql);
}

// Test error cases
// Missing USING clause
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS int_ops RENAME TO integer_ops")
.is_err());

// Invalid operation
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS int_ops USING btree INVALID_OPERATION")
.is_err());

// Missing new name for RENAME TO
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS int_ops USING btree RENAME TO")
.is_err());

// Missing owner for OWNER TO
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS int_ops USING btree OWNER TO")
.is_err());

// Missing schema for SET SCHEMA
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS int_ops USING btree SET SCHEMA")
.is_err());

// Invalid new name
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS int_ops USING btree RENAME TO 123invalid")
.is_err());

// Invalid owner
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS int_ops USING btree OWNER TO 123invalid")
.is_err());

// Invalid schema name
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS int_ops USING btree SET SCHEMA 123invalid")
.is_err());

// Missing operator class name
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS USING btree RENAME TO integer_ops")
.is_err());

// Extra tokens at end
assert!(pg()
.parse_sql_statements(
"ALTER OPERATOR CLASS int_ops USING btree RENAME TO integer_ops EXTRA"
)
.is_err());

// Missing index method
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS int_ops RENAME TO integer_ops")
.is_err());

// Invalid index method
assert!(pg()
.parse_sql_statements("ALTER OPERATOR CLASS int_ops USING 123invalid RENAME TO integer_ops")
.is_err());

// Trying to use ADD operation (only valid for OPERATOR FAMILY)
assert!(pg()
.parse_sql_statements(
"ALTER OPERATOR CLASS int_ops USING btree ADD OPERATOR 1 < (INT4, INT2)"
)
.is_err());

// Trying to use DROP operation (only valid for OPERATOR FAMILY)
assert!(pg()
.parse_sql_statements(
"ALTER OPERATOR CLASS int_ops USING btree DROP OPERATOR 1 (INT4, INT2)"
)
.is_err());
}

#[test]
fn parse_drop_operator_family() {
for if_exists in [true, false] {
Expand Down
Loading