use crate::errors::ExecutorError;
pub fn handle_duplicate_key_update(
db: &mut vibesql_storage::Database,
table_name: &str,
schema: &vibesql_catalog::TableSchema,
row_values: &[vibesql_types::SqlValue],
assignments: &[vibesql_ast::Assignment],
) -> Result<Option<usize>, ExecutorError> {
let conflicting_row_id = find_conflicting_row(db, table_name, schema, row_values)?;
if let Some(row_id) = conflicting_row_id {
update_conflicting_row(db, table_name, schema, row_id, row_values, assignments)?;
db.invalidate_columnar_cache(table_name);
Ok(Some(row_id))
} else {
Ok(None)
}
}
fn find_conflicting_row(
db: &vibesql_storage::Database,
table_name: &str,
schema: &vibesql_catalog::TableSchema,
row_values: &[vibesql_types::SqlValue],
) -> Result<Option<usize>, ExecutorError> {
let table = db
.get_table(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
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 {
let pk_values: Vec<vibesql_types::SqlValue> =
pk_idx.iter().map(|&idx| row_values[idx].clone()).collect();
for (row_id, row) in table.scan_live() {
let row_pk_values: Vec<vibesql_types::SqlValue> =
pk_idx.iter().map(|&idx| row.values[idx].clone()).collect();
if row_pk_values == pk_values {
return Ok(Some(row_id));
}
}
}
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) {
continue;
}
for (row_id, row) in table.scan_live() {
let row_unique_values: Vec<vibesql_types::SqlValue> =
unique_indices.iter().map(|&idx| row.values[idx].clone()).collect();
if row_unique_values == unique_values {
return Ok(Some(row_id));
}
}
}
Ok(None)
}
fn update_conflicting_row(
db: &mut vibesql_storage::Database,
table_name: &str,
schema: &vibesql_catalog::TableSchema,
row_id: usize,
insert_values: &[vibesql_types::SqlValue],
assignments: &[vibesql_ast::Assignment],
) -> Result<(), ExecutorError> {
let table = db
.get_table(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
let existing_row = table
.scan()
.get(row_id)
.ok_or_else(|| ExecutorError::UnsupportedExpression("Row not found".to_string()))?;
let mut new_row_values = existing_row.values.clone();
for assignment in assignments {
let column_idx =
schema.columns.iter().position(|col| col.name == assignment.column).ok_or_else(
|| {
ExecutorError::UnsupportedExpression(format!(
"Column '{}' not found in table '{}'",
assignment.column, table_name
))
},
)?;
let new_value = evaluate_duplicate_key_expression(
&assignment.value,
schema,
&existing_row.values,
insert_values,
)?;
new_row_values[column_idx] = new_value;
}
let table_mut = db
.get_table_mut(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
table_mut
.update_row(row_id, vibesql_storage::Row::new(new_row_values))
.map_err(|e| ExecutorError::UnsupportedExpression(format!("Storage error: {}", e)))?;
Ok(())
}
fn evaluate_duplicate_key_expression(
expr: &vibesql_ast::Expression,
schema: &vibesql_catalog::TableSchema,
existing_row_values: &[vibesql_types::SqlValue],
insert_values: &[vibesql_types::SqlValue],
) -> Result<vibesql_types::SqlValue, ExecutorError> {
match expr {
vibesql_ast::Expression::DuplicateKeyValue { column } => {
let column_idx =
schema.columns.iter().position(|col| col.name == *column).ok_or_else(|| {
ExecutorError::UnsupportedExpression(format!(
"Column '{}' not found in VALUES() function",
column
))
})?;
Ok(insert_values[column_idx].clone())
}
vibesql_ast::Expression::ColumnRef(col_id) => {
let column = col_id.column_canonical();
let column_idx =
schema.columns.iter().position(|col| col.name == column).ok_or_else(|| {
ExecutorError::UnsupportedExpression(format!("Column '{}' not found", column))
})?;
Ok(existing_row_values[column_idx].clone())
}
vibesql_ast::Expression::Literal(value) => Ok(value.clone()),
vibesql_ast::Expression::BinaryOp { op, left, right } => {
let left_val = evaluate_duplicate_key_expression(
left,
schema,
existing_row_values,
insert_values,
)?;
let right_val = evaluate_duplicate_key_expression(
right,
schema,
existing_row_values,
insert_values,
)?;
match op {
vibesql_ast::BinaryOperator::Plus => {
match (&left_val, &right_val) {
(vibesql_types::SqlValue::Integer(l), vibesql_types::SqlValue::Integer(r)) => {
Ok(vibesql_types::SqlValue::Integer(l + r))
}
_ => Err(ExecutorError::UnsupportedExpression(format!(
"Binary operation {:?} not yet fully supported in ON DUPLICATE KEY UPDATE for these types",
op
))),
}
}
_ => Err(ExecutorError::UnsupportedExpression(format!(
"Binary operation {:?} not yet fully supported in ON DUPLICATE KEY UPDATE",
op
))),
}
}
_ => {
Err(ExecutorError::UnsupportedExpression(format!(
"Expression type not yet supported in ON DUPLICATE KEY UPDATE: {:?}",
expr
)))
}
}
}