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 e434c58994..30fcfe7348 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -4835,6 +4835,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 c06e120aee..96944a22aa 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}") @@ -12605,6 +12582,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 704b583b8b..861df34e8e 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: @@ -3423,4 +3458,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}"); + } + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6f82dfaac8..8f16a618e4 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -12113,20 +12113,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 379e37a660..76c5fc95cf 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -5661,13 +5661,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![]); @@ -5680,7 +5685,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 { @@ -5705,13 +5713,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 70b058b422..d2352e1074 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -11621,3 +11621,172 @@ fn display_create_foreign_table_inherits_propagates_formatter_errors() { assert_eq!(write!(writer, "{stmt}"), Err(fmt::Error), "{rejected:?}"); } } + +#[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"), + ( + "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_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:?}"); + } +}