use super::{
addition::ColumnAdditionState, generated::GeneratedRewriteContext, ColumnRewriteContext,
};
use crate::schema::publication::{columns as publication, SchemaWriteTransaction};
use uqa_sql::{
ast::{ColumnType, Expr, GeneratedColumnKind},
schema::columns::{
alteration::{self, ColumnAlterAnalysisContext},
publication::ColumnProperty,
},
SQLError,
};
use uqa_storage::{StorageBackendError, StorageBackendResult};
pub trait ColumnIndexChanges {
fn drop_vector_indexes(&self, table: &str, column: &str) -> StorageBackendResult<bool>;
fn prepare_vector_rewrite(
&self,
table: &str,
column: &str,
dimensions: u32,
) -> StorageBackendResult<()>;
fn rebuild_vector_index(
&self,
table: &str,
column: &str,
dimensions: u32,
) -> StorageBackendResult<bool>;
}
pub struct ColumnAlterContext<'a, S: Clone + 'static> {
pub deferred_rows: super::rows::RewriteDeferral,
pub analysis: ColumnAlterAnalysisContext<'a>,
pub fields: &'a dyn ColumnAdditionState,
pub indexes: &'a dyn ColumnIndexChanges,
pub transactions: &'a dyn SchemaWriteTransaction,
pub generated: GeneratedRewriteContext<'a, S>,
pub rewrite: ColumnRewriteContext<'a>,
}
fn ddl_storage_error(action: &str, error: StorageBackendError) -> SQLError {
uqa_sql::catalog::errors::storage_error(action, &error)
}
fn publish_property(
transactions: &dyn SchemaWriteTransaction,
table: &str,
column: &str,
property: ColumnProperty<'_>,
) -> StorageBackendResult<bool> {
let mut changed = false;
transactions.with_schema_write(Box::new(|context| {
changed = match property {
ColumnProperty::Default(default) => {
publication::set_column_default(context, table, column, default)?
}
ColumnProperty::Generated(generated) => {
publication::set_column_generated(context, table, column, generated)?
}
ColumnProperty::Type(ty) => publication::set_column_type(context, table, column, ty)?,
ColumnProperty::AutoIncrement(provenance) => {
publication::set_column_auto_increment(context, table, column, provenance)?
}
};
Ok(())
}))?;
Ok(changed)
}
pub fn set_default<S: Clone + 'static>(
context: &ColumnAlterContext<'_, S>,
table: &str,
name: &str,
mut default: Expr,
) -> Result<(), SQLError> {
let retained =
alteration::validate_column_default(&context.analysis, table, name, &mut default)?;
if !publish_property(
context.transactions,
table,
name,
ColumnProperty::Default(retained.then_some(default)),
)
.map_err(|error| ddl_storage_error("ALTER COLUMN SET DEFAULT", error))?
{
return Err(uqa_sql::schema::columns::undefined_relation_column(
table, name,
));
}
context
.fields
.persist_schema(table)
.map_err(|error| ddl_storage_error("ALTER TABLE ALTER COLUMN", error))?;
Ok(())
}
pub fn set_auto_increment<S: Clone + 'static>(
context: &ColumnAlterContext<'_, S>,
table: &str,
name: &str,
provenance: Option<uqa_sql::ast::AutoIncrement>,
) -> Result<(), SQLError> {
publish_property(
context.transactions,
table,
name,
ColumnProperty::AutoIncrement(provenance),
)
.map_err(|error| ddl_storage_error("ALTER COLUMN identity", error))?;
context
.fields
.persist_schema(table)
.map_err(|error| ddl_storage_error("ALTER TABLE ALTER COLUMN", error))?;
Ok(())
}
pub fn drop_default<S: Clone + 'static>(
context: &ColumnAlterContext<'_, S>,
table: &str,
name: &str,
) -> Result<(), SQLError> {
alteration::validate_default_removal(&context.analysis, table, name)?;
if !publish_property(
context.transactions,
table,
name,
ColumnProperty::Default(None),
)
.map_err(|error| ddl_storage_error("ALTER COLUMN DROP DEFAULT", error))?
{
return Err(uqa_sql::schema::columns::undefined_relation_column(
table, name,
));
}
context
.fields
.persist_schema(table)
.map_err(|error| ddl_storage_error("ALTER TABLE ALTER COLUMN", error))?;
Ok(())
}
pub fn set_expression<S: Clone + 'static>(
context: &ColumnAlterContext<'_, S>,
table: &str,
qualifier: &str,
name: &str,
expression: Expr,
) -> Result<(), SQLError> {
let (generated, kind) = alteration::analyze_generated_expression(
&context.analysis,
table,
qualifier,
name,
expression,
)?;
publish_property(
context.transactions,
table,
name,
ColumnProperty::Generated(Some(generated)),
)
.map_err(|error| ddl_storage_error("ALTER COLUMN SET EXPRESSION", error))?;
if context.deferred_rows.is_deferred() {
if kind == GeneratedColumnKind::Stored {
context.deferred_rows.require_physical_rewrite(table, name);
}
return Ok(());
}
super::generated::validate_and_rewrite_generated_rows(
&context.generated,
table,
kind == GeneratedColumnKind::Stored,
&[name.to_string()],
)
}
pub fn drop_expression<S: Clone + 'static>(
context: &ColumnAlterContext<'_, S>,
table: &str,
name: &str,
if_exists: bool,
) -> Result<Option<uqa_sql::SQLNotice>, SQLError> {
if let Some(notice) =
alteration::validate_drop_expression(&context.analysis, table, name, if_exists)?
{
return Ok(Some(notice));
}
publish_property(
context.transactions,
table,
name,
ColumnProperty::Generated(None),
)
.map_err(|error| ddl_storage_error("ALTER COLUMN DROP EXPRESSION", error))?;
Ok(None)
}
pub fn begin_type_change<S: Clone + 'static>(
context: &ColumnAlterContext<'_, S>,
table: &str,
qualifier: &str,
name: &str,
ty: &ColumnType,
) -> Result<Option<GeneratedColumnKind>, SQLError> {
let target_generated_kind =
alteration::analyze_column_type(&context.analysis, table, qualifier, name, ty)?;
let old_ty = context
.analysis
.state
.column_type(table, name)
.map_err(|error| {
uqa_sql::catalog::errors::storage_error("ALTER COLUMN TYPE", error.as_ref())
})?
.ok_or_else(|| SQLError::UnknownColumn(format!("{table}.{name}")))?;
let old_was_vector = matches!(&old_ty, ColumnType::Vector(_) | ColumnType::Tensor(_));
if let ColumnType::Vector(dimensions) | ColumnType::Tensor(dimensions) = ty {
context
.indexes
.prepare_vector_rewrite(table, name, *dimensions)
.map_err(|error| ddl_storage_error("ALTER TABLE ALTER COLUMN", error))?;
} else if old_was_vector {
context
.indexes
.drop_vector_indexes(table, name)
.map_err(|error| ddl_storage_error("ALTER TABLE ALTER COLUMN", error))?;
}
publish_property(context.transactions, table, name, ColumnProperty::Type(ty))
.map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?;
Ok(target_generated_kind)
}
pub fn finish_type_change<S: Clone + 'static>(
context: &ColumnAlterContext<'_, S>,
table: &str,
name: &str,
ty: &ColumnType,
target_generated_kind: Option<GeneratedColumnKind>,
) -> Result<(), SQLError> {
match ty {
ColumnType::Text if target_generated_kind != Some(GeneratedColumnKind::Virtual) => {
context.fields.add_text_field(table, name.to_string())?;
}
ColumnType::Vector(dimensions) | ColumnType::Tensor(dimensions) => {
context
.indexes
.rebuild_vector_index(table, name, *dimensions)
.map_err(|error| ddl_storage_error("ALTER TABLE ALTER COLUMN", error))?;
}
_ => {}
}
context
.fields
.persist_schema(table)
.map_err(|error| ddl_storage_error("ALTER TABLE ALTER COLUMN", error))?;
Ok(())
}