feat: Add ALTER SCHEMA support (#1980)
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Rust / test (stable) (push) Waiting to run

This commit is contained in:
Chen Chongchen 2025-08-20 19:45:42 +08:00 committed by GitHub
parent 56848b03dd
commit cb7a51e85f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 164 additions and 11 deletions

View file

@ -3080,6 +3080,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<Vec<SqlOption>>,
},
DropReplica {
replica: Ident,
},
SetOptionsParens {
options: Vec<SqlOption>,
},
}
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.
@ -3102,6 +3144,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<AlterSchemaOperation>,
}
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 {

View file

@ -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}"),
}
}
}

View file

@ -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(),
}
}
}
@ -2387,6 +2391,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};

View file

@ -9247,8 +9247,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),
@ -9387,6 +9393,40 @@ impl<'a> Parser<'a> {
}
}
// Parse a [Statement::AlterSchema]
// ALTER SCHEMA [ IF EXISTS ] schema_name
pub fn parse_alter_schema(&mut self) -> Result<Statement, ParserError> {
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<Statement, ParserError> {

View file

@ -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')");
}