use crate::{
errors::ExecutorError, expression_index_maintenance, partial_index_maintenance, TriggerFirer,
};
pub fn handle_replace_conflicts(
db: &mut vibesql_storage::Database,
table_name: &str,
_storage_table_name: &str,
schema: &vibesql_catalog::TableSchema,
row_values: &[vibesql_types::SqlValue],
) -> Result<(), ExecutorError> {
let mut pk_match: Option<Vec<vibesql_types::SqlValue>> = None;
let mut unique_matches: Vec<Option<Vec<vibesql_types::SqlValue>>> = Vec::new();
let pk_indices = schema.get_primary_key_indices();
let unique_constraint_indices = schema.get_unique_constraint_indices();
if let Some(ref pk_idx) = pk_indices {
pk_match = Some(pk_idx.iter().map(|&idx| row_values[idx].clone()).collect());
}
for unique_indices in unique_constraint_indices.iter() {
let unique_values: Vec<vibesql_types::SqlValue> =
unique_indices.iter().map(|&idx| row_values[idx].clone()).collect();
if unique_values.contains(&vibesql_types::SqlValue::Null) {
unique_matches.push(None);
} else {
unique_matches.push(Some(unique_values));
}
}
let table = db
.get_table(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
let mut rows_to_delete: Vec<(usize, vibesql_storage::Row)> = Vec::new();
for (row_index, row) in table.scan_live() {
let mut should_delete = false;
if let Some(ref pk_values) = pk_match {
if let Some(ref pk_idx) = pk_indices {
let row_pk_values: Vec<vibesql_types::SqlValue> =
pk_idx.iter().map(|&idx| row.values[idx].clone()).collect();
if &row_pk_values == pk_values {
should_delete = true;
}
}
}
if !should_delete {
for (constraint_idx, unique_indices) in unique_constraint_indices.iter().enumerate() {
if let Some(unique_values) =
unique_matches.get(constraint_idx).and_then(|v| v.as_ref())
{
let row_unique_values: Vec<vibesql_types::SqlValue> =
unique_indices.iter().map(|&idx| row.values[idx].clone()).collect();
if row_unique_values == *unique_values {
should_delete = true;
break;
}
}
}
}
if !should_delete {
for index_name in db.list_indexes_for_table(table_name) {
if let Some(index_metadata) = db.get_index(&index_name) {
if !index_metadata.unique {
continue;
}
let mut new_key_values = Vec::new();
let mut existing_key_values = Vec::new();
let mut valid_index = true;
for index_col in &index_metadata.columns {
let col_name = index_col.expect_column_name();
if let Some(col_idx) = schema.get_column_index(col_name) {
new_key_values.push(row_values[col_idx].clone());
existing_key_values.push(row.values[col_idx].clone());
} else {
valid_index = false;
break;
}
}
if !valid_index {
continue;
}
if new_key_values.contains(&vibesql_types::SqlValue::Null) {
continue;
}
if existing_key_values.contains(&vibesql_types::SqlValue::Null) {
continue;
}
if new_key_values == existing_key_values {
should_delete = true;
break;
}
}
}
}
if should_delete {
rows_to_delete.push((row_index, row.clone()));
}
}
if rows_to_delete.is_empty() {
return Ok(());
}
let has_delete_triggers = db
.catalog
.get_triggers_for_table(table_name, Some(vibesql_ast::TriggerEvent::Delete))
.next()
.is_some();
if has_delete_triggers {
let mut kept = Vec::with_capacity(rows_to_delete.len());
for (idx, row) in rows_to_delete {
let outcome = TriggerFirer::execute_before_triggers(
db,
table_name,
vibesql_ast::TriggerEvent::Delete,
Some(&row),
None,
)?;
if outcome != crate::trigger_execution::TriggerOutcome::SkipRow {
kept.push((idx, row));
}
}
rows_to_delete = kept;
if rows_to_delete.is_empty() {
return Ok(());
}
}
let rows_refs: Vec<(usize, &vibesql_storage::Row)> =
rows_to_delete.iter().map(|(idx, row)| (*idx, row)).collect();
db.batch_update_indexes_for_delete(table_name, &rows_refs);
for (row_index, row) in &rows_to_delete {
expression_index_maintenance::maintain_expression_indexes_for_delete(
db, table_name, row, *row_index,
);
partial_index_maintenance::maintain_partial_indexes_for_delete(
db, table_name, row, *row_index,
);
}
let mut deleted_indices: Vec<usize> = rows_to_delete.iter().map(|(idx, _)| *idx).collect();
deleted_indices.sort_unstable();
#[cfg(feature = "mvcc_enabled")]
let mvcc_delete_txn_id = db.transaction_id();
let table_mut = db
.get_table_mut(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
#[cfg(feature = "mvcc_enabled")]
if let Some(id) = mvcc_delete_txn_id {
for &idx in &deleted_indices {
table_mut.stamp_row_xmax_inplace(idx, id);
}
}
let delete_result = table_mut.delete_by_indices_batch(&deleted_indices);
if delete_result.compacted {
db.rebuild_indexes(table_name);
partial_index_maintenance::rebuild_partial_indexes_after_compaction(db, table_name);
}
if delete_result.deleted_count > 0 {
db.invalidate_columnar_cache(table_name);
}
if has_delete_triggers {
for (_, row) in &rows_to_delete {
let _after_outcome = TriggerFirer::execute_after_triggers(
db,
table_name,
vibesql_ast::TriggerEvent::Delete,
Some(row),
None,
)?;
}
}
Ok(())
}