use std::collections::{HashMap, HashSet};
use vibesql_catalog::TableSchema;
use vibesql_storage::{Database, Row, Table};
use vibesql_types::SqlValue;
use super::PendingUpdate;
use crate::errors::ExecutorError;
pub(super) fn find_conflicting_rows_for_update(
table: &Table,
schema: &TableSchema,
database: &Database,
table_name: &str,
new_row: &Row,
current_row_index: usize,
) -> Vec<usize> {
let mut conflicting_indices = Vec::new();
if let Some(pk_indices) = schema.get_primary_key_indices() {
let new_pk_values: Vec<SqlValue> =
pk_indices.iter().map(|&idx| new_row.values[idx].clone()).collect();
if !new_pk_values.contains(&SqlValue::Null) {
if let Some(pk_index) = table.primary_key_index() {
if let Some(&existing_idx) = pk_index.get(&new_pk_values) {
if existing_idx != current_row_index {
conflicting_indices.push(existing_idx);
}
}
}
}
}
let unique_constraint_indices = schema.get_unique_constraint_indices();
for (constraint_idx, unique_indices) in unique_constraint_indices.iter().enumerate() {
let new_unique_values: Vec<SqlValue> =
unique_indices.iter().map(|&idx| new_row.values[idx].clone()).collect();
if new_unique_values.contains(&SqlValue::Null) {
continue;
}
let unique_indexes = table.unique_indexes();
if constraint_idx < unique_indexes.len() {
if let Some(&existing_idx) = unique_indexes[constraint_idx].get(&new_unique_values) {
if existing_idx != current_row_index {
conflicting_indices.push(existing_idx);
}
}
}
}
for index_name in database.list_indexes_for_table(table_name) {
if let Some(index_metadata) = database.get_index(&index_name) {
if !index_metadata.unique {
continue;
}
let mut key_values = Vec::new();
let mut is_expression_index = false;
for index_col in &index_metadata.columns {
if index_col.get_expression().is_some() {
is_expression_index = true;
break;
}
if let Some(col_name) = index_col.column_name() {
if let Some(col_idx) = schema.get_column_index(col_name) {
key_values.push(new_row.values[col_idx].clone());
}
} else {
is_expression_index = true;
break;
}
}
if is_expression_index {
continue;
}
if key_values.contains(&SqlValue::Null) {
continue;
}
if let Some(index_data) = database.get_index_data(&index_name) {
if let Some(existing_indices) = index_data.get(&key_values) {
for existing_idx in existing_indices {
if existing_idx != current_row_index {
conflicting_indices.push(existing_idx);
}
}
}
}
}
}
conflicting_indices
}
pub(super) fn detect_surviving_replace_conflict(
updates: &[PendingUpdate],
schema: &TableSchema,
surviving_conflict_rows: &[(usize, Row)],
database: &Database,
table_name: &str,
) -> Result<(), ExecutorError> {
if surviving_conflict_rows.is_empty() || updates.is_empty() {
return Ok(());
}
let surviving_indices: HashSet<usize> =
surviving_conflict_rows.iter().map(|(idx, _)| *idx).collect();
if let Some(pk_indices) = schema.get_primary_key_indices() {
let mut surviving_pks: HashSet<Vec<SqlValue>> = HashSet::new();
for (_, row) in surviving_conflict_rows {
let pk: Vec<SqlValue> =
pk_indices.iter().map(|&i| row.values[i].clone()).collect();
if !pk.contains(&SqlValue::Null) {
surviving_pks.insert(pk);
}
}
for u in updates {
if surviving_indices.contains(&u.row_index) {
continue;
}
let new_pk: Vec<SqlValue> =
pk_indices.iter().map(|&i| u.new_row.values[i].clone()).collect();
if new_pk.contains(&SqlValue::Null) {
continue;
}
if surviving_pks.contains(&new_pk) {
let pk_col_names = schema.primary_key.as_ref().unwrap();
let qualified: Vec<String> =
pk_col_names.iter().map(|c| format!("{}.{}", schema.name, c)).collect();
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: {}",
qualified.join(", ")
)));
}
}
}
let unique_constraint_indices = schema.get_unique_constraint_indices();
for (constraint_idx, unique_indices) in unique_constraint_indices.iter().enumerate() {
let mut surviving_keys: HashSet<Vec<SqlValue>> = HashSet::new();
for (_, row) in surviving_conflict_rows {
let key: Vec<SqlValue> =
unique_indices.iter().map(|&i| row.values[i].clone()).collect();
if !key.contains(&SqlValue::Null) {
surviving_keys.insert(key);
}
}
for u in updates {
if surviving_indices.contains(&u.row_index) {
continue;
}
let new_key: Vec<SqlValue> =
unique_indices.iter().map(|&i| u.new_row.values[i].clone()).collect();
if new_key.contains(&SqlValue::Null) {
continue;
}
if surviving_keys.contains(&new_key) {
let unique_col_names = &schema.unique_constraints[constraint_idx];
let qualified: Vec<String> =
unique_col_names.iter().map(|c| format!("{}.{}", schema.name, c)).collect();
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: {}",
qualified.join(", ")
)));
}
}
}
for index_name in database.list_indexes_for_table(table_name) {
let index_metadata = match database.get_index(&index_name) {
Some(m) => m,
None => continue,
};
if !index_metadata.unique {
continue;
}
let mut col_idxs: Vec<usize> = Vec::with_capacity(index_metadata.columns.len());
let mut is_expression_index = false;
for ic in &index_metadata.columns {
if ic.get_expression().is_some() {
is_expression_index = true;
break;
}
match ic.column_name().and_then(|cn| schema.get_column_index(cn)) {
Some(ci) => col_idxs.push(ci),
None => {
is_expression_index = true;
break;
}
}
}
if is_expression_index {
continue;
}
let mut surviving_keys: HashSet<Vec<SqlValue>> = HashSet::new();
for (_, row) in surviving_conflict_rows {
let key: Vec<SqlValue> = col_idxs.iter().map(|&i| row.values[i].clone()).collect();
if !key.contains(&SqlValue::Null) {
surviving_keys.insert(key);
}
}
for u in updates {
if surviving_indices.contains(&u.row_index) {
continue;
}
let new_key: Vec<SqlValue> =
col_idxs.iter().map(|&i| u.new_row.values[i].clone()).collect();
if new_key.contains(&SqlValue::Null) {
continue;
}
if surviving_keys.contains(&new_key) {
let columns_str = index_metadata
.columns
.iter()
.map(|col| format!("{}.{}", table_name, col.column_name().unwrap_or("?")))
.collect::<Vec<_>>()
.join(", ");
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: {}",
columns_str
)));
}
}
}
Ok(())
}
pub(super) fn validate_cross_update_uniqueness(
updates: &[PendingUpdate],
schema: &TableSchema,
) -> Result<(), ExecutorError> {
if let Some(pk_indices) = schema.get_primary_key_indices() {
let mut seen_pks: HashSet<Vec<SqlValue>> = HashSet::new();
for update in updates {
let new_row = &update.new_row;
let pk_values: Vec<SqlValue> =
pk_indices.iter().map(|&idx| new_row.values[idx].clone()).collect();
if pk_values.contains(&SqlValue::Null) {
continue;
}
if !seen_pks.insert(pk_values.clone()) {
let pk_col_names: Vec<String> = schema.primary_key.as_ref().unwrap().clone();
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: {} (multiple rows would have same key)",
pk_col_names.join(", ")
)));
}
}
}
let unique_constraint_indices = schema.get_unique_constraint_indices();
for (constraint_idx, unique_indices) in unique_constraint_indices.iter().enumerate() {
let mut seen_values: HashSet<Vec<SqlValue>> = HashSet::new();
for update in updates {
let new_row = &update.new_row;
let unique_values: Vec<SqlValue> =
unique_indices.iter().map(|&idx| new_row.values[idx].clone()).collect();
if unique_values.contains(&SqlValue::Null) {
continue;
}
if !seen_values.insert(unique_values.clone()) {
let unique_col_names: Vec<String> =
schema.unique_constraints[constraint_idx].clone();
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: {} (multiple rows would have same key)",
unique_col_names.join(", ")
)));
}
}
}
Ok(())
}
pub(super) fn resolve_cross_update_conflicts_for_replace(
updates: &mut Vec<PendingUpdate>,
schema: &TableSchema,
) -> Vec<usize> {
let mut indices_to_delete = Vec::new();
let mut indices_to_remove = HashSet::new();
if let Some(pk_indices) = schema.get_primary_key_indices() {
let mut pk_map: HashMap<Vec<SqlValue>, (usize, usize)> = HashMap::new();
for (pos, update) in updates.iter().enumerate() {
if indices_to_remove.contains(&pos) {
continue;
}
let pk_values: Vec<SqlValue> = pk_indices
.iter()
.map(|&idx| update.new_row.values[idx].clone())
.collect();
if pk_values.contains(&SqlValue::Null) {
continue;
}
if let Some((prev_pos, prev_row_index)) = pk_map.get(&pk_values) {
indices_to_remove.insert(*prev_pos);
indices_to_delete.push(*prev_row_index);
}
pk_map.insert(pk_values, (pos, update.row_index));
}
}
let unique_constraint_indices = schema.get_unique_constraint_indices();
for unique_indices in unique_constraint_indices.iter() {
let mut unique_map: HashMap<Vec<SqlValue>, (usize, usize)> = HashMap::new();
for (pos, update) in updates.iter().enumerate() {
if indices_to_remove.contains(&pos) {
continue;
}
let unique_values: Vec<SqlValue> = unique_indices
.iter()
.map(|&idx| update.new_row.values[idx].clone())
.collect();
if unique_values.contains(&SqlValue::Null) {
continue;
}
if let Some((prev_pos, prev_row_index)) = unique_map.get(&unique_values) {
if !indices_to_remove.contains(prev_pos) {
indices_to_remove.insert(*prev_pos);
indices_to_delete.push(*prev_row_index);
}
}
unique_map.insert(unique_values, (pos, update.row_index));
}
}
let mut remove_positions: Vec<usize> = indices_to_remove.into_iter().collect();
remove_positions.sort_unstable();
remove_positions.reverse();
for pos in remove_positions {
updates.remove(pos);
}
indices_to_delete
}
pub(super) fn validate_post_statement_uniqueness(
updates: &[PendingUpdate],
schema: &TableSchema,
table: &Table,
database: &Database,
table_name: &str,
) -> Result<(), ExecutorError> {
let pk_indices_opt = schema.get_primary_key_indices();
let mut updated_new_pk: HashMap<usize, Vec<SqlValue>> = HashMap::new();
if let Some(ref pk_indices) = pk_indices_opt {
for u in updates {
let new_pk: Vec<SqlValue> =
pk_indices.iter().map(|&i| u.new_row.values[i].clone()).collect();
updated_new_pk.insert(u.row_index, new_pk);
}
}
if let Some(ref pk_indices) = pk_indices_opt {
if let Some(pk_index) = table.primary_key_index() {
for u in updates {
let new_pk: Vec<SqlValue> =
pk_indices.iter().map(|&i| u.new_row.values[i].clone()).collect();
let old_pk: Vec<SqlValue> =
pk_indices.iter().map(|&i| u.old_row.values[i].clone()).collect();
if new_pk == old_pk {
continue;
}
if let Some(&existing_idx) = pk_index.get(&new_pk) {
if existing_idx == u.row_index {
continue;
}
if let Some(other_new_pk) = updated_new_pk.get(&existing_idx) {
if other_new_pk != &new_pk {
continue;
}
}
let pk_col_names: Vec<String> =
schema.primary_key.as_ref().unwrap().clone();
let qualified_cols: Vec<String> = pk_col_names
.iter()
.map(|col| format!("{}.{}", schema.name, col))
.collect();
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: {}",
qualified_cols.join(", ")
)));
}
}
}
}
let unique_constraint_indices = schema.get_unique_constraint_indices();
let unique_indexes = table.unique_indexes();
for (constraint_idx, unique_indices) in unique_constraint_indices.iter().enumerate() {
if constraint_idx >= unique_indexes.len() {
continue; }
let unique_index = &unique_indexes[constraint_idx];
let mut updated_new_unique: HashMap<usize, Vec<SqlValue>> = HashMap::new();
for u in updates {
let new_uv: Vec<SqlValue> =
unique_indices.iter().map(|&i| u.new_row.values[i].clone()).collect();
updated_new_unique.insert(u.row_index, new_uv);
}
for u in updates {
let new_uv: Vec<SqlValue> =
unique_indices.iter().map(|&i| u.new_row.values[i].clone()).collect();
let old_uv: Vec<SqlValue> =
unique_indices.iter().map(|&i| u.old_row.values[i].clone()).collect();
if new_uv.contains(&SqlValue::Null) {
continue;
}
if new_uv == old_uv {
continue;
}
if let Some(&existing_idx) = unique_index.get(&new_uv) {
if existing_idx == u.row_index {
continue;
}
if let Some(other_new_uv) = updated_new_unique.get(&existing_idx) {
if other_new_uv != &new_uv {
continue;
}
}
let unique_col_names: Vec<String> =
schema.unique_constraints[constraint_idx].clone();
let qualified_cols: Vec<String> = unique_col_names
.iter()
.map(|col| format!("{}.{}", schema.name, col))
.collect();
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: {}",
qualified_cols.join(", ")
)));
}
}
}
for index_name in database.list_indexes_for_table(table_name) {
let index_metadata = match database.get_index(&index_name) {
Some(m) => m,
None => continue,
};
if !index_metadata.unique {
continue;
}
let mut col_idxs: Vec<usize> = Vec::with_capacity(index_metadata.columns.len());
let mut is_expression_index = false;
for ic in &index_metadata.columns {
if ic.get_expression().is_some() {
is_expression_index = true;
break;
}
let cn = match ic.column_name() {
Some(n) => n,
None => {
is_expression_index = true;
break;
}
};
match schema.get_column_index(cn) {
Some(ci) => col_idxs.push(ci),
None => {
is_expression_index = true;
break;
}
}
}
if is_expression_index {
continue;
}
let mut updated_new_key: HashMap<usize, Vec<SqlValue>> = HashMap::new();
for u in updates {
let nk: Vec<SqlValue> =
col_idxs.iter().map(|&i| u.new_row.values[i].clone()).collect();
updated_new_key.insert(u.row_index, nk);
}
let index_data = match database.get_index_data(&index_name) {
Some(d) => d,
None => continue,
};
for u in updates {
let new_key: Vec<SqlValue> =
col_idxs.iter().map(|&i| u.new_row.values[i].clone()).collect();
let old_key: Vec<SqlValue> =
col_idxs.iter().map(|&i| u.old_row.values[i].clone()).collect();
if new_key.contains(&SqlValue::Null) {
continue;
}
if new_key == old_key {
continue;
}
let conflicting_rows = match index_data.get(&new_key) {
Some(v) => v,
None => continue,
};
let mut real_conflict = false;
for existing_idx in &conflicting_rows {
if *existing_idx == u.row_index {
continue;
}
if let Some(other_new_key) = updated_new_key.get(existing_idx) {
if other_new_key != &new_key {
continue;
}
}
real_conflict = true;
break;
}
if real_conflict {
let columns_str = index_metadata
.columns
.iter()
.map(|col| {
format!("{}.{}", table_name, col.column_name().unwrap_or("?"))
})
.collect::<Vec<_>>()
.join(", ");
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: {}",
columns_str
)));
}
}
}
Ok(())
}
pub(super) fn validate_rowid_relocation(
updates: &[PendingUpdate],
schema: &TableSchema,
table: &Table,
) -> Result<(), ExecutorError> {
let ipk_col = schema.rowid_alias_column;
let effective_rowid = |row: &Row, physical_index: usize| -> Option<u64> {
match ipk_col {
Some(idx) => match &row.values[idx] {
SqlValue::Integer(i) => Some(*i as u64),
SqlValue::Bigint(i) => Some(*i as u64),
_ => None,
},
None => Some(row.row_id.unwrap_or((physical_index + 1) as u64)),
}
};
let mut relocations: Vec<(u64, u64)> = Vec::new(); for u in updates {
let new_rowid = match effective_rowid(&u.new_row, u.row_index) {
Some(id) => id,
None => continue,
};
let old_rowid = match effective_rowid(&u.old_row, u.row_index) {
Some(id) => id,
None => continue,
};
if new_rowid != old_rowid {
relocations.push((old_rowid, new_rowid));
}
}
if relocations.is_empty() {
return Ok(());
}
let mut occupied: HashSet<u64> = table
.scan_live()
.filter_map(|(i, row)| effective_rowid(row, i))
.collect();
relocations.sort_unstable_by_key(|&(old_rowid, _)| old_rowid);
let conflict_column = match ipk_col {
Some(idx) => format!("{}.{}", schema.name, schema.columns[idx].name),
None => format!("{}.rowid", schema.name),
};
for (old_rowid, new_rowid) in relocations {
occupied.remove(&old_rowid);
if !occupied.insert(new_rowid) {
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: {}",
conflict_column
)));
}
}
Ok(())
}
pub(super) fn validate_unique_relocation(
updates: &[PendingUpdate],
schema: &TableSchema,
table: &Table,
database: &Database,
table_name: &str,
) -> Result<(), ExecutorError> {
if updates.is_empty() {
return Ok(());
}
let order_rowid = |row: &Row, physical_index: usize| -> u64 {
row.row_id.unwrap_or((physical_index + 1) as u64)
};
let check_key_space =
|col_idxs: &[usize], conflict_label: &str| -> Result<(), ExecutorError> {
if col_idxs.is_empty() {
return Ok(());
}
let key_of = |row: &Row| -> Vec<SqlValue> {
col_idxs.iter().map(|&i| row.values[i].clone()).collect()
};
let mut relocations: Vec<(u64, Vec<SqlValue>, Vec<SqlValue>)> = Vec::new();
for u in updates {
let old_key = key_of(&u.old_row);
let new_key = key_of(&u.new_row);
if old_key == new_key {
continue; }
relocations.push((order_rowid(&u.old_row, u.row_index), old_key, new_key));
}
if relocations.is_empty() {
return Ok(());
}
let mut occupied: HashSet<Vec<SqlValue>> = table
.scan_live()
.map(|(_, row)| key_of(row))
.filter(|k| !k.contains(&SqlValue::Null))
.collect();
relocations.sort_by_key(|&(order, _, _)| order);
for (_order, old_key, new_key) in relocations {
if !old_key.contains(&SqlValue::Null) {
occupied.remove(&old_key);
}
if new_key.contains(&SqlValue::Null) {
continue;
}
if !occupied.insert(new_key) {
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: {}",
conflict_label
)));
}
}
Ok(())
};
if schema.rowid_alias_column.is_none() {
if let (Some(pk_indices), Some(pk_cols)) =
(schema.get_primary_key_indices(), schema.primary_key.as_ref())
{
let label = pk_cols
.iter()
.map(|c| format!("{}.{}", schema.name, c))
.collect::<Vec<_>>()
.join(", ");
check_key_space(&pk_indices, &label)?;
}
}
let unique_constraint_indices = schema.get_unique_constraint_indices();
for (constraint_idx, unique_indices) in unique_constraint_indices.iter().enumerate() {
let label = schema.unique_constraints[constraint_idx]
.iter()
.map(|c| format!("{}.{}", schema.name, c))
.collect::<Vec<_>>()
.join(", ");
check_key_space(unique_indices, &label)?;
}
for index_name in database.list_indexes_for_table(table_name) {
let index_metadata = match database.get_index(&index_name) {
Some(m) => m,
None => continue,
};
if !index_metadata.unique {
continue;
}
let mut col_idxs: Vec<usize> = Vec::with_capacity(index_metadata.columns.len());
let mut is_expression_index = false;
for ic in &index_metadata.columns {
if ic.get_expression().is_some() {
is_expression_index = true;
break;
}
match ic.column_name().and_then(|cn| schema.get_column_index(cn)) {
Some(ci) => col_idxs.push(ci),
None => {
is_expression_index = true;
break;
}
}
}
if is_expression_index {
continue;
}
let label = index_metadata
.columns
.iter()
.map(|col| format!("{}.{}", table_name, col.column_name().unwrap_or("?")))
.collect::<Vec<_>>()
.join(", ");
check_key_space(&col_idxs, &label)?;
}
Ok(())
}