use std::sync::Arc;
use delta_kernel_derive::internal_api;
use crate::committer::Committer;
use crate::expressions::ColumnName;
use crate::schema::StructField;
use crate::snapshot::SnapshotRef;
use crate::table_features::{Operation, TableFeature};
use crate::transaction::alter_table::AlterTableTransaction;
use crate::transaction::schema_evolution::{evolve_table_config, SchemaOperation};
use crate::utils::PhantomType;
use crate::{DeltaResult, Engine, Error};
pub struct Ready;
pub struct Modifying;
pub trait Chainable: sealed::Sealed {}
impl Chainable for Ready {}
impl Chainable for Modifying {}
mod sealed {
pub trait Sealed {}
impl Sealed for super::Ready {}
impl Sealed for super::Modifying {}
}
pub struct AlterTableTransactionBuilder<S = Ready> {
snapshot: SnapshotRef,
operations: Vec<SchemaOperation>,
correlation_id: Option<Arc<str>>,
_state: PhantomType<S>,
}
impl<S> AlterTableTransactionBuilder<S> {
fn transition<T>(self) -> AlterTableTransactionBuilder<T> {
AlterTableTransactionBuilder {
snapshot: self.snapshot,
operations: self.operations,
correlation_id: self.correlation_id,
_state: PhantomType::default(),
}
}
pub fn with_correlation_id(mut self, correlation_id: impl Into<Arc<str>>) -> Self {
self.correlation_id = Some(correlation_id.into()).filter(|id| !id.is_empty());
self
}
}
impl AlterTableTransactionBuilder<Ready> {
pub(crate) fn new(snapshot: SnapshotRef) -> Self {
AlterTableTransactionBuilder {
snapshot,
operations: Vec::new(),
correlation_id: None,
_state: PhantomType::default(),
}
}
}
impl<S: Chainable> AlterTableTransactionBuilder<S> {
pub fn add_column(mut self, field: StructField) -> AlterTableTransactionBuilder<Modifying> {
self.operations
.push(SchemaOperation::add_column(None, field));
self.transition()
}
pub fn set_nullable(mut self, column: ColumnName) -> AlterTableTransactionBuilder<Modifying> {
self.operations
.push(SchemaOperation::SetNullable { column });
self.transition()
}
#[internal_api]
pub(crate) fn add_column_at(
mut self,
parent: ColumnName,
field: StructField,
) -> AlterTableTransactionBuilder<Modifying> {
self.operations
.push(SchemaOperation::add_column(parent, field));
self.transition()
}
}
impl AlterTableTransactionBuilder<Modifying> {
pub fn build(
self,
_engine: &dyn Engine,
committer: Box<dyn Committer>,
) -> DeltaResult<AlterTableTransaction> {
let table_config = self.snapshot.table_configuration();
let unsupported_iceberg_compat =
[TableFeature::IcebergCompatV2, TableFeature::IcebergCompatV3]
.into_iter()
.find(|feature| table_config.is_feature_enabled(feature));
if let Some(feature) = unsupported_iceberg_compat {
return Err(Error::unsupported(format!(
"ALTER TABLE is not yet supported on tables with {feature} enabled"
)));
}
if table_config.is_feature_enabled(&TableFeature::AllowColumnDefaults) {
return Err(Error::unsupported(
"ALTER TABLE is not yet supported on tables with allowColumnDefaults enabled",
));
}
table_config.ensure_operation_supported(Operation::Write)?;
let evolved_table_config = evolve_table_config(table_config, self.operations)?;
AlterTableTransaction::try_new_alter_table(
self.snapshot,
evolved_table_config,
committer,
self.correlation_id,
)
}
}