use crate::{cilassembly::TableOperation, Error, Result};
use std::collections::HashMap;
pub trait ConflictResolver {
fn resolve_conflict(&self, conflicts: &[Conflict]) -> Result<Resolution>;
}
#[derive(Debug)]
pub enum Conflict {
MultipleOperationsOnRid {
rid: u32,
operations: Vec<TableOperation>,
},
InsertDeleteConflict {
rid: u32,
insert_op: TableOperation,
delete_op: TableOperation,
},
}
#[derive(Debug, Default)]
pub struct Resolution {
pub operations: HashMap<u32, OperationResolution>,
}
#[derive(Debug)]
pub enum OperationResolution {
UseOperation(TableOperation),
UseLatest,
Merge(Vec<TableOperation>),
Reject(String),
}
pub struct LastWriteWinsResolver;
impl ConflictResolver for LastWriteWinsResolver {
fn resolve_conflict(&self, conflicts: &[Conflict]) -> Result<Resolution> {
let mut resolution_map = HashMap::new();
for conflict in conflicts {
match conflict {
Conflict::MultipleOperationsOnRid { rid, operations } => {
if let Some(latest_op) = operations.iter().max_by_key(|op| op.timestamp) {
resolution_map
.insert(*rid, OperationResolution::UseOperation(latest_op.clone()));
}
}
Conflict::InsertDeleteConflict {
rid,
insert_op,
delete_op,
} => {
if !insert_op.is_insert() {
return Err(Error::ConflictResolution(format!(
"InsertDeleteConflict for RID {rid}: insert_op is not an Insert operation"
)));
}
if !delete_op.is_delete() {
return Err(Error::ConflictResolution(format!(
"InsertDeleteConflict for RID {rid}: delete_op is not a Delete operation"
)));
}
let winning_op = if insert_op.timestamp >= delete_op.timestamp {
insert_op
} else {
delete_op
};
resolution_map
.insert(*rid, OperationResolution::UseOperation(winning_op.clone()));
}
}
}
Ok(Resolution {
operations: resolution_map,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{cilassembly::Operation, test::factories::table::cilassembly::create_test_row};
#[test]
fn test_last_write_wins_resolver_multiple_operations() {
let operations = vec![
{
let mut op = TableOperation::new(Operation::Insert(100, create_test_row()));
op.timestamp = 1000; op
},
{
let mut op = TableOperation::new(Operation::Update(100, create_test_row()));
op.timestamp = 2000; op
},
];
let conflict = Conflict::MultipleOperationsOnRid {
rid: 100,
operations,
};
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[conflict]);
assert!(result.is_ok(), "Conflict resolution should succeed");
if let Ok(resolution) = result {
assert!(
resolution.operations.contains_key(&100),
"Should resolve RID 100"
);
if let Some(OperationResolution::UseOperation(op)) = resolution.operations.get(&100) {
assert!(
matches!(op.operation, Operation::Update(100, _)),
"Should use Update operation"
);
} else {
panic!("Expected UseOperation resolution");
}
}
}
#[test]
fn test_last_write_wins_resolver_single_operation_in_conflict() {
let operations = vec![{
let mut op = TableOperation::new(Operation::Delete(50));
op.timestamp = 5000;
op
}];
let conflict = Conflict::MultipleOperationsOnRid {
rid: 50,
operations,
};
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[conflict]).unwrap();
assert!(result.operations.contains_key(&50));
if let Some(OperationResolution::UseOperation(op)) = result.operations.get(&50) {
assert!(matches!(op.operation, Operation::Delete(50)));
assert_eq!(op.timestamp, 5000);
} else {
panic!("Expected UseOperation resolution");
}
}
#[test]
fn test_last_write_wins_resolver_equal_timestamps() {
let operations = vec![
{
let mut op = TableOperation::new(Operation::Insert(100, create_test_row()));
op.timestamp = 1000;
op
},
{
let mut op = TableOperation::new(Operation::Update(100, create_test_row()));
op.timestamp = 1000; op
},
];
let conflict = Conflict::MultipleOperationsOnRid {
rid: 100,
operations,
};
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[conflict]);
assert!(result.is_ok(), "Should handle equal timestamps");
let resolution = result.unwrap();
assert!(resolution.operations.contains_key(&100));
}
#[test]
fn test_last_write_wins_resolver_insert_delete_conflict() {
let insert_op = {
let mut op = TableOperation::new(Operation::Insert(100, create_test_row()));
op.timestamp = 1000; op
};
let delete_op = {
let mut op = TableOperation::new(Operation::Delete(100));
op.timestamp = 2000; op
};
let conflict = Conflict::InsertDeleteConflict {
rid: 100,
insert_op,
delete_op,
};
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[conflict]);
assert!(result.is_ok(), "Conflict resolution should succeed");
if let Ok(resolution) = result {
assert!(
resolution.operations.contains_key(&100),
"Should resolve RID 100"
);
if let Some(OperationResolution::UseOperation(op)) = resolution.operations.get(&100) {
assert!(
matches!(op.operation, Operation::Delete(100)),
"Should use Delete operation (later timestamp)"
);
} else {
panic!("Expected UseOperation resolution");
}
}
}
#[test]
fn test_last_write_wins_resolver_insert_wins_over_delete() {
let insert_op = {
let mut op = TableOperation::new(Operation::Insert(100, create_test_row()));
op.timestamp = 3000; op
};
let delete_op = {
let mut op = TableOperation::new(Operation::Delete(100));
op.timestamp = 1000; op
};
let conflict = Conflict::InsertDeleteConflict {
rid: 100,
insert_op,
delete_op,
};
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[conflict]).unwrap();
if let Some(OperationResolution::UseOperation(op)) = result.operations.get(&100) {
assert!(
matches!(op.operation, Operation::Insert(100, _)),
"Should use Insert operation (later timestamp)"
);
} else {
panic!("Expected UseOperation resolution");
}
}
#[test]
fn test_last_write_wins_resolver_insert_delete_equal_timestamps() {
let insert_op = {
let mut op = TableOperation::new(Operation::Insert(100, create_test_row()));
op.timestamp = 1000;
op
};
let delete_op = {
let mut op = TableOperation::new(Operation::Delete(100));
op.timestamp = 1000; op
};
let conflict = Conflict::InsertDeleteConflict {
rid: 100,
insert_op,
delete_op,
};
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[conflict]).unwrap();
if let Some(OperationResolution::UseOperation(op)) = result.operations.get(&100) {
assert!(
matches!(op.operation, Operation::Insert(100, _)),
"Insert should win on equal timestamps"
);
} else {
panic!("Expected UseOperation resolution");
}
}
#[test]
fn test_last_write_wins_resolver_empty_conflict_vector() {
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[]);
assert!(result.is_ok());
let resolution = result.unwrap();
assert!(
resolution.operations.is_empty(),
"Empty conflicts should produce empty resolution"
);
}
#[test]
fn test_last_write_wins_resolver_multiple_conflicts() {
let conflict1 = Conflict::MultipleOperationsOnRid {
rid: 10,
operations: vec![{
let mut op = TableOperation::new(Operation::Delete(10));
op.timestamp = 1000;
op
}],
};
let conflict2 = Conflict::InsertDeleteConflict {
rid: 20,
insert_op: {
let mut op = TableOperation::new(Operation::Insert(20, create_test_row()));
op.timestamp = 2000;
op
},
delete_op: {
let mut op = TableOperation::new(Operation::Delete(20));
op.timestamp = 1000;
op
},
};
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[conflict1, conflict2]).unwrap();
assert!(result.operations.contains_key(&10));
assert!(result.operations.contains_key(&20));
if let Some(OperationResolution::UseOperation(op)) = result.operations.get(&10) {
assert!(matches!(op.operation, Operation::Delete(10)));
}
if let Some(OperationResolution::UseOperation(op)) = result.operations.get(&20) {
assert!(matches!(op.operation, Operation::Insert(20, _)));
}
}
#[test]
fn test_resolution_default() {
let resolution = Resolution::default();
assert!(resolution.operations.is_empty());
}
#[test]
fn test_operation_resolution_variants() {
let row_data = create_test_row();
let use_op = OperationResolution::UseOperation(TableOperation::new(Operation::Delete(1)));
assert!(matches!(use_op, OperationResolution::UseOperation(_)));
let use_latest = OperationResolution::UseLatest;
assert!(matches!(use_latest, OperationResolution::UseLatest));
let merge = OperationResolution::Merge(vec![
TableOperation::new(Operation::Insert(1, row_data.clone())),
TableOperation::new(Operation::Update(1, row_data)),
]);
if let OperationResolution::Merge(ops) = merge {
assert_eq!(ops.len(), 2);
}
let reject = OperationResolution::Reject("Conflict cannot be resolved".to_string());
if let OperationResolution::Reject(msg) = reject {
assert_eq!(msg, "Conflict cannot be resolved");
}
}
#[test]
fn test_insert_delete_conflict_validation_invalid_insert_op() {
let insert_op = {
let mut op = TableOperation::new(Operation::Delete(100));
op.timestamp = 1000;
op
};
let delete_op = {
let mut op = TableOperation::new(Operation::Delete(100));
op.timestamp = 2000;
op
};
let conflict = Conflict::InsertDeleteConflict {
rid: 100,
insert_op,
delete_op,
};
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[conflict]);
assert!(result.is_err(), "Should fail when insert_op is not Insert");
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("insert_op is not an Insert operation"),
"Error message should indicate invalid insert_op"
);
}
#[test]
fn test_insert_delete_conflict_validation_invalid_delete_op() {
let insert_op = {
let mut op = TableOperation::new(Operation::Insert(100, create_test_row()));
op.timestamp = 1000;
op
};
let delete_op = {
let mut op = TableOperation::new(Operation::Insert(100, create_test_row()));
op.timestamp = 2000;
op
};
let conflict = Conflict::InsertDeleteConflict {
rid: 100,
insert_op,
delete_op,
};
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[conflict]);
assert!(result.is_err(), "Should fail when delete_op is not Delete");
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("delete_op is not a Delete operation"),
"Error message should indicate invalid delete_op"
);
}
#[test]
fn test_insert_delete_conflict_validation_update_operations() {
let insert_op = {
let mut op = TableOperation::new(Operation::Update(100, create_test_row()));
op.timestamp = 1000;
op
};
let delete_op = {
let mut op = TableOperation::new(Operation::Update(100, create_test_row()));
op.timestamp = 2000;
op
};
let conflict = Conflict::InsertDeleteConflict {
rid: 100,
insert_op,
delete_op,
};
let resolver = LastWriteWinsResolver;
let result = resolver.resolve_conflict(&[conflict]);
assert!(
result.is_err(),
"Should fail when operations are not correct types"
);
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("insert_op is not an Insert operation"),
"Error should indicate insert_op validation failed first"
);
}
}