From 4419396441c7550b78b8007cae48dba98be3d92f Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 27 Oct 2026 09:03:34 +0100 Subject: [PATCH 1/6] 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 | 77 ++++++++--- src/parser/mod.rs | 80 ++++++++++-- tests/sqlparser_common.rs | 35 +++-- tests/sqlparser_postgres.rs | 99 ++++++++++++++ 7 files changed, 415 insertions(+), 84 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 019414730f..c74585efc4 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 e66cdaa08a..cefead4aa5 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, CreateUserMapping, 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, CreateUserMapping, 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, UserMappingUser, ViewColumnDef, @@ -3912,17 +3913,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 @@ -5750,21 +5741,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}") @@ -12651,6 +12628,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 ca48359557..e4f32db203 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -29,16 +29,17 @@ 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, CreateUserMapping, - 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, + 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, CreateUserMapping, 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, @@ -410,17 +411,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(), @@ -1164,6 +1155,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 965a5e3603..b11b402f78 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -12129,20 +12129,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 = ObjectName::from(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 9400df113d..adeda6393e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -5668,13 +5668,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![]); @@ -5687,7 +5692,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 { @@ -5712,13 +5720,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 824ab78351..346b91378f 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -12146,6 +12146,105 @@ fn parse_create_user_mapping() { } } +#[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}" + ); + } +} + #[test] fn display_create_user_mapping_propagates_formatter_errors() { use core::fmt::{self, Write}; From e04f63831c1d18d7f3534bb37894c92e39ceda03 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 27 Oct 2026 21:59:56 +0100 Subject: [PATCH 2/6] 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 e4f32db203..e89d575700 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -3499,4 +3499,32 @@ SETTINGS update_parallel_mode = 'auto'"#; "toast.autovacuum_enabled = false" ); } + + #[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 01f2e1b5ba3cdd30461d801bc79304028748f3bf Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 30 Oct 2026 11:48:13 +0100 Subject: [PATCH 3/6] 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 346b91378f..8cb46d92e0 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -12236,6 +12236,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(), @@ -12307,3 +12343,37 @@ fn parse_namespaced_storage_parameters_without_value() { ParserError::ParserError("Expected: identifier, found: )".to_string()) ); } + +#[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 e631a2443eae0959021a0dc902a4a1b22ffb4ef3 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 30 Oct 2026 18:20:01 +0100 Subject: [PATCH 4/6] 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 8cb46d92e0..7209794712 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -12230,6 +12230,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 3b24c40e79a89d49714d801afe48bd245531c8ef Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 30 Oct 2026 23:24:08 +0100 Subject: [PATCH 5/6] 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 7209794712..3ea19e47fc 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -12229,6 +12229,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: ="), From 804b779b44d13cb7351efe8dde65153c4af88b5f Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 31 Oct 2026 12:17:01 +0100 Subject: [PATCH 6/6] Fix ALTER VIEW column and WITH order, test on all dialects --- src/ast/ddl.rs | 6 +- tests/sqlparser_common.rs | 178 ++++++++++++++++++++++++++++++++++++ tests/sqlparser_postgres.rs | 171 ---------------------------------- 3 files changed, 181 insertions(+), 174 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index c74585efc4..e2cc004032 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -4945,12 +4945,12 @@ impl fmt::Display for AlterViewOperation { 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))?; } + if !with_options.is_empty() { + write!(f, "WITH ({}) ", display_comma_separated(with_options))?; + } write!(f, "AS {query}") } AlterViewOperation::AlterColumn { column_name, op } => { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index adeda6393e..bd1d7606af 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -5714,6 +5714,8 @@ fn parse_alter_view_with_options() { } _ => unreachable!(), } + + verified_stmt("ALTER VIEW v (a) WITH (x = 1) AS SELECT 1"); } #[test] @@ -5740,6 +5742,182 @@ fn parse_alter_view_with_columns() { } } +#[test] +fn parse_alter_view_operations() { + let operation = |sql: &str| match verified_stmt(sql) { + Statement::AlterView(AlterView { operation, .. }) => operation, + stmt => panic!("Expected ALTER VIEW, got {stmt:?}"), + }; + + assert_eq!( + 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 (check_option = 'local', security_barrier)"), + AlterViewOperation::SetOptions { + options: vec![ + SqlOption::KeyValue { + key: ObjectName::from(vec![Ident::new("check_option")]), + value: Expr::Value(Value::SingleQuotedString("local".into()).with_empty_span()), + }, + SqlOption::Ident(ObjectName::from(vec![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")]), + } + ); + + one_statement_parses_to( + "ALTER VIEW v ALTER c DROP DEFAULT", + "ALTER VIEW v ALTER COLUMN c DROP DEFAULT", + ); + 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 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: ="), + ( + "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"), + ( + "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 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!( + all_dialects().parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError(expected.to_string()), + "{sql}" + ); + } + + assert_eq!( + all_dialects_where(|d| !d.supports_trailing_commas()) + .parse_sql_statements("ALTER VIEW v RESET (a, )") + .unwrap_err(), + ParserError::ParserError("Expected: identifier, found: )".to_string()), + ); +} + +#[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 = 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 = 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:?}"); + } +} + #[test] fn parse_alter_table_add_column() { match alter_table_op(verified_stmt("ALTER TABLE tab ADD foo TEXT")) { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 3ea19e47fc..824ab78351 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -12146,143 +12146,6 @@ fn parse_create_user_mapping() { } } -#[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 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: ="), - ( - "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"), - ( - "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(), - ParserError::ParserError(expected.to_string()), - "{sql}" - ); - } -} - #[test] fn display_create_user_mapping_propagates_formatter_errors() { use core::fmt::{self, Write}; @@ -12345,37 +12208,3 @@ fn parse_namespaced_storage_parameters_without_value() { ParserError::ParserError("Expected: identifier, found: )".to_string()) ); } - -#[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:?}"); - } -}