use crate::comm::ExecuteContext;
use crate::errors::Result;
use crate::interceptor::shared::InterceptorBase;
use crate::interceptor::{InterceptorType, OperationType};
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub struct OptimisticLockConfig {
pub column: String,
pub ignore_tables: HashSet<String>,
}
impl Default for OptimisticLockConfig {
fn default() -> Self {
Self {
column: "version".to_string(),
ignore_tables: HashSet::new(),
}
}
}
impl OptimisticLockConfig {
pub fn with_column(mut self, column: &str) -> Self {
self.column = column.to_string();
self
}
pub fn ignore_table(mut self, table: &str) -> Self {
self.ignore_tables.insert(table.to_string());
self
}
}
pub struct OptimisticLockerInterceptor {
config: OptimisticLockConfig,
}
impl OptimisticLockerInterceptor {
pub fn new(config: OptimisticLockConfig) -> Self {
Self { config }
}
pub fn with_default() -> Self {
Self::new(OptimisticLockConfig::default())
}
pub fn config(&self) -> &OptimisticLockConfig {
&self.config
}
pub(crate) fn should_ignore_table(&self, table: &str) -> bool {
self.config.ignore_tables.contains(table)
}
}
impl InterceptorBase for OptimisticLockerInterceptor {
fn name(&self) -> &'static str {
"optimistic_lock"
}
fn interceptor_type(&self) -> InterceptorType {
InterceptorType::OptimisticLock
}
fn order(&self) -> i32 {
25 }
fn will_ignore_table(&self, table_name: &str) -> bool {
self.should_ignore_table(table_name)
}
fn supports_operation(&self, operation: &OperationType) -> bool {
matches!(operation, OperationType::Update)
}
}
#[cfg(any(
feature = "mysql-sync",
feature = "postgres-sync",
feature = "sqlite-sync",
feature = "oracle-sync",
feature = "mssql-sync"
))]
impl crate::interceptor::blocking::AkitaInterceptor for OptimisticLockerInterceptor {
fn before_execute(&self, ctx: &mut ExecuteContext) -> Result<()> {
let table = ctx.table_info().name.clone();
if self.should_ignore_table(&table) {
return Ok(());
}
if !matches!(ctx.operation_type(), OperationType::Update) {
return Ok(());
}
ctx.set_metadata(
"optimistic_lock_column".to_string(),
self.config.column.clone(),
);
ctx.set_metadata("optimistic_lock_active".to_string(), "true".to_string());
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_optimistic_lock_config_default() {
let config = OptimisticLockConfig::default();
assert_eq!(config.column, "version");
assert!(config.ignore_tables.is_empty());
}
#[test]
fn test_optimistic_lock_config_custom() {
let config = OptimisticLockConfig::default()
.with_column("lock_version")
.ignore_table("logs");
assert_eq!(config.column, "lock_version");
assert!(config.ignore_tables.contains("logs"));
}
#[test]
fn test_optimistic_lock_interceptor() {
let interceptor = OptimisticLockerInterceptor::with_default();
assert_eq!(interceptor.name(), "optimistic_lock");
assert_eq!(
interceptor.interceptor_type(),
InterceptorType::OptimisticLock
);
assert_eq!(interceptor.order(), 25);
}
#[test]
fn test_should_ignore_table() {
let interceptor =
OptimisticLockerInterceptor::new(OptimisticLockConfig::default().ignore_table("logs"));
assert!(interceptor.should_ignore_table("logs"));
assert!(!interceptor.should_ignore_table("users"));
}
#[test]
fn test_supports_operation() {
let interceptor = OptimisticLockerInterceptor::with_default();
assert!(interceptor.supports_operation(&OperationType::Update));
assert!(!interceptor.supports_operation(&OperationType::Select));
assert!(!interceptor
.supports_operation(&OperationType::Insert(akita_core::InsertType::SingleInsert)));
assert!(!interceptor.supports_operation(&OperationType::Delete));
}
}