use super::{
constraints::{
context::ConstraintContext, validate_document_non_key_constraints,
validate_document_rewrite_constraints, validate_key_constraints, ConstraintStatement,
},
prepared::{PreparedDocumentDelete, PreparedDocumentRewrite},
triggers::context::TriggerContext,
};
use uqa_core::DocId;
use uqa_sql::{SQLError, SQLParam};
use uqa_storage::document_store::Document;
pub trait MutationCommandRows {
fn stage_shared_command_document(
&self,
table: &str,
doc_id: DocId,
document: Option<std::sync::Arc<Document>>,
) -> Result<(), SQLError>;
fn stage_command_document(
&self,
table: &str,
doc_id: DocId,
document: Option<Document>,
) -> Result<(), SQLError>;
}
#[derive(Clone, Copy)]
pub struct MutationStagingContext<'a> {
pub commands: &'a dyn MutationCommandRows,
pub constraints: ConstraintContext<'a>,
pub triggers: TriggerContext<'a>,
}
#[expect(clippy::too_many_lines, reason = "preserves DML lock and event order")]
pub fn stage_prepared_document_rewrite(
context: MutationStagingContext<'_>,
prepared: &mut PreparedDocumentRewrite,
params: &[SQLParam],
statement: ConstraintStatement<'_>,
root_updated_columns: Option<&[String]>,
after_row_events: &mut Vec<crate::mutation::triggers::AfterRowTriggerEvent>,
) -> Result<DocId, SQLError> {
let trigger_updated_columns = root_updated_columns
.or_else(|| prepared.referential_columns())
.map(<[String]>::to_vec);
if let Some(delete) = prepared.partition_move_delete.as_deref() {
stage_document_delete(
&context,
delete,
crate::mutation::referential::checks::moved_row_delete_checks(
context.constraints,
&delete.table,
&delete.document,
)?,
after_row_events,
)?;
if prepared.capture_partition_move_update_transition {
if let Some(updated_columns) = trigger_updated_columns.as_deref() {
if let Some(event) =
crate::mutation::triggers::AfterRowTriggerEvent::prepare_transition_capture(
&context.triggers,
crate::mutation::triggers::AfterRowTriggerInput {
table: &prepared.table,
event: uqa_sql::ast::TriggerEvent::Update,
old_doc_id: prepared.doc_id,
new_doc_id: prepared.doc_id,
old_document: Some(&prepared.old_document),
new_document: None,
updated_columns,
foreign_key_checks: Vec::new(),
},
)?
{
after_row_events.push(event);
}
}
}
return Ok(prepared.doc_id);
}
let rewritten_doc_id =
if let Some((destination_table, destination_doc_id)) = prepared.destination.as_ref() {
validate_document_non_key_constraints(
context.constraints,
Some(statement),
destination_table,
&prepared.new_document,
params,
)?;
validate_key_constraints(
context.constraints,
destination_table,
&prepared.new_document,
None,
)?;
context
.commands
.stage_command_document(&prepared.table, prepared.doc_id, None)?;
context.commands.stage_command_document(
destination_table,
*destination_doc_id,
Some(prepared.new_document.clone()),
)?;
*destination_doc_id
} else {
validate_document_rewrite_constraints(
context.constraints,
statement,
&prepared.table,
&prepared.old_document,
&prepared.new_document,
params,
prepared.doc_id,
)?;
let rewritten_doc_id = prepared.relocation.unwrap_or(prepared.doc_id);
if rewritten_doc_id != prepared.doc_id {
context
.commands
.stage_command_document(&prepared.table, prepared.doc_id, None)?;
}
context.commands.stage_command_document(
&prepared.table,
rewritten_doc_id,
Some(prepared.new_document.clone()),
)?;
rewritten_doc_id
};
if let Some((destination_table, _)) = prepared.destination.as_ref() {
if let Some(event) = crate::mutation::triggers::AfterRowTriggerEvent::prepare(
&context.triggers,
crate::mutation::triggers::AfterRowTriggerInput {
table: &prepared.table,
event: uqa_sql::ast::TriggerEvent::Delete,
old_doc_id: prepared.doc_id,
new_doc_id: prepared.doc_id,
old_document: Some(&prepared.old_document),
new_document: None,
updated_columns: &[],
foreign_key_checks: crate::mutation::referential::checks::moved_row_delete_checks(
context.constraints,
&prepared.table,
&prepared.old_document,
)?,
},
)? {
after_row_events.push(event);
}
if let Some(event) = crate::mutation::triggers::AfterRowTriggerEvent::prepare(
&context.triggers,
crate::mutation::triggers::AfterRowTriggerInput {
table: destination_table,
event: uqa_sql::ast::TriggerEvent::Insert,
old_doc_id: rewritten_doc_id,
new_doc_id: rewritten_doc_id,
old_document: None,
new_document: Some(&prepared.new_document),
updated_columns: &[],
foreign_key_checks: crate::mutation::referential::checks::referencing_checks(
context.constraints,
destination_table,
rewritten_doc_id,
&prepared.new_document,
None,
)?,
},
)? {
after_row_events.push(event);
}
if prepared.capture_partition_move_update_transition {
if let Some(updated_columns) = trigger_updated_columns.as_deref() {
if let Some(event) =
crate::mutation::triggers::AfterRowTriggerEvent::prepare_transition_capture(
&context.triggers,
crate::mutation::triggers::AfterRowTriggerInput {
table: &prepared.table,
event: uqa_sql::ast::TriggerEvent::Update,
old_doc_id: prepared.doc_id,
new_doc_id: rewritten_doc_id,
old_document: Some(&prepared.old_document),
new_document: Some(&prepared.new_document),
updated_columns,
foreign_key_checks: Vec::new(),
},
)?
{
after_row_events.push(event);
}
}
}
if let Some(event) = crate::mutation::triggers::AfterRowTriggerEvent::foreign_key_checks(
prepared.moved_through.as_deref().unwrap_or(&prepared.table),
uqa_sql::ast::TriggerEvent::Update,
crate::mutation::referential::checks::referenced_checks(
context.constraints,
&prepared.table,
&prepared.old_document,
Some(&prepared.new_document),
prepared.moved_through.as_deref(),
)?,
) {
after_row_events.push(event);
}
} else {
let mut foreign_key_checks = crate::mutation::referential::checks::referenced_checks(
context.constraints,
&prepared.table,
&prepared.old_document,
Some(&prepared.new_document),
None,
)?;
foreign_key_checks.extend(crate::mutation::referential::checks::referencing_checks(
context.constraints,
&prepared.table,
rewritten_doc_id,
&prepared.new_document,
Some(&prepared.old_document),
)?);
let event = match trigger_updated_columns.as_deref() {
Some(updated_columns) => crate::mutation::triggers::AfterRowTriggerEvent::prepare(
&context.triggers,
crate::mutation::triggers::AfterRowTriggerInput {
table: &prepared.table,
event: uqa_sql::ast::TriggerEvent::Update,
old_doc_id: prepared.doc_id,
new_doc_id: rewritten_doc_id,
old_document: Some(&prepared.old_document),
new_document: Some(&prepared.new_document),
updated_columns,
foreign_key_checks,
},
)?,
None => crate::mutation::triggers::AfterRowTriggerEvent::foreign_key_checks(
&prepared.table,
uqa_sql::ast::TriggerEvent::Update,
foreign_key_checks,
),
};
if let Some(event) = event {
after_row_events.push(event);
}
}
Ok(rewritten_doc_id)
}
pub fn stage_referential_rewrite(
context: MutationStagingContext<'_>,
action: &mut PreparedDocumentRewrite,
params: &[SQLParam],
after_row_events: &mut Vec<crate::mutation::triggers::AfterRowTriggerEvent>,
) -> Result<DocId, SQLError> {
let referential = action.referential_action.clone().ok_or_else(|| {
SQLError::Internal("a referential action rewrite does not name its foreign key".into())
})?;
stage_prepared_document_rewrite(
context,
action,
params,
ConstraintStatement::referential_action(&referential.relation, &referential.columns),
None,
after_row_events,
)
}
pub fn stage_prepared_document_delete(
context: MutationStagingContext<'_>,
prepared: &PreparedDocumentDelete,
after_row_events: &mut Vec<crate::mutation::triggers::AfterRowTriggerEvent>,
) -> Result<(), SQLError> {
let foreign_key_checks = crate::mutation::referential::checks::referenced_checks(
context.constraints,
&prepared.table,
&prepared.document,
None,
None,
)?;
stage_document_delete(&context, prepared, foreign_key_checks, after_row_events)
}
fn stage_document_delete(
context: &MutationStagingContext<'_>,
prepared: &PreparedDocumentDelete,
foreign_key_checks: Vec<crate::mutation::referential::checks::ForeignKeyCheck>,
after_row_events: &mut Vec<crate::mutation::triggers::AfterRowTriggerEvent>,
) -> Result<(), SQLError> {
context
.commands
.stage_command_document(&prepared.table, prepared.doc_id, None)?;
if let Some(event) = crate::mutation::triggers::AfterRowTriggerEvent::prepare(
&context.triggers,
crate::mutation::triggers::AfterRowTriggerInput {
table: &prepared.table,
event: uqa_sql::ast::TriggerEvent::Delete,
old_doc_id: prepared.doc_id,
new_doc_id: prepared.doc_id,
old_document: Some(&prepared.document),
new_document: None,
updated_columns: &[],
foreign_key_checks,
},
)? {
after_row_events.push(event);
}
Ok(())
}