From 472209e087463dc8f05d22ea8b1ed2ec1645b91a Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 2 Oct 2026 18:15:02 +0200 Subject: [PATCH 1/3] PostgreSQL: parse `ALTER TRIGGER RENAME TO` and `DEPENDS ON EXTENSION` --- src/ast/ddl.rs | 74 +++++++++++++++++++++++++++++++++ src/ast/mod.rs | 43 +++++++++++-------- src/ast/spans.rs | 1 + src/parser/alter.rs | 53 ++++++++++++++++++++++-- src/parser/mod.rs | 4 +- tests/sqlparser_common.rs | 87 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 239 insertions(+), 23 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 5d5b964d17..1b563fcd00 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -601,6 +601,43 @@ impl fmt::Display for AlterPolicyOperation { } } +/// An `ALTER TRIGGER` (`Statement::AlterTrigger`) operation +/// +/// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-altertrigger.html) +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum AlterTriggerOperation { + /// `RENAME TO new_name` + Rename { + /// The new identifier for the trigger. + new_name: Ident, + }, + /// `[ NO ] DEPENDS ON EXTENSION extension_name` + DependsOnExtension { + /// `true` when `NO DEPENDS ON EXTENSION`. + no: bool, + /// Extension name. + extension_name: ObjectName, + }, +} + +impl fmt::Display for AlterTriggerOperation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AlterTriggerOperation::Rename { new_name } => { + write!(f, "RENAME TO {new_name}") + } + AlterTriggerOperation::DependsOnExtension { no, extension_name } => { + if *no { + write!(f, "NO ")?; + } + write!(f, "DEPENDS ON EXTENSION {extension_name}") + } + } + } +} + /// [MySQL] `ALTER TABLE` algorithm. /// /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/alter-table.html @@ -6041,6 +6078,43 @@ impl From for crate::ast::Statement { } } +/// ALTER TRIGGER statement. +/// +/// ```sql +/// ALTER TRIGGER ON +/// ``` +/// (Postgresql-specific) +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct AlterTrigger { + /// Trigger name to alter. + pub name: Ident, + /// Target table name the trigger is defined on. + #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] + pub table_name: ObjectName, + /// Operation specific to the trigger alteration. + pub operation: AlterTriggerOperation, +} + +impl fmt::Display for AlterTrigger { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "ALTER TRIGGER {name} ON {table_name} {operation}", + name = self.name, + table_name = self.table_name, + operation = self.operation + ) + } +} + +impl From for crate::ast::Statement { + fn from(v: AlterTrigger) -> Self { + crate::ast::Statement::AlterTrigger(v) + } +} + /// A `CREATE FOREIGN TABLE` statement. /// /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createforeigntable.html) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index eb5bc6cc75..9bd7625aae 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -68,24 +68,25 @@ pub use self::ddl::{ AlterOperatorFamily, AlterOperatorFamilyOperation, AlterOperatorOperation, AlterPolicy, AlterPolicyOperation, AlterSchema, AlterSchemaOperation, AlterTable, AlterTableAlgorithm, AlterTableLock, AlterTableOperation, AlterTableType, AlterTextSearch, AlterTextSearchOperation, - AlterTextSearchOption, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition, - AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, ClusteredBy, ColumnDef, - ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, ColumnPolicyProperty, - ConstraintCharacteristics, CreateCollation, CreateCollationDefinition, CreateConnector, - CreateDomain, CreateExtension, CreateForeignTable, CreateFunction, CreateIndex, CreateOperator, - CreateOperatorClass, CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType, - CreateTable, CreateTextSearch, CreateTrigger, CreateView, Deduplicate, DeferrableInitial, - DistStyle, DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, - DropOperatorFamily, DropOperatorSignature, DropPolicy, DropTrigger, ForValues, - FunctionReturnType, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, - IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, - IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes, - OperatorClassItem, OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorPurpose, - Owner, Partition, PartitionBoundValue, ProcedureParam, ReferentialAction, RenameTableNameKind, - ReplicaIdentity, SqliteTableOption, SqliteTableOptions, TagsColumnOption, TextSearchObjectType, - TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef, - UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, - UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef, WithData, + AlterTextSearchOption, AlterTrigger, AlterTriggerOperation, AlterType, AlterTypeAddValue, + AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, + ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, + ColumnPolicyProperty, ConstraintCharacteristics, CreateCollation, CreateCollationDefinition, + CreateConnector, CreateDomain, CreateExtension, CreateForeignTable, CreateFunction, + CreateIndex, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreatePolicy, + CreatePolicyCommand, CreatePolicyType, CreateTable, CreateTextSearch, CreateTrigger, + CreateView, Deduplicate, DeferrableInitial, DistStyle, DropBehavior, DropExtension, + DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily, DropOperatorSignature, + DropPolicy, DropTrigger, ForValues, FunctionReturnType, GeneratedAs, GeneratedExpressionMode, + IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, + IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, + NullsDistinctOption, OperatorArgTypes, OperatorClassItem, OperatorFamilyDropItem, + OperatorFamilyItem, OperatorOption, OperatorPurpose, Owner, Partition, PartitionBoundValue, + ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, SqliteTableOption, + SqliteTableOptions, TagsColumnOption, TextSearchObjectType, TriggerObjectKind, Truncate, + UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength, + UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption, + UserDefinedTypeStorage, ViewColumnDef, WithData, }; pub use self::dml::{ Delete, Insert, InsertOverriding, Merge, MergeAction, MergeClause, MergeClauseKind, @@ -3928,6 +3929,11 @@ pub enum Statement { /// (Postgresql-specific) AlterPolicy(AlterPolicy), /// ```sql + /// ALTER TRIGGER ON
+ /// ``` + /// (Postgresql-specific) + AlterTrigger(AlterTrigger), + /// ```sql /// ALTER CONNECTOR connector_name SET DCPROPERTIES(property_name=property_value, ...); /// or /// ALTER CONNECTOR connector_name SET URL new_url; @@ -5729,6 +5735,7 @@ impl fmt::Display for Statement { write!(f, "ALTER ROLE {name} {operation}") } Statement::AlterPolicy(alter_policy) => write!(f, "{alter_policy}"), + Statement::AlterTrigger(alter_trigger) => write!(f, "{alter_trigger}"), Statement::AlterConnector { name, properties, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 8267c71798..a99abda70b 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -488,6 +488,7 @@ impl Spanned for Statement { Statement::OptimizeTable { .. } => Span::empty(), Statement::CreatePolicy { .. } => Span::empty(), Statement::AlterPolicy { .. } => Span::empty(), + Statement::AlterTrigger { .. } => Span::empty(), Statement::AlterConnector { .. } => Span::empty(), Statement::DropPolicy { .. } => Span::empty(), Statement::DropConnector { .. } => Span::empty(), diff --git a/src/parser/alter.rs b/src/parser/alter.rs index 4000eb26ba..047023b893 100644 --- a/src/parser/alter.rs +++ b/src/parser/alter.rs @@ -19,10 +19,11 @@ use super::{Parser, ParserError}; use crate::{ ast::{ helpers::key_value_options::{KeyValueOptions, KeyValueOptionsDelimiter}, - AlterConnectorOwner, AlterPolicy, AlterPolicyOperation, AlterRoleOperation, AlterUser, - AlterUserAddMfaMethodOtp, AlterUserAddRoleDelegation, AlterUserModifyMfaMethod, - AlterUserPassword, AlterUserRemoveRoleDelegation, AlterUserSetPolicy, Expr, MfaMethodKind, - Password, ResetConfig, RoleOption, SetConfigValue, Statement, UserPolicyKind, + AlterConnectorOwner, AlterPolicy, AlterPolicyOperation, AlterRoleOperation, AlterTrigger, + AlterTriggerOperation, AlterUser, AlterUserAddMfaMethodOtp, AlterUserAddRoleDelegation, + AlterUserModifyMfaMethod, AlterUserPassword, AlterUserRemoveRoleDelegation, + AlterUserSetPolicy, Expr, MfaMethodKind, Password, ResetConfig, RoleOption, SetConfigValue, + Statement, UserPolicyKind, }, dialect::{MsSqlDialect, PostgreSqlDialect}, keywords::Keyword, @@ -103,6 +104,50 @@ impl Parser<'_> { } } + /// Parse ALTER TRIGGER statement + /// ```sql + /// ALTER TRIGGER trigger_name ON table_name RENAME TO new_name + /// or + /// ALTER TRIGGER trigger_name ON table_name [ NO ] DEPENDS ON EXTENSION extension_name + /// ``` + /// + /// [PostgreSQL](https://www.postgresql.org/docs/current/sql-altertrigger.html) + pub fn parse_alter_trigger(&mut self) -> Result { + let name = self.parse_identifier()?; + self.expect_keyword_is(Keyword::ON)?; + let table_name = self.parse_object_name(false)?; + + let operation = if self.parse_keyword(Keyword::RENAME) { + self.expect_keyword_is(Keyword::TO)?; + AlterTriggerOperation::Rename { + new_name: self.parse_identifier()?, + } + } else { + let no = self.parse_keyword(Keyword::NO); + if !self.parse_keyword(Keyword::DEPENDS) { + return self.expected_ref( + if no { + "DEPENDS after NO" + } else { + "RENAME, DEPENDS or NO DEPENDS after ALTER TRIGGER" + }, + self.peek_token_ref(), + ); + } + self.expect_keywords(&[Keyword::ON, Keyword::EXTENSION])?; + AlterTriggerOperation::DependsOnExtension { + no, + extension_name: self.parse_object_name(false)?, + } + }; + + Ok(AlterTrigger { + name, + table_name, + operation, + }) + } + /// Parse an `ALTER CONNECTOR` statement /// ```sql /// ALTER CONNECTOR connector_name SET DCPROPERTIES(property_name=property_value, ...); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 276b252ed2..52b41254c0 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11386,6 +11386,7 @@ impl<'a> Parser<'a> { Keyword::AGGREGATE, Keyword::ROLE, Keyword::POLICY, + Keyword::TRIGGER, Keyword::CONNECTOR, Keyword::ICEBERG, Keyword::SCHEMA, @@ -11437,6 +11438,7 @@ impl<'a> Parser<'a> { } Keyword::ROLE => self.parse_alter_role(), Keyword::POLICY => self.parse_alter_policy().map(Into::into), + Keyword::TRIGGER => self.parse_alter_trigger().map(Into::into), Keyword::CONNECTOR => self.parse_alter_connector(), Keyword::USER if self.dialect.supports_alter_user_as_alter_role() => { self.parse_alter_role() @@ -11444,7 +11446,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 {{TEXT SEARCH, VIEW, TYPE, COLLATION, TABLE, INDEX, FUNCTION, AGGREGATE, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"), + format!("Internal parser error: expected any of {{TEXT SEARCH, VIEW, TYPE, COLLATION, TABLE, INDEX, FUNCTION, AGGREGATE, ROLE, POLICY, TRIGGER, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"), )), } } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 87fb61dc17..c86391a755 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -15180,6 +15180,93 @@ fn test_alter_policy() { ); } +#[test] +fn test_alter_trigger() { + match verified_stmt("ALTER TRIGGER old_trigger ON my_table RENAME TO new_trigger") { + Statement::AlterTrigger(AlterTrigger { + name, + table_name, + operation, + }) => { + assert_eq!(name.to_string(), "old_trigger"); + assert_eq!(table_name.to_string(), "my_table"); + assert_eq!( + operation, + AlterTriggerOperation::Rename { + new_name: Ident::new("new_trigger") + } + ); + } + _ => unreachable!(), + } + + match verified_stmt("ALTER TRIGGER my_trigger ON my_table DEPENDS ON EXTENSION my_extension") { + Statement::AlterTrigger(AlterTrigger { operation, .. }) => { + assert_eq!( + operation, + AlterTriggerOperation::DependsOnExtension { + no: false, + extension_name: ObjectName::from(Ident::new("my_extension")) + } + ); + } + _ => unreachable!(), + } + + match verified_stmt("ALTER TRIGGER my_trigger ON my_table NO DEPENDS ON EXTENSION my_extension") + { + Statement::AlterTrigger(AlterTrigger { operation, .. }) => { + assert_eq!( + operation, + AlterTriggerOperation::DependsOnExtension { + no: true, + extension_name: ObjectName::from(Ident::new("my_extension")) + } + ); + } + _ => unreachable!(), + } + + // a qualified table name is preserved + verified_stmt("ALTER TRIGGER my_trigger ON my_schema.my_table RENAME TO new_trigger"); + + // an operation is required + assert_eq!( + parse_sql_statements("ALTER TRIGGER my_trigger ON my_table") + .unwrap_err() + .to_string(), + "sql parser error: Expected: RENAME, DEPENDS or NO DEPENDS after ALTER TRIGGER, found: EOF" + ); + // missing TO in RENAME TO + assert_eq!( + parse_sql_statements("ALTER TRIGGER my_trigger ON my_table RENAME") + .unwrap_err() + .to_string(), + "sql parser error: Expected: TO, found: EOF" + ); + // missing new name in RENAME TO + assert_eq!( + parse_sql_statements("ALTER TRIGGER my_trigger ON my_table RENAME TO") + .unwrap_err() + .to_string(), + "sql parser error: Expected: identifier, found: EOF" + ); + // NO must be followed by DEPENDS + assert_eq!( + parse_sql_statements("ALTER TRIGGER my_trigger ON my_table NO EXTENSION my_extension") + .unwrap_err() + .to_string(), + "sql parser error: Expected: DEPENDS after NO, found: EXTENSION" + ); + // missing the extension name + assert_eq!( + parse_sql_statements("ALTER TRIGGER my_trigger ON my_table DEPENDS ON EXTENSION") + .unwrap_err() + .to_string(), + "sql parser error: Expected: identifier, found: EOF" + ); +} + #[test] fn test_create_connector() { let sql = "CREATE CONNECTOR my_connector \ From 2b4335093de1d5257095f113b7ac7f4c0cdca7f6 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 20 Oct 2026 13:13:28 +0200 Subject: [PATCH 2/3] Track trigger alteration spans and cover parser boundaries --- src/ast/ddl.rs | 2 ++ src/ast/spans.rs | 52 ++++++++++++++++++++------- src/parser/mod.rs | 34 +++++++----------- tests/sqlparser_common.rs | 75 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 34 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 1b563fcd00..e29e6b9453 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -6080,6 +6080,8 @@ impl From for crate::ast::Statement { /// ALTER TRIGGER statement. /// +/// Its source span covers `name`, `table_name` and `operation`. +/// /// ```sql /// ALTER TRIGGER ON
/// ``` diff --git a/src/ast/spans.rs b/src/ast/spans.rs index a99abda70b..f792e0b5d8 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -29,18 +29,26 @@ use crate::tokenizer::Span; use super::{ comments, dcl::SecondaryRoles, value::ValueWithSpan, AccessExpr, AlterColumnOperation, - AlterIndexOperation, AlterTableOperation, Analyze, Array, Assignment, AssignmentTarget, - AttachedToken, BeginEndStatements, CaseStatement, CloseCursor, ClusteredIndex, ColumnDef, - ColumnOption, ColumnOptionDef, ConditionalStatementBlock, ConditionalStatements, - ConflictTarget, ConnectByKind, ConstraintCharacteristics, CopySource, CreateIndex, CreateTable, - CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, ExcludeConstraintElement, - ExcludeSelectItem, Expr, ExprWithAlias, Fetch, ForValues, FromTable, Function, FunctionArg, - FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList, FunctionArguments, GroupByExpr, - HavingBound, IfStatement, IlikeSelectItem, InTarget, IndexColumn, Insert, Interpolate, - InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, - LimitClause, MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr, - MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, NamedParenthesizedList, - NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction, + comments, dcl::SecondaryRoles, value::ValueWithSpan, AccessExpr, AlterColumnOperation, + AlterIndexOperation, AlterTableOperation, AlterTrigger, AlterTriggerOperation, Analyze, Array, + Assignment, AssignmentTarget, AttachedToken, BeginEndStatements, CaseStatement, CloseCursor, + ClusteredIndex, ColumnDef, ColumnOption, ColumnOptionDef, ConditionalStatementBlock, + ConditionalStatements, ConflictTarget, ConnectByKind, ConstraintCharacteristics, CopySource, + CreateIndex, CreateTable, CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, + ExcludeConstraintElement, ExcludeSelectItem, Expr, ExprWithAlias, Fetch, ForValues, FromTable, + Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList, + FunctionArguments, GroupByExpr, HavingBound, IfStatement, IlikeSelectItem, InTarget, + IndexColumn, Insert, Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator, + JsonPath, JsonPathElem, LateralView, LimitClause, MatchRecognizePattern, Measure, Merge, + MergeAction, MergeClause, MergeInsertExpr, MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, + NamedParenthesizedList, NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, + OnConflictAction, OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, OutputClause, + Parens, Partition, PartitionBoundValue, PivotValueSource, ProjectionSelect, Query, + RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement, + ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, Setting, SqlOption, Statement, + Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, + TableObject, TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use, Values, + ViewColumnDef, WhileStatement, WildcardAdditionalOptions, With, WithFill, NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, OutputClause, Parens, Partition, PartitionBoundValue, PivotValueSource, ProjectionSelect, Query, RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement, @@ -488,7 +496,7 @@ impl Spanned for Statement { Statement::OptimizeTable { .. } => Span::empty(), Statement::CreatePolicy { .. } => Span::empty(), Statement::AlterPolicy { .. } => Span::empty(), - Statement::AlterTrigger { .. } => Span::empty(), + Statement::AlterTrigger(trigger) => trigger.span(), Statement::AlterConnector { .. } => Span::empty(), Statement::DropPolicy { .. } => Span::empty(), Statement::DropConnector { .. } => Span::empty(), @@ -529,6 +537,24 @@ impl Spanned for Statement { } } +impl Spanned for AlterTrigger { + fn span(&self) -> Span { + self.name + .span + .union(&self.table_name.span()) + .union(&self.operation.span()) + } +} + +impl Spanned for AlterTriggerOperation { + fn span(&self) -> Span { + match self { + Self::Rename { new_name } => new_name.span, + Self::DependsOnExtension { extension_name, .. } => extension_name.span(), + } + } +} + impl Spanned for Use { fn span(&self) -> Span { match self { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 52b41254c0..d2f1bacbbc 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11376,23 +11376,12 @@ impl<'a> Parser<'a> { return self.parse_alter_text_search().map(Into::into); } - let object_type = self.expect_one_of_keywords(&[ - Keyword::VIEW, - Keyword::TYPE, - Keyword::COLLATION, - Keyword::TABLE, - Keyword::INDEX, - Keyword::FUNCTION, - Keyword::AGGREGATE, - Keyword::ROLE, - Keyword::POLICY, - Keyword::TRIGGER, - Keyword::CONNECTOR, - Keyword::ICEBERG, - Keyword::SCHEMA, - Keyword::USER, - Keyword::OPERATOR, - ])?; + let index = self.index; + self.advance_token(); + let object_type = match &self.get_current_token().token { + Token::Word(word) => word.keyword, + _ => Keyword::NoKeyword, + }; match object_type { Keyword::SCHEMA => { self.prev_token(); @@ -11444,10 +11433,13 @@ impl<'a> Parser<'a> { self.parse_alter_role() } 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 {{TEXT SEARCH, VIEW, TYPE, COLLATION, TABLE, INDEX, FUNCTION, AGGREGATE, ROLE, POLICY, TRIGGER, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"), - )), + _ => { + self.index = index; + self.expected_ref( + "one of VIEW or TYPE or COLLATION or TABLE or INDEX or FUNCTION or AGGREGATE or ROLE or POLICY or TRIGGER or CONNECTOR or ICEBERG or SCHEMA or USER or OPERATOR", + self.peek_token_ref(), + ) + } } } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index c86391a755..e1b09178e7 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -15267,6 +15267,66 @@ fn test_alter_trigger() { ); } +#[test] +fn parse_alter_trigger_rejects_malformed_statements() { + for (sql, expected) in [ + ("ALTER TRIGGER", "Expected: identifier, found: EOF"), + ("ALTER TRIGGER t", "Expected: ON, found: EOF"), + ("ALTER TRIGGER t ON", "Expected: identifier, found: EOF"), + ("ALTER TRIGGER t ON tbl DEPENDS", "Expected: ON, found: EOF"), + ] { + assert_eq!( + parse_sql_statements(sql).unwrap_err().to_string(), + format!("sql parser error: {expected}"), + ); + } +} + +#[test] +fn display_alter_trigger_propagates_write_errors() { + use core::fmt::{self, Write}; + + struct RejectingWriter<'a>(&'a str); + + impl Write for RejectingWriter<'_> { + fn write_str(&mut self, value: &str) -> fmt::Result { + if value.contains(self.0) { + Err(fmt::Error) + } else { + Ok(()) + } + } + } + + let statement = verified_stmt("ALTER TRIGGER t ON tbl NO DEPENDS ON EXTENSION ext"); + let mut writer = RejectingWriter("NO "); + assert_eq!(write!(writer, "{statement}"), Err(fmt::Error)); +} + +#[test] +fn parse_alter_trigger_spans() { + let dialects = all_dialects(); + for (sql, end) in [ + ( + "ALTER TRIGGER trg ON t RENAME TO trg2", + Location::new(1, 38), + ), + ( + "ALTER TRIGGER trg ON t DEPENDS ON EXTENSION ext", + Location::new(1, 48), + ), + ] { + for dialect in &dialects.dialects { + let statements = Parser::parse_sql(dialect.as_ref(), sql).unwrap(); + assert_eq!( + statements[0].span(), + Span::new(Location::new(1, 15), end), + "{dialect:?} {sql}" + ); + } + } +} + #[test] fn test_create_connector() { let sql = "CREATE CONNECTOR my_connector \ @@ -21109,3 +21169,18 @@ fn parse_window_spec_keyword_base_window_name() { "SELECT count(*) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t", ); } + +#[test] +fn parse_alter_rejects_missing_or_unknown_object_type() { + let objects = "one of VIEW or TYPE or COLLATION or TABLE or INDEX or FUNCTION or AGGREGATE or ROLE or POLICY or TRIGGER or CONNECTOR or ICEBERG or SCHEMA or USER or OPERATOR"; + for (sql, found) in [ + ("ALTER", "EOF"), + ("ALTER 1", "1"), + ("ALTER unknown", "unknown"), + ] { + assert_eq!( + all_dialects().parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError(format!("Expected: {objects}, found: {found}")), + ); + } +} From eb549720b90c50b360ae7dc95a91e5a7f2b720c4 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Wed, 21 Oct 2026 09:50:31 +0200 Subject: [PATCH 3/3] Format merged parser and AST code --- src/ast/spans.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/ast/spans.rs b/src/ast/spans.rs index f792e0b5d8..07921025f9 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -28,7 +28,6 @@ use core::iter; use crate::tokenizer::Span; use super::{ - comments, dcl::SecondaryRoles, value::ValueWithSpan, AccessExpr, AlterColumnOperation, comments, dcl::SecondaryRoles, value::ValueWithSpan, AccessExpr, AlterColumnOperation, AlterIndexOperation, AlterTableOperation, AlterTrigger, AlterTriggerOperation, Analyze, Array, Assignment, AssignmentTarget, AttachedToken, BeginEndStatements, CaseStatement, CloseCursor, @@ -48,13 +47,6 @@ use super::{ ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, Setting, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, TableObject, TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use, Values, - ViewColumnDef, WhileStatement, WildcardAdditionalOptions, With, WithFill, NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction, - OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, OutputClause, Parens, Partition, - PartitionBoundValue, PivotValueSource, ProjectionSelect, Query, RaiseStatement, - RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement, - ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, Setting, SqlOption, Statement, - Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, - TableObject, TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use, Values, ViewColumnDef, WhileStatement, WildcardAdditionalOptions, With, WithFill, };