#![allow(dead_code)]
pub mod soak;
use async_trait::async_trait;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use sz_orm_core::DbError;
use sz_orm_core::Value;
use sz_orm_core::{Connection, ConnectionFactory};
use tokio::sync::Mutex;
#[derive(Debug, Default, Clone)]
pub struct InMemoryDb {
tables: std::collections::HashMap<String, Vec<std::collections::HashMap<String, Value>>>,
}
impl InMemoryDb {
pub fn new() -> Self {
Self {
tables: std::collections::HashMap::new(),
}
}
pub fn create_table(&mut self, name: &str) {
self.tables.insert(name.to_string(), Vec::new());
}
pub fn insert(&mut self, table: &str, row: std::collections::HashMap<String, Value>) {
self.tables.entry(table.to_string()).or_default().push(row);
}
pub fn select_all(&self, table: &str) -> &[std::collections::HashMap<String, Value>] {
self.tables.get(table).map(|v| v.as_slice()).unwrap_or(&[])
}
pub fn delete_where(&mut self, table: &str, field: &str, value: &Value) -> u64 {
if let Some(rows) = self.tables.get_mut(table) {
let before = rows.len();
rows.retain(|r| r.get(field) != Some(value));
(before - rows.len()) as u64
} else {
0
}
}
pub fn update_where(
&mut self,
table: &str,
cond_field: &str,
cond_value: &Value,
set_field: &str,
set_value: Value,
) -> u64 {
if let Some(rows) = self.tables.get_mut(table) {
let mut count = 0u64;
for row in rows.iter_mut() {
if row.get(cond_field) == Some(cond_value) {
row.insert(set_field.to_string(), set_value.clone());
count += 1;
}
}
count
} else {
0
}
}
pub fn count(&self, table: &str) -> usize {
self.tables.get(table).map(|v| v.len()).unwrap_or(0)
}
pub fn select_where(
&self,
table: &str,
field: &str,
value: &Value,
) -> Vec<std::collections::HashMap<String, Value>> {
self.tables
.get(table)
.map(|rows| {
rows.iter()
.filter(|r| r.get(field) == Some(value))
.cloned()
.collect()
})
.unwrap_or_default()
}
pub fn find_where(
&self,
table: &str,
field: &str,
value: &Value,
) -> Option<std::collections::HashMap<String, Value>> {
self.tables
.get(table)
.and_then(|rows| rows.iter().find(|r| r.get(field) == Some(value)).cloned())
}
pub fn snapshot(&self, table: &str) -> Vec<std::collections::HashMap<String, Value>> {
self.tables.get(table).cloned().unwrap_or_default()
}
pub fn replace_table(
&mut self,
table: &str,
rows: Vec<std::collections::HashMap<String, Value>>,
) {
self.tables.insert(table.to_string(), rows);
}
pub fn deep_clone(&self) -> Self {
self.clone()
}
pub fn sum_i64(&self, table: &str, field: &str) -> i64 {
self.tables
.get(table)
.map(|rows| {
rows.iter()
.filter_map(|r| r.get(field).and_then(|v| v.as_i64()))
.sum()
})
.unwrap_or(0)
}
}
pub struct MockConnection {
db: Arc<Mutex<InMemoryDb>>,
connected: bool,
in_transaction: bool,
pub executed_sql: Vec<String>,
}
impl MockConnection {
pub fn new(db: Arc<Mutex<InMemoryDb>>) -> Self {
Self {
db,
connected: true,
in_transaction: false,
executed_sql: Vec::new(),
}
}
}
impl Connection for MockConnection {
fn execute<'a>(
&'a mut self,
sql: &'a str,
) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
Box::pin(async move {
self.executed_sql.push(sql.to_string());
Ok(1)
})
}
fn query<'a>(
&'a mut self,
sql: &'a str,
) -> Pin<
Box<
dyn Future<Output = Result<Vec<std::collections::HashMap<String, Value>>, DbError>>
+ Send
+ 'a,
>,
> {
Box::pin(async move {
self.executed_sql.push(sql.to_string());
Ok(vec![])
})
}
fn begin_transaction<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
self.in_transaction = true;
Ok(())
})
}
fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
self.in_transaction = false;
Ok(())
})
}
fn rollback<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
self.in_transaction = false;
Ok(())
})
}
fn is_connected(&self) -> bool {
self.connected
}
fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
Box::pin(async move { self.connected })
}
fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
self.connected = false;
Ok(())
})
}
}
pub struct FaultyConnection {
db: Arc<Mutex<InMemoryDb>>,
connected: bool,
pub fail_on_execute_n: Option<u32>,
execute_count: u32,
pub fail_on_commit: bool,
pub fail_on_rollback: bool,
}
impl FaultyConnection {
pub fn new(db: Arc<Mutex<InMemoryDb>>) -> Self {
Self {
db,
connected: true,
fail_on_execute_n: None,
execute_count: 0,
fail_on_commit: false,
fail_on_rollback: false,
}
}
}
impl Connection for FaultyConnection {
fn execute<'a>(
&'a mut self,
sql: &'a str,
) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
Box::pin(async move {
self.execute_count += 1;
if let Some(n) = self.fail_on_execute_n {
if self.execute_count >= n {
self.connected = false;
return Err(DbError::ConnectionError("injected fault".to_string()));
}
}
let _ = sql;
Ok(1)
})
}
fn query<'a>(
&'a mut self,
_sql: &'a str,
) -> Pin<
Box<
dyn Future<Output = Result<Vec<std::collections::HashMap<String, Value>>, DbError>>
+ Send
+ 'a,
>,
> {
Box::pin(async move { Ok(vec![]) })
}
fn begin_transaction<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move { Ok(()) })
}
fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
if self.fail_on_commit {
return Err(DbError::Internal("injected commit fault".to_string()));
}
Ok(())
})
}
fn rollback<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
if self.fail_on_rollback {
return Err(DbError::Internal("injected rollback fault".to_string()));
}
Ok(())
})
}
fn is_connected(&self) -> bool {
self.connected
}
fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
Box::pin(async move { self.connected })
}
fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
self.connected = false;
Ok(())
})
}
}
pub struct MockConnectionFactory {
db: Arc<Mutex<InMemoryDb>>,
}
impl MockConnectionFactory {
pub fn new(db: Arc<Mutex<InMemoryDb>>) -> Self {
Self { db }
}
}
#[async_trait]
impl ConnectionFactory for MockConnectionFactory {
async fn create(&self) -> Result<Box<dyn Connection>, DbError> {
Ok(Box::new(MockConnection::new(self.db.clone())))
}
}
pub struct FaultyConnectionFactory {
db: Arc<Mutex<InMemoryDb>>,
pub faulty_nth: u32,
counter: Mutex<u32>,
}
impl FaultyConnectionFactory {
pub fn new(db: Arc<Mutex<InMemoryDb>>, faulty_nth: u32) -> Self {
Self {
db,
faulty_nth,
counter: Mutex::new(0),
}
}
}
#[async_trait]
impl ConnectionFactory for FaultyConnectionFactory {
async fn create(&self) -> Result<Box<dyn Connection>, DbError> {
let mut count = self.counter.lock().await;
*count += 1;
if *count == self.faulty_nth {
return Err(DbError::ConnectionError(
"injected factory fault".to_string(),
));
}
Ok(Box::new(MockConnection::new(self.db.clone())))
}
}
pub struct Rng {
state: u64,
}
impl Rng {
pub fn new(seed: u64) -> Self {
Self {
state: if seed == 0 { 0xdeadbeef } else { seed },
}
}
pub fn next_u64(&mut self) -> u64 {
let mut x = self.state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.state = x;
x
}
pub fn next_u32(&mut self) -> u32 {
self.next_u64() as u32
}
pub fn next_usize(&mut self, max: usize) -> usize {
if max == 0 {
return 0;
}
(self.next_u64() as usize) % max
}
pub fn next_i64(&mut self) -> i64 {
self.next_u64() as i64
}
pub fn next_bool(&mut self) -> bool {
self.next_u64().is_multiple_of(2)
}
pub fn next_f64(&mut self) -> f64 {
(self.next_u64() as f64) / (u64::MAX as f64)
}
pub fn next_bytes(&mut self, len: usize) -> Vec<u8> {
(0..len).map(|_| self.next_u64() as u8).collect()
}
pub fn next_string(&mut self, len: usize) -> String {
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()'\"\\;-- \n\r\t\x00";
(0..len)
.map(|_| chars.as_bytes()[self.next_usize(chars.len())] as char)
.collect()
}
}
pub struct TransactionalConnection {
db: Arc<Mutex<InMemoryDb>>,
snapshots: Vec<InMemoryDb>,
savepoint_names: std::collections::HashMap<String, usize>,
connected: bool,
pub executed_sql: Vec<String>,
}
impl TransactionalConnection {
pub fn new(db: Arc<Mutex<InMemoryDb>>) -> Self {
Self {
db,
snapshots: Vec::new(),
savepoint_names: std::collections::HashMap::new(),
connected: true,
executed_sql: Vec::new(),
}
}
pub fn db_handle(&self) -> Arc<Mutex<InMemoryDb>> {
self.db.clone()
}
fn parse_value(literal: &str) -> Value {
let trimmed = literal.trim();
if trimmed.eq_ignore_ascii_case("NULL") {
return Value::Null;
}
if trimmed.starts_with('\'') && trimmed.ends_with('\'') && trimmed.len() >= 2 {
let inner = &trimmed[1..trimmed.len() - 1];
return Value::String(inner.to_string());
}
if let Ok(i) = trimmed.parse::<i64>() {
return Value::I64(i);
}
if let Ok(f) = trimmed.parse::<f64>() {
return Value::F64(f);
}
if trimmed.eq_ignore_ascii_case("true") {
return Value::Bool(true);
}
if trimmed.eq_ignore_ascii_case("false") {
return Value::Bool(false);
}
Value::String(trimmed.to_string())
}
fn split_csv(s: &str) -> Vec<String> {
s.split(',').map(|p| p.trim().to_string()).collect()
}
fn apply_insert(db: &mut InMemoryDb, sql: &str) -> u64 {
let upper = sql.to_uppercase();
let Some(values_pos) = upper.find("VALUES") else {
return 0;
};
let Some(open_paren) = sql.find('(') else {
return 0;
};
let Some(close_paren) = sql[open_paren..].find(')') else {
return 0;
};
let cols_str = &sql[open_paren + 1..open_paren + close_paren];
let vals_start = values_pos + 6; let Some(vals_open) = sql[vals_start..].find('(') else {
return 0;
};
let vals_open_abs = vals_start + vals_open;
let Some(vals_close) = sql[vals_open_abs..].find(')') else {
return 0;
};
let vals_close_abs = vals_open_abs + vals_close;
let vals_str = &sql[vals_open_abs + 1..vals_close_abs];
let after_into = sql.find("INTO").map(|p| p + 4).unwrap_or(0);
let table_part = sql[after_into..open_paren].trim();
let table = table_part.trim_matches(|c: char| c == '`' || c == '"');
let cols = Self::split_csv(cols_str);
let vals = Self::split_csv(vals_str);
let mut row = std::collections::HashMap::new();
for (col, val) in cols.iter().zip(vals.iter()) {
row.insert(col.clone(), Self::parse_value(val));
}
db.insert(table, row);
1
}
fn apply_delete(db: &mut InMemoryDb, sql: &str) -> u64 {
let upper = sql.to_uppercase();
let Some(from_pos) = upper.find("FROM") else {
return 0;
};
let where_pos = upper.find("WHERE");
let (table_part, cond_part) = if let Some(wp) = where_pos {
(sql[from_pos + 4..wp].trim(), sql[wp + 5..].trim())
} else {
(sql[from_pos + 4..].trim(), "")
};
let table = table_part.trim_matches(|c: char| c == '`' || c == '"');
if cond_part.is_empty() {
let count = db.count(table) as u64;
let rows: Vec<_> = db.snapshot(table);
db.replace_table(table, Vec::new());
let _ = rows;
return count;
}
if let Some(eq_pos) = cond_part.find('=') {
let col = cond_part[..eq_pos]
.trim()
.trim_matches(|c: char| c == '`' || c == '"');
let val = Self::parse_value(&cond_part[eq_pos + 1..]);
return db.delete_where(table, col, &val);
}
0
}
fn apply_update(db: &mut InMemoryDb, sql: &str) -> u64 {
let upper = sql.to_uppercase();
let Some(set_pos) = upper.find("SET") else {
return 0;
};
let where_pos = upper.find("WHERE");
let table_part = sql[7..set_pos].trim(); let table = table_part.trim_matches(|c: char| c == '`' || c == '"');
let (set_part, cond_part) = if let Some(wp) = where_pos {
(sql[set_pos + 3..wp].trim(), sql[wp + 5..].trim())
} else {
(sql[set_pos + 3..].trim(), "")
};
let Some(set_eq) = set_part.find('=') else {
return 0;
};
let set_col = set_part[..set_eq]
.trim()
.trim_matches(|c: char| c == '`' || c == '"');
let set_val = Self::parse_value(&set_part[set_eq + 1..]);
if cond_part.is_empty() {
let rows = db.snapshot(table);
let count = rows.len() as u64;
for mut row in rows {
row.insert(set_col.to_string(), set_val.clone());
db.insert(table, row);
}
return count;
}
if let Some(eq_pos) = cond_part.find('=') {
let cond_col = cond_part[..eq_pos]
.trim()
.trim_matches(|c: char| c == '`' || c == '"');
let cond_val = Self::parse_value(&cond_part[eq_pos + 1..]);
return db.update_where(table, cond_col, &cond_val, set_col, set_val);
}
0
}
async fn handle_savepoint(&mut self, name: &str) -> u64 {
let db = self.db.lock().await;
self.snapshots.push(db.clone());
self.savepoint_names
.insert(name.to_string(), self.snapshots.len() - 1);
0
}
async fn handle_rollback_to_savepoint(&mut self, name: &str) -> u64 {
if let Some(&idx) = self.savepoint_names.get(name) {
if idx < self.snapshots.len() {
let snapshot = self.snapshots[idx].clone();
let mut db = self.db.lock().await;
*db = snapshot;
self.snapshots.truncate(idx);
self.savepoint_names.retain(|_, v| *v < idx);
}
}
0
}
async fn handle_release_savepoint(&mut self, name: &str) -> u64 {
if let Some(idx) = self.savepoint_names.remove(name) {
if idx < self.snapshots.len() {
self.snapshots.remove(idx);
for v in self.savepoint_names.values_mut() {
if *v > idx {
*v -= 1;
}
}
}
}
0
}
}
impl Connection for TransactionalConnection {
fn execute<'a>(
&'a mut self,
sql: &'a str,
) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
Box::pin(async move {
self.executed_sql.push(sql.to_string());
let upper = sql.to_uppercase();
if upper.starts_with("SAVEPOINT") {
let name = sql[9..].trim();
return Ok(Self::handle_savepoint(self, name).await);
}
if upper.starts_with("ROLLBACK TO SAVEPOINT") {
let name = sql[21..].trim();
return Ok(Self::handle_rollback_to_savepoint(self, name).await);
}
if upper.starts_with("RELEASE SAVEPOINT") {
let name = sql[17..].trim();
return Ok(Self::handle_release_savepoint(self, name).await);
}
let mut db = self.db.lock().await;
let affected = if upper.starts_with("INSERT") {
Self::apply_insert(&mut db, sql)
} else if upper.starts_with("DELETE") {
Self::apply_delete(&mut db, sql)
} else if upper.starts_with("UPDATE") {
Self::apply_update(&mut db, sql)
} else {
0
};
Ok(affected)
})
}
fn query<'a>(
&'a mut self,
sql: &'a str,
) -> Pin<
Box<
dyn Future<Output = Result<Vec<std::collections::HashMap<String, Value>>, DbError>>
+ Send
+ 'a,
>,
> {
Box::pin(async move {
self.executed_sql.push(sql.to_string());
let upper = sql.to_uppercase();
if !upper.starts_with("SELECT") {
return Ok(vec![]);
}
let Some(from_pos) = upper.find("FROM") else {
return Ok(vec![]);
};
let where_pos = upper.find("WHERE");
let cols_str = sql[6..from_pos].trim(); let cols: Vec<String> = if cols_str == "*" {
Vec::new() } else {
cols_str.split(',').map(|s| s.trim().to_string()).collect()
};
let (table_part, cond_part) = if let Some(wp) = where_pos {
(sql[from_pos + 4..wp].trim(), sql[wp + 5..].trim())
} else {
(sql[from_pos + 4..].trim(), "")
};
let table = table_part.trim_matches(|c: char| c == '`' || c == '"');
let db = self.db.lock().await;
let all_rows = db.select_all(table);
let filtered: Vec<&std::collections::HashMap<String, Value>> = if cond_part.is_empty() {
all_rows.iter().collect()
} else if let Some(eq_pos) = cond_part.find('=') {
let col = cond_part[..eq_pos]
.trim()
.trim_matches(|c: char| c == '`' || c == '"');
let val = Self::parse_value(&cond_part[eq_pos + 1..]);
all_rows
.iter()
.filter(|r| r.get(col) == Some(&val))
.collect()
} else {
all_rows.iter().collect()
};
let result: Vec<std::collections::HashMap<String, Value>> = if cols.is_empty() {
filtered.iter().map(|r| (*r).clone()).collect()
} else {
filtered
.iter()
.map(|r| {
let mut row = std::collections::HashMap::new();
for col in &cols {
let col_clean = col.trim_matches(|c: char| c == '`' || c == '"');
if let Some(v) = r.get(col_clean) {
row.insert(col_clean.to_string(), v.clone());
}
}
row
})
.collect()
};
Ok(result)
})
}
fn begin_transaction<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
let db = self.db.lock().await;
self.snapshots.clear();
self.savepoint_names.clear();
self.snapshots.push(db.clone());
Ok(())
})
}
fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
self.snapshots.clear();
self.savepoint_names.clear();
Ok(())
})
}
fn rollback<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
if let Some(snapshot) = self.snapshots.first().cloned() {
let mut db = self.db.lock().await;
*db = snapshot;
}
self.snapshots.clear();
self.savepoint_names.clear();
Ok(())
})
}
fn is_connected(&self) -> bool {
self.connected
}
fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
Box::pin(async move { self.connected })
}
fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
self.connected = false;
Ok(())
})
}
}
pub struct TransactionalConnectionFactory {
db: Arc<Mutex<InMemoryDb>>,
}
impl TransactionalConnectionFactory {
pub fn new(db: Arc<Mutex<InMemoryDb>>) -> Self {
Self { db }
}
}
#[async_trait]
impl ConnectionFactory for TransactionalConnectionFactory {
async fn create(&self) -> Result<Box<dyn Connection>, DbError> {
Ok(Box::new(TransactionalConnection::new(self.db.clone())))
}
}