diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 1c2aaf48d..1f1419268 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -3020,6 +3020,48 @@ impl fmt::Display for CreateConnector { } } +/// An `ALTER SCHEMA` (`Statement::AlterSchema`) operation. +/// +/// See [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#alter_schema_collate_statement) +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum AlterSchemaOperation { + SetDefaultCollate { + collate: Expr, + }, + AddReplica { + replica: Ident, + options: Option>, + }, + DropReplica { + replica: Ident, + }, + SetOptionsParens { + options: Vec, + }, +} + +impl fmt::Display for AlterSchemaOperation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AlterSchemaOperation::SetDefaultCollate { collate } => { + write!(f, "SET DEFAULT COLLATE {collate}") + } + AlterSchemaOperation::AddReplica { replica, options } => { + write!(f, "ADD REPLICA {replica}")?; + if let Some(options) = options { + write!(f, " OPTIONS ({})", display_comma_separated(options))?; + } + Ok(()) + } + AlterSchemaOperation::DropReplica { replica } => write!(f, "DROP REPLICA {replica}"), + AlterSchemaOperation::SetOptionsParens { options } => { + write!(f, "SET OPTIONS ({})", display_comma_separated(options)) + } + } + } +} /// `RenameTableNameKind` is the kind used in an `ALTER TABLE _ RENAME` statement. /// /// Note: [MySQL] is the only database that supports the AS keyword for this operation. @@ -3042,6 +3084,30 @@ impl fmt::Display for RenameTableNameKind { } } +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct AlterSchema { + pub name: ObjectName, + pub if_exists: bool, + pub operations: Vec, +} + +impl fmt::Display for AlterSchema { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ALTER SCHEMA ")?; + if self.if_exists { + write!(f, "IF EXISTS ")?; + } + write!(f, "{}", self.name)?; + for operation in &self.operations { + write!(f, " {operation}")?; + } + + Ok(()) + } +} + impl Spanned for RenameTableNameKind { fn span(&self) -> Span { match self { diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 5b50d020c..770d9ecdb 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -59,16 +59,17 @@ pub use self::dcl::{ }; pub use self::ddl::{ AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterPolicyOperation, - AlterTableAlgorithm, AlterTableLock, AlterTableOperation, AlterType, AlterTypeAddValue, - AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, - ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, - ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain, CreateFunction, - CreateIndex, CreateTable, Deduplicate, DeferrableInitial, DropBehavior, GeneratedAs, - GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, - IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, - KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction, - RenameTableNameKind, ReplicaIdentity, TableConstraint, TagsColumnOption, - UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, + AlterSchema, AlterSchemaOperation, AlterTableAlgorithm, AlterTableLock, AlterTableOperation, + AlterType, AlterTypeAddValue, AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, + AlterTypeRenameValue, ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, + ColumnPolicy, ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain, + CreateFunction, CreateIndex, CreateTable, Deduplicate, DeferrableInitial, DropBehavior, + GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, + IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, + IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition, + ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TableConstraint, + TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, + ViewColumnDef, }; pub use self::dml::{Delete, Insert}; pub use self::operator::{BinaryOperator, UnaryOperator}; @@ -3388,6 +3389,11 @@ pub enum Statement { end_token: AttachedToken, }, /// ```sql + /// ALTER SCHEMA + /// ``` + /// See [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#alter_schema_collate_statement) + AlterSchema(AlterSchema), + /// ```sql /// ALTER INDEX /// ``` AlterIndex { @@ -6336,6 +6342,7 @@ impl fmt::Display for Statement { Statement::Remove(command) => write!(f, "REMOVE {command}"), Statement::ExportData(e) => write!(f, "{e}"), Statement::CreateUser(s) => write!(f, "{s}"), + Statement::AlterSchema(s) => write!(f, "{s}"), } } } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index e17090268..1a13178fb 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -15,7 +15,10 @@ // specific language governing permissions and limitations // under the License. -use crate::ast::{query::SelectItemQualifiedWildcardKind, ColumnOptions, ExportData, TypedString}; +use crate::ast::{ + ddl::AlterSchema, query::SelectItemQualifiedWildcardKind, AlterSchemaOperation, ColumnOptions, + ExportData, TypedString, +}; use core::iter; use crate::tokenizer::Span; @@ -548,6 +551,7 @@ impl Spanned for Statement { .chain(connection.iter().map(|i| i.span())), ), Statement::CreateUser(..) => Span::empty(), + Statement::AlterSchema(s) => s.span(), } } } @@ -2381,6 +2385,30 @@ impl Spanned for OpenStatement { } } +impl Spanned for AlterSchemaOperation { + fn span(&self) -> Span { + match self { + AlterSchemaOperation::SetDefaultCollate { collate } => collate.span(), + AlterSchemaOperation::AddReplica { replica, options } => union_spans( + core::iter::once(replica.span) + .chain(options.iter().flat_map(|i| i.iter().map(|i| i.span()))), + ), + AlterSchemaOperation::DropReplica { replica } => replica.span, + AlterSchemaOperation::SetOptionsParens { options } => { + union_spans(options.iter().map(|i| i.span())) + } + } + } +} + +impl Spanned for AlterSchema { + fn span(&self) -> Span { + union_spans( + core::iter::once(self.name.span()).chain(self.operations.iter().map(|i| i.span())), + ) + } +} + #[cfg(test)] pub mod tests { use crate::dialect::{Dialect, GenericDialect, SnowflakeDialect}; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2c5f1b9e6..3482e8bf7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9207,8 +9207,14 @@ impl<'a> Parser<'a> { Keyword::POLICY, Keyword::CONNECTOR, Keyword::ICEBERG, + Keyword::SCHEMA, ])?; match object_type { + Keyword::SCHEMA => { + self.prev_token(); + self.prev_token(); + self.parse_alter_schema() + } Keyword::VIEW => self.parse_alter_view(), Keyword::TYPE => self.parse_alter_type(), Keyword::TABLE => self.parse_alter_table(false), @@ -9347,6 +9353,40 @@ impl<'a> Parser<'a> { } } + // Parse a [Statement::AlterSchema] + // ALTER SCHEMA [ IF EXISTS ] schema_name + pub fn parse_alter_schema(&mut self) -> Result { + self.expect_keywords(&[Keyword::ALTER, Keyword::SCHEMA])?; + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let name = self.parse_object_name(false)?; + let operation = if self.parse_keywords(&[Keyword::SET, Keyword::OPTIONS]) { + self.prev_token(); + let options = self.parse_options(Keyword::OPTIONS)?; + AlterSchemaOperation::SetOptionsParens { options } + } else if self.parse_keywords(&[Keyword::SET, Keyword::DEFAULT, Keyword::COLLATE]) { + let collate = self.parse_expr()?; + AlterSchemaOperation::SetDefaultCollate { collate } + } else if self.parse_keywords(&[Keyword::ADD, Keyword::REPLICA]) { + let replica = self.parse_identifier()?; + let options = if self.peek_keyword(Keyword::OPTIONS) { + Some(self.parse_options(Keyword::OPTIONS)?) + } else { + None + }; + AlterSchemaOperation::AddReplica { replica, options } + } else if self.parse_keywords(&[Keyword::DROP, Keyword::REPLICA]) { + let replica = self.parse_identifier()?; + AlterSchemaOperation::DropReplica { replica } + } else { + return self.expected_ref("ALTER SCHEMA operation", self.peek_token_ref()); + }; + Ok(Statement::AlterSchema(AlterSchema { + name, + if_exists, + operations: vec![operation], + })) + } + /// Parse a `CALL procedure_name(arg1, arg2, ...)` /// or `CALL procedure_name` statement pub fn parse_call(&mut self) -> Result { diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 1ca26ae49..4f0cfa3e8 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -2825,3 +2825,15 @@ fn test_begin_transaction() { fn test_begin_statement() { bigquery().verified_stmt("BEGIN"); } + +#[test] +fn test_alter_schema() { + bigquery_and_generic().verified_stmt("ALTER SCHEMA mydataset SET DEFAULT COLLATE 'und:ci'"); + bigquery_and_generic().verified_stmt("ALTER SCHEMA mydataset ADD REPLICA 'us'"); + bigquery_and_generic() + .verified_stmt("ALTER SCHEMA mydataset ADD REPLICA 'us' OPTIONS (location = 'us')"); + bigquery_and_generic().verified_stmt("ALTER SCHEMA mydataset DROP REPLICA 'us'"); + bigquery_and_generic().verified_stmt("ALTER SCHEMA mydataset SET OPTIONS (location = 'us')"); + bigquery_and_generic() + .verified_stmt("ALTER SCHEMA IF EXISTS mydataset SET OPTIONS (location = 'us')"); +}