use ops::prelude::*;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
type SearchIndex = Arc<Mutex<HashMap<String, String>>>;
struct UpdateSearchIndexCloseReadOp {
document_id: String,
content: String,
index: SearchIndex,
}
#[async_trait]
impl Op<()> for UpdateSearchIndexCloseReadOp {
async fn perform(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<()> {
self.index
.lock()
.unwrap()
.insert(self.document_id.clone(), self.content.clone());
println!("Updated search index for document: {}", self.document_id);
Ok(())
}
async fn rollback(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<()> {
self.index.lock().unwrap().remove(&self.document_id);
println!(
"Rolled back search index for document: {}",
self.document_id
);
Ok(())
}
fn metadata(&self) -> OpMetadata {
OpMetadata::builder("UpdateSearchIndexCloseReadOp")
.description("Updates search index with document content")
.build()
}
}
struct DatabaseOp {
document_id: String,
should_fail: bool,
}
#[async_trait]
impl Op<()> for DatabaseOp {
async fn perform(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<()> {
if self.should_fail {
return Err(OpError::ExecutionFailed(
"Database operation failed".to_string(),
));
}
println!(
"Database operation succeeded for document: {}",
self.document_id
);
Ok(())
}
fn metadata(&self) -> OpMetadata {
OpMetadata::builder("DatabaseOp")
.description("Performs database operation")
.build()
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Custom Rollback Example ===\n");
let search_index: SearchIndex = Arc::new(Mutex::new(HashMap::new()));
let ops = vec![
Arc::new(UpdateSearchIndexCloseReadOp {
document_id: "doc1".to_string(),
content: "First document content".to_string(),
index: search_index.clone(),
}) as Arc<dyn Op<()>>,
Arc::new(UpdateSearchIndexCloseReadOp {
document_id: "doc2".to_string(),
content: "Second document content".to_string(),
index: search_index.clone(),
}) as Arc<dyn Op<()>>,
Arc::new(DatabaseOp {
document_id: "doc1".to_string(),
should_fail: true, }) as Arc<dyn Op<()>>,
];
println!("Initial search index state:");
println!("{:?}\n", *search_index.lock().unwrap());
let batch = ops::BatchOp::new(ops);
let mut dry = DryContext::new();
let mut wet = WetContext::new();
println!("Executing batch operations...\n");
match batch.perform(&mut dry, &mut wet).await {
Ok(_) => println!("Batch succeeded (unexpected)"),
Err(e) => println!("Batch failed as expected: {}\n", e),
}
println!("Final search index state (should be empty due to rollback):");
println!("{:?}", *search_index.lock().unwrap());
println!("\n=== Summary ===");
println!("This example demonstrates how:");
println!("1. UpdateSearchIndexCloseReadOp implements custom rollback logic");
println!("2. When DatabaseOp fails, the batch automatically rolls back");
println!("3. Search index entries are cleaned up via custom rollback");
println!("4. Transaction-based ops (DatabaseOp) can rely on transaction rollback");
Ok(())
}