From 4fe3194dfea96b605e24c5785fb78cc85798c862 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 27 Oct 2026 09:03:34 +0100 Subject: [PATCH 1/5] PostgreSQL: Support ALTER VIEW operations --- fuzz/fuzz_seeds/postgres_886_alter_view.sql | 5 + src/ast/ddl.rs | 136 ++++++++++++++++++++ src/ast/mod.rs | 67 ++++------ src/ast/spans.rs | 97 +++++++++----- src/parser/mod.rs | 80 ++++++++++-- tests/sqlparser_common.rs | 35 +++-- tests/sqlparser_postgres.rs | 99 ++++++++++++++ 7 files changed, 425 insertions(+), 94 deletions(-) create mode 100644 fuzz/fuzz_seeds/postgres_886_alter_view.sql diff --git a/fuzz/fuzz_seeds/postgres_886_alter_view.sql b/fuzz/fuzz_seeds/postgres_886_alter_view.sql new file mode 100644 index 0000000000..cf357ed812 --- /dev/null +++ b/fuzz/fuzz_seeds/postgres_886_alter_view.sql @@ -0,0 +1,5 @@ +ALTER VIEW IF EXISTS s.v OWNER TO CURRENT_USER; +ALTER VIEW v SET (security_invoker = true, security_barrier); +ALTER VIEW v RESET (check_option); +ALTER VIEW v ALTER c SET DEFAULT 1; +ALTER VIEW v RENAME c TO d diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 6d651e39a0..2f47af82a3 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -4849,6 +4849,142 @@ impl Spanned for AlterCollation { } } +/// `ALTER VIEW` 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 AlterView { + /// View name being altered. + #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] + pub name: ObjectName, + /// Whether `IF EXISTS` was specified. + pub if_exists: bool, + /// The operation to perform on the view. + pub operation: AlterViewOperation, +} + +/// Operations supported by `ALTER VIEW`. +/// +/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-alterview.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 AlterViewOperation { + /// Replace the view definition. + /// + /// ```sql + /// ALTER VIEW name [ (columns) ] [ WITH (options) ] AS query + /// ``` + /// + /// See [MySQL](https://dev.mysql.com/doc/refman/8.4/en/alter-view.html) + As { + /// Optional new column list for the view. + columns: Vec, + /// Replacement query for the view definition. + query: Box, + /// Additional `WITH` options for the view. + with_options: Vec, + }, + /// ```sql + /// ALTER VIEW name ALTER [ COLUMN ] column_name { SET DEFAULT expression | DROP DEFAULT } + /// ``` + AlterColumn { + /// The column to alter. + column_name: Ident, + /// Either [`AlterColumnOperation::SetDefault`] or [`AlterColumnOperation::DropDefault`]. + op: AlterColumnOperation, + }, + /// ```sql + /// ALTER VIEW name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } + /// ``` + OwnerTo(Owner), + /// ```sql + /// ALTER VIEW name RENAME [ COLUMN ] column_name TO new_column_name + /// ``` + RenameColumn { + /// Existing column name. + old_column_name: Ident, + /// New column name. + new_column_name: Ident, + }, + /// ```sql + /// ALTER VIEW name RENAME TO new_name + /// ``` + RenameTo { + /// New view name. + new_name: ObjectName, + }, + /// ```sql + /// ALTER VIEW name SET SCHEMA new_schema + /// ``` + SetSchema { + /// Target schema name. + schema_name: ObjectName, + }, + /// ```sql + /// ALTER VIEW name SET ( view_option_name [ = view_option_value ] [, ... ] ) + /// ``` + SetOptions { + /// Options to set, [`SqlOption::Ident`] when no value is given. + options: Vec, + }, + /// ```sql + /// ALTER VIEW name RESET ( view_option_name [, ... ] ) + /// ``` + ResetOptions { + /// Options to reset. + options: Vec, + }, +} + +impl fmt::Display for AlterViewOperation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AlterViewOperation::As { + columns, + query, + with_options, + } => { + if !with_options.is_empty() { + write!(f, "WITH ({}) ", display_comma_separated(with_options))?; + } + if !columns.is_empty() { + write!(f, "({}) ", display_comma_separated(columns))?; + } + write!(f, "AS {query}") + } + AlterViewOperation::AlterColumn { column_name, op } => { + write!(f, "ALTER COLUMN {column_name} {op}") + } + AlterViewOperation::OwnerTo(owner) => write!(f, "OWNER TO {owner}"), + AlterViewOperation::RenameColumn { + old_column_name, + new_column_name, + } => write!(f, "RENAME COLUMN {old_column_name} TO {new_column_name}"), + AlterViewOperation::RenameTo { new_name } => write!(f, "RENAME TO {new_name}"), + AlterViewOperation::SetSchema { schema_name } => { + write!(f, "SET SCHEMA {schema_name}") + } + AlterViewOperation::SetOptions { options } => { + write!(f, "SET ({})", display_comma_separated(options)) + } + AlterViewOperation::ResetOptions { options } => { + write!(f, "RESET ({})", display_comma_separated(options)) + } + } + } +} + +impl fmt::Display for AlterView { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ALTER VIEW ")?; + if self.if_exists { + write!(f, "IF EXISTS ")?; + } + write!(f, "{} {}", self.name, self.operation) + } +} + /// 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 653a3581e5..cd7c2b899b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -71,22 +71,23 @@ pub use self::ddl::{ AlterTable, AlterTableAlgorithm, AlterTableLock, AlterTableOperation, AlterTableType, AlterTextSearch, AlterTextSearchOperation, AlterTextSearchOption, AlterTrigger, AlterTriggerOperation, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition, - AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, ClusteredBy, ColumnDef, - ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, ColumnPolicyProperty, - ConstraintCharacteristics, CreateAggregate, CreateAggregateArgs, CreateAggregateOption, - CreateCollation, CreateCollationDefinition, CreateConnector, CreateDomain, CreateExtension, - CreateForeignDataWrapper, CreateForeignTable, CreateFunction, CreateIndex, CreateOperator, - CreateOperatorClass, CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType, - CreateRule, CreateRuleAction, CreateRuleDoKind, CreateRuleEvent, CreateTable, CreateTextSearch, - CreateTrigger, CreateView, Deduplicate, DeferrableInitial, DistStyle, DropBehavior, - DropExtension, DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily, - DropOperatorSignature, DropPolicy, DropTrigger, ForValues, ForeignDataWrapperRoutineClause, - 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, + AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, AlterView, AlterViewOperation, + ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, + ColumnPolicyProperty, ConstraintCharacteristics, CreateAggregate, CreateAggregateArgs, + CreateAggregateOption, CreateCollation, CreateCollationDefinition, CreateConnector, + CreateDomain, CreateExtension, CreateForeignDataWrapper, CreateForeignTable, CreateFunction, + CreateIndex, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreatePolicy, + CreatePolicyCommand, CreatePolicyType, CreateRule, CreateRuleAction, CreateRuleDoKind, + CreateRuleEvent, CreateTable, CreateTextSearch, CreateTrigger, CreateView, Deduplicate, + DeferrableInitial, DistStyle, DropBehavior, DropExtension, DropFunction, DropOperator, + DropOperatorClass, DropOperatorFamily, DropOperatorSignature, DropPolicy, DropTrigger, + ForValues, ForeignDataWrapperRoutineClause, 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, @@ -3906,17 +3907,7 @@ pub enum Statement { /// ```sql /// ALTER VIEW /// ``` - AlterView { - /// View name being altered. - #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] - name: ObjectName, - /// Optional new column list for the view. - columns: Vec, - /// Replacement query for the view definition. - query: Box, - /// Additional WITH options for the view. - with_options: Vec, - }, + AlterView(AlterView), /// ```sql /// ALTER FUNCTION /// ALTER AGGREGATE @@ -5743,21 +5734,7 @@ impl fmt::Display for Statement { Statement::AlterIndex { name, operation } => { write!(f, "ALTER INDEX {name} {operation}") } - Statement::AlterView { - name, - columns, - query, - with_options, - } => { - write!(f, "ALTER VIEW {name}")?; - if !with_options.is_empty() { - write!(f, " WITH ({})", display_comma_separated(with_options))?; - } - if !columns.is_empty() { - write!(f, " ({})", display_comma_separated(columns))?; - } - write!(f, " AS {query}") - } + Statement::AlterView(alter_view) => write!(f, "{alter_view}"), Statement::AlterFunction(alter_function) => write!(f, "{alter_function}"), Statement::AlterType(AlterType { name, operation }) => { write!(f, "ALTER TYPE {name} {operation}") @@ -12643,6 +12620,12 @@ impl From for Statement { } } +impl From for Statement { + fn from(a: AlterView) -> Self { + Self::AlterView(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 e5eca80edb..c62f2fc91f 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -29,26 +29,27 @@ use crate::tokenizer::Span; use super::{ 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, CreateRule, CreateRuleAction, 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, OperateFunctionArg, 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, + AlterIndexOperation, AlterTableOperation, AlterTrigger, AlterTriggerOperation, AlterView, + AlterViewOperation, Analyze, Array, Assignment, AssignmentTarget, AttachedToken, + BeginEndStatements, CaseStatement, CloseCursor, ClusteredIndex, ColumnDef, ColumnOption, + ColumnOptionDef, ConditionalStatementBlock, ConditionalStatements, ConflictTarget, + ConnectByKind, ConstraintCharacteristics, CopySource, CreateIndex, CreateRule, + CreateRuleAction, 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, OperateFunctionArg, 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, }; /// Given an iterator of spans, return the [Span::union] of all spans. @@ -408,17 +409,7 @@ impl Spanned for Statement { Statement::CreateTextSearch(create_text_search) => create_text_search.span(), Statement::AlterTable(alter_table) => alter_table.span(), Statement::AlterIndex { name, operation } => name.span().union(&operation.span()), - Statement::AlterView { - name, - columns, - query, - with_options, - } => union_spans( - core::iter::once(name.span()) - .chain(columns.iter().map(|i| i.span)) - .chain(core::iter::once(query.span())) - .chain(with_options.iter().map(|i| i.span())), - ), + Statement::AlterView(alter_view) => alter_view.span(), // These statements need to be implemented Statement::AlterFunction { .. } => Span::empty(), Statement::AlterType { .. } => Span::empty(), @@ -1162,6 +1153,50 @@ impl Spanned for CreateTableOptions { } } +impl Spanned for AlterView { + fn span(&self) -> Span { + self.name.span().union(&self.operation.span()) + } +} + +/// # partial span +/// +/// Missing spans: +/// - [AlterViewOperation::OwnerTo] +impl Spanned for AlterViewOperation { + fn span(&self) -> Span { + match self { + AlterViewOperation::As { + columns, + query, + with_options, + } => union_spans( + columns + .iter() + .map(|i| i.span) + .chain(core::iter::once(query.span())) + .chain(with_options.iter().map(|i| i.span())), + ), + AlterViewOperation::AlterColumn { column_name, op } => { + column_name.span.union(&op.span()) + } + AlterViewOperation::OwnerTo(_) => Span::empty(), + AlterViewOperation::RenameColumn { + old_column_name, + new_column_name, + } => old_column_name.span.union(&new_column_name.span), + AlterViewOperation::RenameTo { new_name } => new_name.span(), + AlterViewOperation::SetSchema { schema_name } => schema_name.span(), + AlterViewOperation::SetOptions { options } => { + union_spans(options.iter().map(|i| i.span())) + } + AlterViewOperation::ResetOptions { options } => { + union_spans(options.iter().map(|i| i.span)) + } + } + } +} + /// # partial span /// /// Missing spans: diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1b35306d3f..865618f9bc 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -12092,20 +12092,80 @@ impl<'a> Parser<'a> { /// Parse an `ALTER VIEW` statement. pub fn parse_alter_view(&mut self) -> Result { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); let name = self.parse_object_name(false)?; - let columns = self.parse_parenthesized_column_list(Optional, false)?; - - let with_options = self.parse_options(Keyword::WITH)?; - self.expect_keyword_is(Keyword::AS)?; - let query = self.parse_query()?; + let operation = if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) { + AlterViewOperation::OwnerTo(self.parse_owner()?) + } else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) { + AlterViewOperation::SetSchema { + schema_name: self.parse_object_name(false)?, + } + } else if self.parse_keyword(Keyword::SET) { + self.expect_token(&Token::LParen)?; + let options = self.parse_comma_separated(|parser| { + let key = parser.parse_identifier()?; + Ok(if parser.consume_token(&Token::Eq) { + SqlOption::KeyValue { + key, + value: parser.parse_expr()?, + } + } else { + SqlOption::Ident(key) + }) + })?; + self.expect_token(&Token::RParen)?; + AlterViewOperation::SetOptions { options } + } else if self.parse_keyword(Keyword::RESET) { + self.expect_token(&Token::LParen)?; + let options = self.parse_comma_separated(Parser::parse_identifier)?; + self.expect_token(&Token::RParen)?; + AlterViewOperation::ResetOptions { options } + } else if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) { + AlterViewOperation::RenameTo { + new_name: self.parse_object_name(false)?, + } + } else if self.parse_keyword(Keyword::RENAME) { + let _ = self.parse_keyword(Keyword::COLUMN); + let old_column_name = self.parse_identifier()?; + self.expect_keyword_is(Keyword::TO)?; + AlterViewOperation::RenameColumn { + old_column_name, + new_column_name: self.parse_identifier()?, + } + } else if self.parse_keyword(Keyword::ALTER) { + let _ = self.parse_keyword(Keyword::COLUMN); + let column_name = self.parse_identifier()?; + let op = if self.parse_keywords(&[Keyword::SET, Keyword::DEFAULT]) { + AlterColumnOperation::SetDefault { + value: self.parse_expr()?, + } + } else if self.parse_keywords(&[Keyword::DROP, Keyword::DEFAULT]) { + AlterColumnOperation::DropDefault + } else { + return self.expected_ref( + "SET DEFAULT or DROP DEFAULT after ALTER COLUMN", + self.peek_token_ref(), + ); + }; + AlterViewOperation::AlterColumn { column_name, op } + } else { + let columns = self.parse_parenthesized_column_list(Optional, false)?; + let with_options = self.parse_options(Keyword::WITH)?; + self.expect_keyword_is(Keyword::AS)?; + AlterViewOperation::As { + columns, + query: self.parse_query()?, + with_options, + } + }; - Ok(Statement::AlterView { + Ok(AlterView { name, - columns, - query, - with_options, - }) + if_exists, + operation, + } + .into()) } /// Parse a [Statement::AlterType] diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index db7786d681..31b9b060ae 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -5665,13 +5665,18 @@ fn parse_alter_index() { fn parse_alter_view() { let sql = "ALTER VIEW myschema.myview AS SELECT foo FROM bar"; match verified_stmt(sql) { - Statement::AlterView { + Statement::AlterView(AlterView { name, - columns, - query, - with_options, - } => { + if_exists, + operation: + AlterViewOperation::As { + columns, + query, + with_options, + }, + }) => { assert_eq!("myschema.myview", name.to_string()); + assert!(!if_exists); assert_eq!(Vec::::new(), columns); assert_eq!("SELECT foo FROM bar", query.to_string()); assert_eq!(with_options, vec![]); @@ -5684,7 +5689,10 @@ fn parse_alter_view() { fn parse_alter_view_with_options() { let sql = "ALTER VIEW v WITH (foo = 'bar', a = 123) AS SELECT 1"; match verified_stmt(sql) { - Statement::AlterView { with_options, .. } => { + Statement::AlterView(AlterView { + operation: AlterViewOperation::As { with_options, .. }, + .. + }) => { assert_eq!( vec![ SqlOption::KeyValue { @@ -5709,13 +5717,18 @@ fn parse_alter_view_with_options() { fn parse_alter_view_with_columns() { let sql = "ALTER VIEW v (has, cols) AS SELECT 1, 2"; match verified_stmt(sql) { - Statement::AlterView { + Statement::AlterView(AlterView { name, - columns, - query, - with_options, - } => { + if_exists, + operation: + AlterViewOperation::As { + columns, + query, + with_options, + }, + }) => { assert_eq!("v", name.to_string()); + assert!(!if_exists); assert_eq!(columns, vec![Ident::new("has"), Ident::new("cols")]); assert_eq!("SELECT 1, 2", query.to_string()); assert_eq!(with_options, vec![]); diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 15479cbd41..f6674270fd 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -12004,3 +12004,102 @@ fn display_table_inheritance_propagates_write_errors() { assert_eq!(write!(writer, "{stmt}"), Err(fmt::Error), "{sql}"); } } + +#[test] +fn parse_alter_view_postgres() { + let operation = |sql: &str| match pg_and_generic().verified_stmt(sql) { + Statement::AlterView(AlterView { operation, .. }) => operation, + stmt => panic!("Expected ALTER VIEW, got {stmt:?}"), + }; + + assert_eq!( + pg_and_generic().verified_stmt("ALTER VIEW IF EXISTS s.v OWNER TO o"), + Statement::AlterView(AlterView { + name: ObjectName::from(vec![Ident::new("s"), Ident::new("v")]), + if_exists: true, + operation: AlterViewOperation::OwnerTo(Owner::Ident(Ident::new("o"))), + }) + ); + assert_eq!( + operation("ALTER VIEW v OWNER TO CURRENT_USER"), + AlterViewOperation::OwnerTo(Owner::CurrentUser) + ); + assert_eq!( + operation("ALTER VIEW v SET (security_invoker = true, security_barrier)"), + AlterViewOperation::SetOptions { + options: vec![ + SqlOption::KeyValue { + key: Ident::new("security_invoker"), + value: Expr::Value(Value::Boolean(true).with_empty_span()), + }, + SqlOption::Ident(Ident::new("security_barrier")), + ], + } + ); + assert_eq!( + operation("ALTER VIEW v RESET (security_invoker, check_option)"), + AlterViewOperation::ResetOptions { + options: vec![Ident::new("security_invoker"), Ident::new("check_option")], + } + ); + assert_eq!( + operation("ALTER VIEW v ALTER COLUMN c SET DEFAULT 1"), + AlterViewOperation::AlterColumn { + column_name: Ident::new("c"), + op: AlterColumnOperation::SetDefault { + value: Expr::value(number("1")), + }, + } + ); + assert_eq!( + operation("ALTER VIEW v ALTER COLUMN c DROP DEFAULT"), + AlterViewOperation::AlterColumn { + column_name: Ident::new("c"), + op: AlterColumnOperation::DropDefault, + } + ); + assert_eq!( + operation("ALTER VIEW v RENAME COLUMN a TO b"), + AlterViewOperation::RenameColumn { + old_column_name: Ident::new("a"), + new_column_name: Ident::new("b"), + } + ); + assert_eq!( + operation("ALTER VIEW v RENAME TO w"), + AlterViewOperation::RenameTo { + new_name: ObjectName::from(vec![Ident::new("w")]), + } + ); + assert_eq!( + operation("ALTER VIEW v SET SCHEMA s"), + AlterViewOperation::SetSchema { + schema_name: ObjectName::from(vec![Ident::new("s")]), + } + ); + + pg_and_generic().one_statement_parses_to( + "ALTER VIEW v ALTER c DROP DEFAULT", + "ALTER VIEW v ALTER COLUMN c DROP DEFAULT", + ); + pg_and_generic().one_statement_parses_to( + "ALTER VIEW v RENAME a TO b", + "ALTER VIEW v RENAME COLUMN a TO b", + ); + + for (sql, expected) in [ + ("ALTER VIEW v SET ()", "Expected: identifier, found: )"), + ("ALTER VIEW v RESET (a = 1)", "Expected: ), found: ="), + ( + "ALTER VIEW v ALTER COLUMN c SET NOT NULL", + "Expected: SET DEFAULT or DROP DEFAULT after ALTER COLUMN, found: SET", + ), + ("ALTER VIEW v RENAME COLUMN a", "Expected: TO, found: EOF"), + ] { + assert_eq!( + pg_and_generic().parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError(expected.to_string()), + "{sql}" + ); + } +} From bc53be69001df7a76ac1ad21077c6e450de1b69f Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 27 Oct 2026 21:59:56 +0100 Subject: [PATCH 2/5] Tests: Cover ALTER VIEW spans --- src/ast/spans.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/ast/spans.rs b/src/ast/spans.rs index c62f2fc91f..edb63d9436 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -3462,4 +3462,32 @@ SETTINGS update_parallel_mode = 'auto'"#; assert_eq!(test.get_source(expr.span()), expected, "{sql}"); } } + + #[test] + fn test_alter_view_spans() { + let dialect = &crate::dialect::PostgreSqlDialect {}; + for (sql, expected) in [ + ( + "ALTER VIEW v (a) WITH (k = 1) AS SELECT 1", + "v (a) WITH (k = 1) AS SELECT 1", + ), + ( + "ALTER VIEW v ALTER COLUMN c SET DEFAULT 1", + "v ALTER COLUMN c SET DEFAULT 1", + ), + ("ALTER VIEW IF EXISTS s.v OWNER TO o", "s.v"), + ( + "ALTER VIEW v RENAME COLUMN a TO b", + "v RENAME COLUMN a TO b", + ), + ("ALTER VIEW v RENAME TO w", "v RENAME TO w"), + ("ALTER VIEW v SET SCHEMA s", "v SET SCHEMA s"), + ("ALTER VIEW v SET (a = 1, b)", "v SET (a = 1, b"), + ("ALTER VIEW v RESET (a, b)", "v RESET (a, b"), + ] { + let test = SpanTest::new(dialect, sql); + let stmt = &Parser::parse_sql(dialect, sql).unwrap()[0]; + assert_eq!(test.get_source(stmt.span()), expected, "{sql}"); + } + } } From 059d3c5be7d9c382816765f663195e71e46eeb79 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 30 Oct 2026 11:48:13 +0100 Subject: [PATCH 3/5] Test ALTER VIEW error paths and formatter errors --- tests/sqlparser_postgres.rs | 70 +++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index f6674270fd..eabcdf439d 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -12095,6 +12095,42 @@ fn parse_alter_view_postgres() { "Expected: SET DEFAULT or DROP DEFAULT after ALTER COLUMN, found: SET", ), ("ALTER VIEW v RENAME COLUMN a", "Expected: TO, found: EOF"), + ( + "ALTER VIEW v OWNER TO", + "Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO. \ + sql parser error: Expected: identifier, found: EOF", + ), + ( + "ALTER VIEW v SET SCHEMA", + "Expected: identifier, found: EOF", + ), + ("ALTER VIEW v SET a", "Expected: (, found: a"), + ( + "ALTER VIEW v SET (a = )", + "Expected: an expression, found: )", + ), + ("ALTER VIEW v SET (a", "Expected: ), found: EOF"), + ("ALTER VIEW v RESET a", "Expected: (, found: a"), + ("ALTER VIEW v RESET (a b)", "Expected: ), found: b"), + ("ALTER VIEW v RESET (a, )", "Expected: identifier, found: )"), + ("ALTER VIEW v RENAME TO", "Expected: identifier, found: EOF"), + ("ALTER VIEW v RENAME", "Expected: identifier, found: EOF"), + ( + "ALTER VIEW v RENAME COLUMN a TO", + "Expected: identifier, found: EOF", + ), + ("ALTER VIEW v ALTER", "Expected: identifier, found: EOF"), + ( + "ALTER VIEW v ALTER COLUMN c SET DEFAULT", + "Expected: an expression, found: EOF", + ), + ("ALTER VIEW v (a", "Expected: ), found: EOF"), + ("ALTER VIEW v WITH (a", "Expected: =, found: EOF"), + ("ALTER VIEW v WITH (a = 1)", "Expected: AS, found: EOF"), + ( + "ALTER VIEW v AS", + "Expected: SELECT, VALUES, or a subquery in the query body, found: EOF", + ), ] { assert_eq!( pg_and_generic().parse_sql_statements(sql).unwrap_err(), @@ -12103,3 +12139,37 @@ fn parse_alter_view_postgres() { ); } } + +#[test] +fn display_alter_view_propagates_write_errors() { + use core::fmt::{self, Write}; + + struct RejectingWriter(&'static str); + + impl Write for RejectingWriter { + fn write_str(&mut self, value: &str) -> fmt::Result { + if value == self.0 { + Err(fmt::Error) + } else { + Ok(()) + } + } + } + + let with_options = + pg_and_generic().verified_stmt("ALTER VIEW IF EXISTS v WITH (x = 1) AS SELECT 1"); + for rejected in ["ALTER VIEW ", "IF EXISTS ", "WITH ("] { + let mut writer = RejectingWriter(rejected); + assert_eq!( + write!(writer, "{with_options}"), + Err(fmt::Error), + "{rejected:?}" + ); + } + + let columns = pg_and_generic().verified_stmt("ALTER VIEW IF EXISTS v (zeta) AS SELECT 1"); + for rejected in ["ALTER VIEW ", "IF EXISTS ", "("] { + let mut writer = RejectingWriter(rejected); + assert_eq!(write!(writer, "{columns}"), Err(fmt::Error), "{rejected:?}"); + } +} From 5f96701b6055d6ad292740666539e0167f9960e7 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 30 Oct 2026 18:20:01 +0100 Subject: [PATCH 4/5] Test ALTER VIEW IF without EXISTS --- tests/sqlparser_postgres.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index eabcdf439d..3d7a1fff21 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -12089,6 +12089,7 @@ fn parse_alter_view_postgres() { for (sql, expected) in [ ("ALTER VIEW v SET ()", "Expected: identifier, found: )"), + ("ALTER VIEW IF v OWNER TO o", "Expected: AS, found: v"), ("ALTER VIEW v RESET (a = 1)", "Expected: ), found: ="), ( "ALTER VIEW v ALTER COLUMN c SET NOT NULL", From 99ff07dea64d4f02a0d68488daee966692bafb36 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 30 Oct 2026 23:24:08 +0100 Subject: [PATCH 5/5] Test ALTER VIEW name error --- tests/sqlparser_postgres.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 3d7a1fff21..7679fcc03a 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -12088,6 +12088,7 @@ fn parse_alter_view_postgres() { ); for (sql, expected) in [ + ("ALTER VIEW 123", "Expected: identifier, found: 123"), ("ALTER VIEW v SET ()", "Expected: identifier, found: )"), ("ALTER VIEW IF v OWNER TO o", "Expected: AS, found: v"), ("ALTER VIEW v RESET (a = 1)", "Expected: ), found: ="),