Added support for ALTER OPERATOR syntax (#2114)
Some checks failed
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / compile (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled

This commit is contained in:
Luca Cappelletti 2025-12-04 14:17:31 +01:00 committed by GitHub
parent effaac5c39
commit 326f111808
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 585 additions and 142 deletions

View file

@ -995,6 +995,103 @@ impl fmt::Display for AlterTypeOperation {
}
}
/// `ALTER OPERATOR` statement
/// See <https://www.postgresql.org/docs/current/sql-alteroperator.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 AlterOperator {
/// Operator name (can be schema-qualified)
pub name: ObjectName,
/// Left operand type (`None` if no left operand)
pub left_type: Option<DataType>,
/// Right operand type
pub right_type: DataType,
/// The operation to perform
pub operation: AlterOperatorOperation,
}
/// An [AlterOperator] 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 AlterOperatorOperation {
/// `OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER }`
OwnerTo(Owner),
/// `SET SCHEMA new_schema`
SetSchema { schema_name: ObjectName },
/// `SET ( options )`
Set {
/// List of operator options to set
options: Vec<OperatorOption>,
},
}
/// Option for `ALTER OPERATOR SET` 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 OperatorOption {
/// `RESTRICT = { res_proc | NONE }`
Restrict(Option<ObjectName>),
/// `JOIN = { join_proc | NONE }`
Join(Option<ObjectName>),
/// `COMMUTATOR = com_op`
Commutator(ObjectName),
/// `NEGATOR = neg_op`
Negator(ObjectName),
/// `HASHES`
Hashes,
/// `MERGES`
Merges,
}
impl fmt::Display for AlterOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ALTER OPERATOR {} (", self.name)?;
if let Some(left_type) = &self.left_type {
write!(f, "{}", left_type)?;
} else {
write!(f, "NONE")?;
}
write!(f, ", {}) {}", self.right_type, self.operation)
}
}
impl fmt::Display for AlterOperatorOperation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::OwnerTo(owner) => write!(f, "OWNER TO {}", owner),
Self::SetSchema { schema_name } => write!(f, "SET SCHEMA {}", schema_name),
Self::Set { options } => {
write!(f, "SET (")?;
for (i, option) in options.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", option)?;
}
write!(f, ")")
}
}
}
}
impl fmt::Display for OperatorOption {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Restrict(Some(proc_name)) => write!(f, "RESTRICT = {}", proc_name),
Self::Restrict(None) => write!(f, "RESTRICT = NONE"),
Self::Join(Some(proc_name)) => write!(f, "JOIN = {}", proc_name),
Self::Join(None) => write!(f, "JOIN = NONE"),
Self::Commutator(op_name) => write!(f, "COMMUTATOR = {}", op_name),
Self::Negator(op_name) => write!(f, "NEGATOR = {}", op_name),
Self::Hashes => write!(f, "HASHES"),
Self::Merges => write!(f, "MERGES"),
}
}
}
/// An `ALTER COLUMN` (`Statement::AlterTable`) operation
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -1790,7 +1887,7 @@ impl fmt::Display for ColumnOption {
GeneratedAs::Always => "ALWAYS",
GeneratedAs::ByDefault => "BY DEFAULT",
// ExpStored goes with an expression, handled above
GeneratedAs::ExpStored => unreachable!(),
GeneratedAs::ExpStored => "",
};
write!(f, "GENERATED {when} AS IDENTITY")?;
if sequence_options.is_some() {
@ -3981,18 +4078,8 @@ pub struct CreateOperator {
pub left_arg: Option<DataType>,
/// RIGHTARG parameter (right operand type)
pub right_arg: Option<DataType>,
/// COMMUTATOR parameter (commutator operator)
pub commutator: Option<ObjectName>,
/// NEGATOR parameter (negator operator)
pub negator: Option<ObjectName>,
/// RESTRICT parameter (restriction selectivity function)
pub restrict: Option<ObjectName>,
/// JOIN parameter (join selectivity function)
pub join: Option<ObjectName>,
/// HASHES flag
pub hashes: bool,
/// MERGES flag
pub merges: bool,
/// Operator options (COMMUTATOR, NEGATOR, RESTRICT, JOIN, HASHES, MERGES)
pub options: Vec<OperatorOption>,
}
/// CREATE OPERATOR FAMILY statement
@ -4044,23 +4131,9 @@ impl fmt::Display for CreateOperator {
if let Some(right_arg) = &self.right_arg {
params.push(format!("RIGHTARG = {}", right_arg));
}
if let Some(commutator) = &self.commutator {
params.push(format!("COMMUTATOR = {}", commutator));
}
if let Some(negator) = &self.negator {
params.push(format!("NEGATOR = {}", negator));
}
if let Some(restrict) = &self.restrict {
params.push(format!("RESTRICT = {}", restrict));
}
if let Some(join) = &self.join {
params.push(format!("JOIN = {}", join));
}
if self.hashes {
params.push("HASHES".to_string());
}
if self.merges {
params.push("MERGES".to_string());
for option in &self.options {
params.push(option.to_string());
}
write!(f, "{}", params.join(", "))?;

View file

@ -99,15 +99,15 @@ impl fmt::Display for StageParamsObject {
impl fmt::Display for StageLoadSelectItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.alias.is_some() {
write!(f, "{}.", self.alias.as_ref().unwrap())?;
if let Some(alias) = &self.alias {
write!(f, "{alias}.")?;
}
write!(f, "${}", self.file_col_num)?;
if self.element.is_some() {
write!(f, ":{}", self.element.as_ref().unwrap())?;
if let Some(element) = &self.element {
write!(f, ":{element}")?;
}
if self.item_as.is_some() {
write!(f, " AS {}", self.item_as.as_ref().unwrap())?;
if let Some(item_as) = &self.item_as {
write!(f, " AS {item_as}")?;
}
Ok(())
}

View file

@ -59,23 +59,23 @@ pub use self::dcl::{
AlterRoleOperation, CreateRole, ResetConfig, RoleOption, SecondaryRoles, SetConfigValue, Use,
};
pub use self::ddl::{
Alignment, AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation,
AlterPolicyOperation, AlterSchema, AlterSchemaOperation, AlterTable, AlterTableAlgorithm,
AlterTableLock, AlterTableOperation, AlterTableType, AlterType, AlterTypeAddValue,
AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue,
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy,
ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain,
Alignment, AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterOperator,
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, OperatorPurpose, Owner, Partition, ProcedureParam,
ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption, TriggerObjectKind,
Truncate, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength,
UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption,
UserDefinedTypeStorage, ViewColumnDef,
OperatorArgTypes, OperatorClassItem, OperatorOption, OperatorPurpose, Owner, Partition,
ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption,
TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation,
UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef,
};
pub use self::dml::{Delete, Insert, Update};
pub use self::operator::{BinaryOperator, UnaryOperator};
@ -3396,6 +3396,11 @@ pub enum Statement {
/// ```
AlterType(AlterType),
/// ```sql
/// ALTER OPERATOR
/// ```
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-alteroperator.html)
AlterOperator(AlterOperator),
/// ```sql
/// ALTER ROLE
/// ```
AlterRole {
@ -4971,6 +4976,7 @@ impl fmt::Display for Statement {
Statement::AlterType(AlterType { name, operation }) => {
write!(f, "ALTER TYPE {name} {operation}")
}
Statement::AlterOperator(alter_operator) => write!(f, "{alter_operator}"),
Statement::AlterRole { name, operation } => {
write!(f, "ALTER ROLE {name} {operation}")
}
@ -9814,8 +9820,8 @@ impl fmt::Display for ShowCharset {
} else {
write!(f, " CHARACTER SET")?;
}
if self.filter.is_some() {
write!(f, " {}", self.filter.as_ref().unwrap())?;
if let Some(filter) = &self.filter {
write!(f, " {filter}")?;
}
Ok(())
}

View file

@ -252,6 +252,7 @@ impl Spanned for Values {
/// - [Statement::CreateSecret]
/// - [Statement::CreateRole]
/// - [Statement::AlterType]
/// - [Statement::AlterOperator]
/// - [Statement::AlterRole]
/// - [Statement::AttachDatabase]
/// - [Statement::AttachDuckDBDatabase]
@ -401,6 +402,7 @@ impl Spanned for Statement {
),
// These statements need to be implemented
Statement::AlterType { .. } => Span::empty(),
Statement::AlterOperator { .. } => Span::empty(),
Statement::AlterRole { .. } => Span::empty(),
Statement::AlterSession { .. } => Span::empty(),
Statement::AttachDatabase { .. } => Span::empty(),

View file

@ -67,6 +67,15 @@ macro_rules! define_keywords {
pub const ALL_KEYWORDS: &[&str] = &[
$($ident),*
];
impl core::fmt::Display for Keyword {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Keyword::NoKeyword => write!(f, "NoKeyword"),
$(Keyword::$ident => write!(f, "{}", $ident),)*
}
}
}
};
}

View file

@ -153,6 +153,7 @@
// Splitting complex nodes (expressions, statements, types) into separate types
// would bloat the API and hide intent. Extra memory is a worthwhile tradeoff.
#![allow(clippy::large_enum_variant)]
#![forbid(clippy::unreachable)]
// Allow proc-macros to find this crate
extern crate self as sqlparser;

View file

@ -1194,7 +1194,11 @@ impl<'a> Parser<'a> {
let mut id_parts: Vec<Ident> = vec![match t {
Token::Word(w) => w.into_ident(next_token.span),
Token::SingleQuotedString(s) => Ident::with_quote('\'', s),
_ => unreachable!(), // We matched above
_ => {
return Err(ParserError::ParserError(
"Internal parser error: unexpected token type".to_string(),
))
}
}];
while self.consume_token(&Token::Period) {
@ -1641,7 +1645,11 @@ impl<'a> Parser<'a> {
Token::PGSquareRoot => UnaryOperator::PGSquareRoot,
Token::PGCubeRoot => UnaryOperator::PGCubeRoot,
Token::AtSign => UnaryOperator::PGAbs,
_ => unreachable!(),
_ => {
return Err(ParserError::ParserError(
"Internal parser error: unexpected unary operator token".to_string(),
))
}
};
Ok(Expr::UnaryOp {
op,
@ -1709,18 +1717,22 @@ impl<'a> Parser<'a> {
Ok(Expr::Value(self.parse_value()?))
}
Token::LParen => {
let expr = if let Some(expr) = self.try_parse_expr_sub_query()? {
expr
} else if let Some(lambda) = self.try_parse_lambda()? {
return Ok(lambda);
} else {
let exprs = self.parse_comma_separated(Parser::parse_expr)?;
match exprs.len() {
0 => unreachable!(), // parse_comma_separated ensures 1 or more
1 => Expr::Nested(Box::new(exprs.into_iter().next().unwrap())),
_ => Expr::Tuple(exprs),
}
};
let expr =
if let Some(expr) = self.try_parse_expr_sub_query()? {
expr
} else if let Some(lambda) = self.try_parse_lambda()? {
return Ok(lambda);
} else {
let exprs = self.parse_comma_separated(Parser::parse_expr)?;
match exprs.len() {
0 => return Err(ParserError::ParserError(
"Internal parser error: parse_comma_separated returned empty list"
.to_string(),
)),
1 => Expr::Nested(Box::new(exprs.into_iter().next().unwrap())),
_ => Expr::Tuple(exprs),
}
};
self.expect_token(&Token::RParen)?;
Ok(expr)
}
@ -3591,7 +3603,9 @@ impl<'a> Parser<'a> {
right: Box::new(right),
is_some: keyword == Keyword::SOME,
},
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: expected any of {{ALL, ANY, SOME}}, got {unexpected_keyword:?}"),
)),
})
} else {
Ok(Expr::BinaryOp {
@ -5590,13 +5604,14 @@ impl<'a> Parser<'a> {
} else {
None
};
let option = self
.parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT])
.map(|keyword| match keyword {
Keyword::CASCADE => ReferentialAction::Cascade,
Keyword::RESTRICT => ReferentialAction::Restrict,
_ => unreachable!(),
});
let option = match self.parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) {
Some(Keyword::CASCADE) => Some(ReferentialAction::Cascade),
Some(Keyword::RESTRICT) => Some(ReferentialAction::Restrict),
Some(unexpected_keyword) => return Err(ParserError::ParserError(
format!("Internal parser error: expected any of {{CASCADE, RESTRICT}}, got {unexpected_keyword:?}"),
)),
None => None,
};
Ok(Statement::DropTrigger(DropTrigger {
if_exists,
trigger_name,
@ -5646,7 +5661,9 @@ impl<'a> Parser<'a> {
match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? {
Keyword::ROW => TriggerObject::Row,
Keyword::STATEMENT => TriggerObject::Statement,
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in ROW/STATEMENT"),
)),
};
Some(if include_each {
@ -5709,7 +5726,9 @@ impl<'a> Parser<'a> {
Keyword::INSTEAD => self
.expect_keyword_is(Keyword::OF)
.map(|_| TriggerPeriod::InsteadOf)?,
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in trigger period"),
)),
},
)
}
@ -5733,7 +5752,9 @@ impl<'a> Parser<'a> {
}
Keyword::DELETE => TriggerEvent::Delete,
Keyword::TRUNCATE => TriggerEvent::Truncate,
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in trigger event"),
)),
},
)
}
@ -5767,7 +5788,9 @@ impl<'a> Parser<'a> {
{
Keyword::FUNCTION => TriggerExecBodyType::Function,
Keyword::PROCEDURE => TriggerExecBodyType::Procedure,
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in trigger exec body"),
)),
},
func_desc: self.parse_function_desc()?,
})
@ -6284,7 +6307,9 @@ impl<'a> Parser<'a> {
Some(Keyword::CURRENT_USER) => Owner::CurrentUser,
Some(Keyword::CURRENT_ROLE) => Owner::CurrentRole,
Some(Keyword::SESSION_USER) => Owner::SessionUser,
Some(_) => unreachable!(),
Some(unexpected_keyword) => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in owner"),
)),
None => {
match self.parse_identifier() {
Ok(ident) => Owner::Ident(ident),
@ -6346,7 +6371,9 @@ impl<'a> Parser<'a> {
Some(match keyword {
Keyword::PERMISSIVE => CreatePolicyType::Permissive,
Keyword::RESTRICTIVE => CreatePolicyType::Restrictive,
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in policy type"),
)),
})
} else {
None
@ -6366,7 +6393,9 @@ impl<'a> Parser<'a> {
Keyword::INSERT => CreatePolicyCommand::Insert,
Keyword::UPDATE => CreatePolicyCommand::Update,
Keyword::DELETE => CreatePolicyCommand::Delete,
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in policy command"),
)),
})
} else {
None
@ -6479,12 +6508,7 @@ impl<'a> Parser<'a> {
let mut is_procedure = false;
let mut left_arg: Option<DataType> = None;
let mut right_arg: Option<DataType> = None;
let mut commutator: Option<ObjectName> = None;
let mut negator: Option<ObjectName> = None;
let mut restrict: Option<ObjectName> = None;
let mut join: Option<ObjectName> = None;
let mut hashes = false;
let mut merges = false;
let mut options: Vec<OperatorOption> = Vec::new();
loop {
let keyword = self.expect_one_of_keywords(&[
@ -6501,11 +6525,11 @@ impl<'a> Parser<'a> {
])?;
match keyword {
Keyword::HASHES if !hashes => {
hashes = true;
Keyword::HASHES if !options.iter().any(|o| matches!(o, OperatorOption::Hashes)) => {
options.push(OperatorOption::Hashes);
}
Keyword::MERGES if !merges => {
merges = true;
Keyword::MERGES if !options.iter().any(|o| matches!(o, OperatorOption::Merges)) => {
options.push(OperatorOption::Merges);
}
Keyword::FUNCTION | Keyword::PROCEDURE if function.is_none() => {
self.expect_token(&Token::Eq)?;
@ -6520,33 +6544,49 @@ impl<'a> Parser<'a> {
self.expect_token(&Token::Eq)?;
right_arg = Some(self.parse_data_type()?);
}
Keyword::COMMUTATOR if commutator.is_none() => {
Keyword::COMMUTATOR
if !options
.iter()
.any(|o| matches!(o, OperatorOption::Commutator(_))) =>
{
self.expect_token(&Token::Eq)?;
if self.parse_keyword(Keyword::OPERATOR) {
self.expect_token(&Token::LParen)?;
commutator = Some(self.parse_operator_name()?);
let op = self.parse_operator_name()?;
self.expect_token(&Token::RParen)?;
options.push(OperatorOption::Commutator(op));
} else {
commutator = Some(self.parse_operator_name()?);
options.push(OperatorOption::Commutator(self.parse_operator_name()?));
}
}
Keyword::NEGATOR if negator.is_none() => {
Keyword::NEGATOR
if !options
.iter()
.any(|o| matches!(o, OperatorOption::Negator(_))) =>
{
self.expect_token(&Token::Eq)?;
if self.parse_keyword(Keyword::OPERATOR) {
self.expect_token(&Token::LParen)?;
negator = Some(self.parse_operator_name()?);
let op = self.parse_operator_name()?;
self.expect_token(&Token::RParen)?;
options.push(OperatorOption::Negator(op));
} else {
negator = Some(self.parse_operator_name()?);
options.push(OperatorOption::Negator(self.parse_operator_name()?));
}
}
Keyword::RESTRICT if restrict.is_none() => {
Keyword::RESTRICT
if !options
.iter()
.any(|o| matches!(o, OperatorOption::Restrict(_))) =>
{
self.expect_token(&Token::Eq)?;
restrict = Some(self.parse_object_name(false)?);
options.push(OperatorOption::Restrict(Some(
self.parse_object_name(false)?,
)));
}
Keyword::JOIN if join.is_none() => {
Keyword::JOIN if !options.iter().any(|o| matches!(o, OperatorOption::Join(_))) => {
self.expect_token(&Token::Eq)?;
join = Some(self.parse_object_name(false)?);
options.push(OperatorOption::Join(Some(self.parse_object_name(false)?)));
}
_ => {
return Err(ParserError::ParserError(format!(
@ -6575,12 +6615,7 @@ impl<'a> Parser<'a> {
is_procedure,
left_arg,
right_arg,
commutator,
negator,
restrict,
join,
hashes,
merges,
options,
}))
}
@ -6997,7 +7032,9 @@ impl<'a> Parser<'a> {
match keyword {
Keyword::WITH => Some(true),
Keyword::WITHOUT => Some(false),
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in cursor hold"),
)),
}
}
None => None,
@ -9764,7 +9801,9 @@ impl<'a> Parser<'a> {
Keyword::PART => Ok(Partition::Part(self.parse_expr()?)),
Keyword::PARTITION => Ok(Partition::Expr(self.parse_expr()?)),
// unreachable because expect_one_of_keywords used above
_ => unreachable!(),
unexpected_keyword => Err(ParserError::ParserError(
format!("Internal parser error: expected any of {{PART, PARTITION}}, got {unexpected_keyword:?}"),
)),
}
}
@ -9780,6 +9819,7 @@ impl<'a> Parser<'a> {
Keyword::ICEBERG,
Keyword::SCHEMA,
Keyword::USER,
Keyword::OPERATOR,
])?;
match object_type {
Keyword::SCHEMA => {
@ -9812,12 +9852,15 @@ impl<'a> Parser<'a> {
operation,
})
}
Keyword::OPERATOR => self.parse_alter_operator(),
Keyword::ROLE => self.parse_alter_role(),
Keyword::POLICY => self.parse_alter_policy(),
Keyword::CONNECTOR => self.parse_alter_connector(),
Keyword::USER => self.parse_alter_user(),
// unreachable because expect_one_of_keywords used above
_ => unreachable!(),
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:?}"),
)),
}
}
@ -9931,6 +9974,116 @@ impl<'a> Parser<'a> {
}
}
/// Parse a [Statement::AlterOperator]
///
/// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-alteroperator.html)
pub fn parse_alter_operator(&mut self) -> Result<Statement, ParserError> {
let name = self.parse_operator_name()?;
// Parse (left_type, right_type)
self.expect_token(&Token::LParen)?;
let left_type = if self.parse_keyword(Keyword::NONE) {
None
} else {
Some(self.parse_data_type()?)
};
self.expect_token(&Token::Comma)?;
let right_type = self.parse_data_type()?;
self.expect_token(&Token::RParen)?;
// Parse the operation
let operation = if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) {
let owner = if self.parse_keyword(Keyword::CURRENT_ROLE) {
Owner::CurrentRole
} else if self.parse_keyword(Keyword::CURRENT_USER) {
Owner::CurrentUser
} else if self.parse_keyword(Keyword::SESSION_USER) {
Owner::SessionUser
} else {
Owner::Ident(self.parse_identifier()?)
};
AlterOperatorOperation::OwnerTo(owner)
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
let schema_name = self.parse_object_name(false)?;
AlterOperatorOperation::SetSchema { schema_name }
} else if self.parse_keyword(Keyword::SET) {
self.expect_token(&Token::LParen)?;
let mut options = Vec::new();
loop {
let keyword = self.expect_one_of_keywords(&[
Keyword::RESTRICT,
Keyword::JOIN,
Keyword::COMMUTATOR,
Keyword::NEGATOR,
Keyword::HASHES,
Keyword::MERGES,
])?;
match keyword {
Keyword::RESTRICT => {
self.expect_token(&Token::Eq)?;
let proc_name = if self.parse_keyword(Keyword::NONE) {
None
} else {
Some(self.parse_object_name(false)?)
};
options.push(OperatorOption::Restrict(proc_name));
}
Keyword::JOIN => {
self.expect_token(&Token::Eq)?;
let proc_name = if self.parse_keyword(Keyword::NONE) {
None
} else {
Some(self.parse_object_name(false)?)
};
options.push(OperatorOption::Join(proc_name));
}
Keyword::COMMUTATOR => {
self.expect_token(&Token::Eq)?;
let op_name = self.parse_operator_name()?;
options.push(OperatorOption::Commutator(op_name));
}
Keyword::NEGATOR => {
self.expect_token(&Token::Eq)?;
let op_name = self.parse_operator_name()?;
options.push(OperatorOption::Negator(op_name));
}
Keyword::HASHES => {
options.push(OperatorOption::Hashes);
}
Keyword::MERGES => {
options.push(OperatorOption::Merges);
}
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in operator option"),
)),
}
if !self.consume_token(&Token::Comma) {
break;
}
}
self.expect_token(&Token::RParen)?;
AlterOperatorOperation::Set { options }
} else {
return self.expected_ref(
"OWNER TO, SET SCHEMA, or SET after ALTER OPERATOR",
self.peek_token_ref(),
);
};
Ok(Statement::AlterOperator(AlterOperator {
name,
left_type,
right_type,
operation,
}))
}
// Parse a [Statement::AlterSchema]
// ALTER SCHEMA [ IF EXISTS ] schema_name
pub fn parse_alter_schema(&mut self) -> Result<Statement, ParserError> {
@ -14175,7 +14328,9 @@ impl<'a> Parser<'a> {
table = match kw {
Keyword::PIVOT => self.parse_pivot_table_factor(table)?,
Keyword::UNPIVOT => self.parse_unpivot_table_factor(table)?,
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in pivot/unpivot"),
)),
}
}
return Ok(table);
@ -14433,7 +14588,9 @@ impl<'a> Parser<'a> {
table = match kw {
Keyword::PIVOT => self.parse_pivot_table_factor(table)?,
Keyword::UNPIVOT => self.parse_unpivot_table_factor(table)?,
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in pivot/unpivot"),
)),
}
}
@ -15532,7 +15689,9 @@ impl<'a> Parser<'a> {
}
}
Some(Keyword::TABLE) | None => Some(GrantObjects::Tables(objects?)),
_ => unreachable!(),
Some(unexpected_keyword) => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in grant objects"),
)),
}
}
} else {
@ -16402,7 +16561,9 @@ impl<'a> Parser<'a> {
let kind = match self.expect_one_of_keywords(&[Keyword::MIN, Keyword::MAX])? {
Keyword::MIN => HavingBoundKind::Min,
Keyword::MAX => HavingBoundKind::Max,
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in having bound"),
)),
};
clauses.push(FunctionArgumentClause::Having(HavingBound(
kind,
@ -16930,7 +17091,9 @@ impl<'a> Parser<'a> {
let lock_type = match self.expect_one_of_keywords(&[Keyword::UPDATE, Keyword::SHARE])? {
Keyword::UPDATE => LockType::Update,
Keyword::SHARE => LockType::Share,
_ => unreachable!(),
unexpected_keyword => return Err(ParserError::ParserError(
format!("Internal parser error: expected any of {{UPDATE, SHARE}}, got {unexpected_keyword:?}"),
)),
};
let of = if self.parse_keyword(Keyword::OF) {
Some(self.parse_object_name(false)?)

View file

@ -6715,24 +6715,26 @@ fn parse_create_operator() {
length: 255,
unit: None
}))),
commutator: Some(ObjectName::from(vec![
Ident::new("schema"),
Ident::new(">")
])),
negator: Some(ObjectName::from(vec![
Ident::new("schema"),
Ident::new("<=")
])),
restrict: Some(ObjectName::from(vec![
Ident::new("myschema"),
Ident::new("sel_func")
])),
join: Some(ObjectName::from(vec![
Ident::new("myschema"),
Ident::new("join_func")
])),
hashes: true,
merges: true,
options: vec![
OperatorOption::Commutator(ObjectName::from(vec![
Ident::new("schema"),
Ident::new(">")
])),
OperatorOption::Negator(ObjectName::from(vec![
Ident::new("schema"),
Ident::new("<=")
])),
OperatorOption::Restrict(Some(ObjectName::from(vec![
Ident::new("myschema"),
Ident::new("sel_func")
]))),
OperatorOption::Join(Some(ObjectName::from(vec![
Ident::new("myschema"),
Ident::new("join_func")
]))),
OperatorOption::Hashes,
OperatorOption::Merges,
],
})
);
@ -6748,12 +6750,7 @@ fn parse_create_operator() {
is_procedure: false,
left_arg: None,
right_arg: None,
commutator: None,
negator: None,
restrict: None,
join: None,
hashes: false,
merges: false,
options: vec![],
})
);
}
@ -6778,13 +6775,9 @@ fn parse_create_operator() {
),
] {
match pg().verified_stmt(&format!("CREATE OPERATOR {name} (FUNCTION = f)")) {
Statement::CreateOperator(CreateOperator {
name,
hashes: false,
merges: false,
..
}) => {
Statement::CreateOperator(CreateOperator { name, options, .. }) => {
assert_eq!(name, expected_name);
assert!(options.is_empty());
}
_ => unreachable!(),
}
@ -6920,6 +6913,202 @@ fn parse_drop_operator() {
assert!(pg().parse_sql_statements(sql).is_err());
}
#[test]
fn parse_alter_operator() {
use sqlparser::ast::{AlterOperator, AlterOperatorOperation, OperatorOption, Owner};
// Test ALTER OPERATOR ... OWNER TO with different owner types
for (owner_sql, owner_ast) in [
("joe", Owner::Ident(Ident::new("joe"))),
("CURRENT_USER", Owner::CurrentUser),
("CURRENT_ROLE", Owner::CurrentRole),
("SESSION_USER", Owner::SessionUser),
] {
for (op_name, op_name_ast, left_type_sql, left_type_ast, right_type_sql, right_type_ast) in [
(
"+",
ObjectName::from(vec![Ident::new("+")]),
"INTEGER",
Some(DataType::Integer(None)),
"INTEGER",
DataType::Integer(None),
),
(
"~",
ObjectName::from(vec![Ident::new("~")]),
"NONE",
None,
"BIT",
DataType::Bit(None),
),
(
"@@",
ObjectName::from(vec![Ident::new("@@")]),
"TEXT",
Some(DataType::Text),
"TEXT",
DataType::Text,
),
] {
let sql = format!(
"ALTER OPERATOR {} ({}, {}) OWNER TO {}",
op_name, left_type_sql, right_type_sql, owner_sql
);
assert_eq!(
pg_and_generic().verified_stmt(&sql),
Statement::AlterOperator(AlterOperator {
name: op_name_ast.clone(),
left_type: left_type_ast.clone(),
right_type: right_type_ast.clone(),
operation: AlterOperatorOperation::OwnerTo(owner_ast.clone()),
})
);
}
}
// Test ALTER OPERATOR ... SET SCHEMA
for (op_name, op_name_ast, schema_name, schema_name_ast) in [
(
"+",
ObjectName::from(vec![Ident::new("+")]),
"new_schema",
ObjectName::from(vec![Ident::new("new_schema")]),
),
(
"myschema.@@",
ObjectName::from(vec![Ident::new("myschema"), Ident::new("@@")]),
"other_schema",
ObjectName::from(vec![Ident::new("other_schema")]),
),
] {
let sql = format!(
"ALTER OPERATOR {} (TEXT, TEXT) SET SCHEMA {}",
op_name, schema_name
);
assert_eq!(
pg_and_generic().verified_stmt(&sql),
Statement::AlterOperator(AlterOperator {
name: op_name_ast,
left_type: Some(DataType::Text),
right_type: DataType::Text,
operation: AlterOperatorOperation::SetSchema {
schema_name: schema_name_ast,
},
})
);
}
// Test ALTER OPERATOR ... SET with RESTRICT and JOIN
for (restrict_val, restrict_ast, join_val, join_ast) in [
(
"_int_contsel",
Some(ObjectName::from(vec![Ident::new("_int_contsel")])),
"_int_contjoinsel",
Some(ObjectName::from(vec![Ident::new("_int_contjoinsel")])),
),
(
"NONE",
None,
"my_joinsel",
Some(ObjectName::from(vec![Ident::new("my_joinsel")])),
),
(
"my_sel",
Some(ObjectName::from(vec![Ident::new("my_sel")])),
"NONE",
None,
),
] {
let sql = format!(
"ALTER OPERATOR && (TEXT, TEXT) SET (RESTRICT = {}, JOIN = {})",
restrict_val, join_val
);
assert_eq!(
pg_and_generic().verified_stmt(&sql),
Statement::AlterOperator(AlterOperator {
name: ObjectName::from(vec![Ident::new("&&")]),
left_type: Some(DataType::Text),
right_type: DataType::Text,
operation: AlterOperatorOperation::Set {
options: vec![
OperatorOption::Restrict(restrict_ast),
OperatorOption::Join(join_ast),
],
},
})
);
}
// Test ALTER OPERATOR ... SET with COMMUTATOR and NEGATOR
for (operator, commutator, negator) in [("&&", "&&", ">"), ("+", "+", "-"), ("<", "<", ">=")] {
let sql = format!(
"ALTER OPERATOR {} (INTEGER, INTEGER) SET (COMMUTATOR = {}, NEGATOR = {})",
operator, commutator, negator
);
assert_eq!(
pg_and_generic().verified_stmt(&sql),
Statement::AlterOperator(AlterOperator {
name: ObjectName::from(vec![Ident::new(operator)]),
left_type: Some(DataType::Integer(None)),
right_type: DataType::Integer(None),
operation: AlterOperatorOperation::Set {
options: vec![
OperatorOption::Commutator(ObjectName::from(vec![Ident::new(commutator)])),
OperatorOption::Negator(ObjectName::from(vec![Ident::new(negator)])),
],
},
})
);
}
// Test ALTER OPERATOR ... SET with HASHES and MERGES (individually and combined)
for (operator, options_sql, options_ast) in [
("=", "HASHES", vec![OperatorOption::Hashes]),
("<", "MERGES", vec![OperatorOption::Merges]),
(
"<=",
"HASHES, MERGES",
vec![OperatorOption::Hashes, OperatorOption::Merges],
),
] {
let sql = format!(
"ALTER OPERATOR {} (INTEGER, INTEGER) SET ({})",
operator, options_sql
);
assert_eq!(
pg_and_generic().verified_stmt(&sql),
Statement::AlterOperator(AlterOperator {
name: ObjectName::from(vec![Ident::new(operator)]),
left_type: Some(DataType::Integer(None)),
right_type: DataType::Integer(None),
operation: AlterOperatorOperation::Set {
options: options_ast
},
})
);
}
// Test ALTER OPERATOR ... SET with multiple options combined
let sql =
"ALTER OPERATOR + (INTEGER, INTEGER) SET (COMMUTATOR = +, NEGATOR = -, HASHES, MERGES)";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::AlterOperator(AlterOperator {
name: ObjectName::from(vec![Ident::new("+")]),
left_type: Some(DataType::Integer(None)),
right_type: DataType::Integer(None),
operation: AlterOperatorOperation::Set {
options: vec![
OperatorOption::Commutator(ObjectName::from(vec![Ident::new("+")])),
OperatorOption::Negator(ObjectName::from(vec![Ident::new("-")])),
OperatorOption::Hashes,
OperatorOption::Merges,
],
},
})
);
}
#[test]
fn parse_drop_operator_family() {
for if_exists in [true, false] {