use super::*;
fn create_users_table(engine: &RelationalEngine) {
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
Column::new("email", ColumnType::String).nullable(),
]);
engine.create_table("users", schema).unwrap();
}
fn create_posts_table(engine: &RelationalEngine) {
let schema = Schema::new(vec![
Column::new("user_id", ColumnType::Int),
Column::new("title", ColumnType::String),
Column::new("views", ColumnType::Int),
]);
engine.create_table("posts", schema).unwrap();
}
#[test]
fn create_table_and_insert() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
let id = engine.insert("users", values).unwrap();
assert_eq!(id, 1);
}
#[test]
fn store_accessor_returns_underlying_store() {
let store = TensorStore::new();
let engine = RelationalEngine::with_store(store.clone());
let accessed_store = engine.store();
store.put("test_key", TensorData::new()).unwrap();
assert!(accessed_store.exists("test_key"));
}
#[test]
fn insert_1000_rows_select_with_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..1000 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + (i % 50)));
engine.insert("users", values).unwrap();
}
assert_eq!(engine.row_count("users").unwrap(), 1000);
let rows = engine
.select("users", Condition::Eq("age".to_string(), Value::Int(25)))
.unwrap();
assert_eq!(rows.len(), 20);
for row in &rows {
assert_eq!(row.get("age"), Some(&Value::Int(25)));
}
}
#[test]
fn select_with_range_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..100 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
let rows = engine
.select("users", Condition::Ge("age".to_string(), Value::Int(90)))
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn select_with_compound_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..100 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
let condition = Condition::Ge("age".to_string(), Value::Int(40))
.and(Condition::Lt("age".to_string(), Value::Int(50)));
let rows = engine.select("users", condition).unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn join_two_tables() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_posts_table(&engine);
for i in 1..=5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
let post_data = vec![
(1, "Post A", 100),
(1, "Post B", 200),
(2, "Post C", 150),
(3, "Post D", 50),
(3, "Post E", 75),
(3, "Post F", 25),
];
for (user_id, title, views) in post_data {
let mut values = HashMap::new();
values.insert("user_id".to_string(), Value::Int(user_id));
values.insert("title".to_string(), Value::String(title.to_string()));
values.insert("views".to_string(), Value::Int(views));
engine.insert("posts", values).unwrap();
}
let joined = engine.join("users", "posts", "_id", "user_id").unwrap();
assert_eq!(joined.len(), 6);
let user1_posts: Vec<_> = joined.iter().filter(|(u, _)| u.id == 1).collect();
assert_eq!(user1_posts.len(), 2);
let user3_posts: Vec<_> = joined.iter().filter(|(u, _)| u.id == 3).collect();
assert_eq!(user3_posts.len(), 3);
}
#[test]
fn update_modifies_correct_rows() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
}
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(30));
let count = engine
.update(
"users",
Condition::Lt("_id".to_string(), Value::Int(6)),
updates,
)
.unwrap();
assert_eq!(count, 5);
let rows = engine
.select("users", Condition::Eq("age".to_string(), Value::Int(30)))
.unwrap();
assert_eq!(rows.len(), 5);
let rows = engine
.select("users", Condition::Eq("age".to_string(), Value::Int(25)))
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn delete_removes_correct_rows() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..20 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
assert_eq!(engine.row_count("users").unwrap(), 20);
let count = engine
.delete_rows("users", Condition::Lt("age".to_string(), Value::Int(10)))
.unwrap();
assert_eq!(count, 10);
assert_eq!(engine.row_count("users").unwrap(), 10);
let remaining = engine.select("users", Condition::True).unwrap();
for row in remaining {
if let Some(Value::Int(age)) = row.get("age") {
assert!(*age >= 10);
}
}
}
#[test]
fn delete_data_is_gone() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("ToDelete".to_string()));
values.insert("age".to_string(), Value::Int(99));
let id = engine.insert("users", values).unwrap();
let rows = engine
.select(
"users",
Condition::Eq("_id".to_string(), Value::Int(id as i64)),
)
.unwrap();
assert_eq!(rows.len(), 1);
engine
.delete_rows(
"users",
Condition::Eq("_id".to_string(), Value::Int(id as i64)),
)
.unwrap();
let rows = engine
.select(
"users",
Condition::Eq("_id".to_string(), Value::Int(id as i64)),
)
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn table_not_found_error() {
let engine = RelationalEngine::new();
let result = engine.select("nonexistent", Condition::True);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn duplicate_table_error() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let schema = Schema::new(vec![Column::new("x", ColumnType::Int)]);
let result = engine.create_table("users", schema);
assert!(matches!(
result,
Err(RelationalError::TableAlreadyExists(_))
));
}
#[test]
fn type_mismatch_error() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::Int(123));
values.insert("age".to_string(), Value::Int(30));
let result = engine.insert("users", values);
assert!(matches!(result, Err(RelationalError::TypeMismatch { .. })));
}
#[test]
fn null_not_allowed_error() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::Null);
values.insert("age".to_string(), Value::Int(30));
let result = engine.insert("users", values);
assert!(matches!(result, Err(RelationalError::NullNotAllowed(_))));
}
#[test]
fn nullable_column_accepts_null() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".to_string()));
values.insert("age".to_string(), Value::Int(30));
values.insert("email".to_string(), Value::Null);
let id = engine.insert("users", values).unwrap();
assert!(id > 0);
}
#[test]
fn drop_table() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
assert!(engine.table_exists("users"));
engine.drop_table("users").unwrap();
assert!(!engine.table_exists("users"));
let result = engine.select("users", Condition::True);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn or_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
let condition = Condition::Eq("age".to_string(), Value::Int(0))
.or(Condition::Eq("age".to_string(), Value::Int(9)));
let rows = engine.select("users", condition).unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn row_id_in_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
let rows = engine
.select("users", Condition::Eq("_id".to_string(), Value::Int(3)))
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].id, 3);
}
#[test]
fn error_display_all_variants() {
let e1 = RelationalError::TableNotFound("test".into());
assert!(format!("{e1}").contains("test"));
let e2 = RelationalError::TableAlreadyExists("test".into());
assert!(format!("{e2}").contains("test"));
let e3 = RelationalError::ColumnNotFound("col".into());
assert!(format!("{e3}").contains("col"));
let e4 = RelationalError::TypeMismatch {
column: "age".into(),
expected: ColumnType::Int,
};
assert!(format!("{e4}").contains("age"));
let e5 = RelationalError::NullNotAllowed("name".into());
assert!(format!("{e5}").contains("name"));
let e6 = RelationalError::StorageError("disk full".into());
assert!(format!("{e6}").contains("disk full"));
}
#[test]
fn error_is_error_trait() {
let err: &dyn std::error::Error = &RelationalError::TableNotFound("x".into());
assert!(err.to_string().contains('x'));
}
#[test]
fn engine_default_trait() {
let engine = RelationalEngine::default();
assert!(!engine.table_exists("any"));
}
#[test]
fn engine_with_store() {
let store = TensorStore::new();
let engine = RelationalEngine::with_store(store);
assert!(!engine.table_exists("any"));
}
#[test]
fn row_get_returns_none_for_id() {
let values = vec![("name".to_string(), Value::String("test".into()))];
let row = Row { id: 1, values };
assert!(row.get("_id").is_none());
assert_eq!(row.get_with_id("_id"), Some(Value::Int(1)));
}
#[test]
fn condition_ne() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
let rows = engine
.select("users", Condition::Ne("age".to_string(), Value::Int(2)))
.unwrap();
assert_eq!(rows.len(), 4);
}
#[test]
fn condition_le() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
let rows = engine
.select("users", Condition::Le("age".to_string(), Value::Int(5)))
.unwrap();
assert_eq!(rows.len(), 6);
}
#[test]
fn condition_gt() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
let rows = engine
.select("users", Condition::Gt("age".to_string(), Value::Int(7)))
.unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn float_comparisons() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("score", ColumnType::Float),
]);
engine.create_table("scores", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("score".to_string(), Value::Float(f64::from(i) * 0.5));
engine.insert("scores", values).unwrap();
}
let lt = engine
.select(
"scores",
Condition::Lt("score".to_string(), Value::Float(2.0)),
)
.unwrap();
assert_eq!(lt.len(), 4);
let le = engine
.select(
"scores",
Condition::Le("score".to_string(), Value::Float(2.0)),
)
.unwrap();
assert_eq!(le.len(), 5);
let gt = engine
.select(
"scores",
Condition::Gt("score".to_string(), Value::Float(3.5)),
)
.unwrap();
assert_eq!(gt.len(), 2);
let ge = engine
.select(
"scores",
Condition::Ge("score".to_string(), Value::Float(3.5)),
)
.unwrap();
assert_eq!(ge.len(), 3);
}
#[test]
fn string_comparisons() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let names = vec!["Alice", "Bob", "Charlie", "David", "Eve"];
for name in &names {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(name.to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
}
let lt = engine
.select(
"users",
Condition::Lt("name".to_string(), Value::String("Charlie".into())),
)
.unwrap();
assert_eq!(lt.len(), 2);
let le = engine
.select(
"users",
Condition::Le("name".to_string(), Value::String("Charlie".into())),
)
.unwrap();
assert_eq!(le.len(), 3);
let gt = engine
.select(
"users",
Condition::Gt("name".to_string(), Value::String("Charlie".into())),
)
.unwrap();
assert_eq!(gt.len(), 2);
let ge = engine
.select(
"users",
Condition::Ge("name".to_string(), Value::String("Charlie".into())),
)
.unwrap();
assert_eq!(ge.len(), 3);
}
#[test]
fn update_column_not_found() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let mut updates = HashMap::new();
updates.insert("nonexistent".to_string(), Value::Int(1));
let result = engine.update("users", Condition::True, updates);
assert!(matches!(result, Err(RelationalError::ColumnNotFound(_))));
}
#[test]
fn update_type_mismatch() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::String("wrong type".into()));
let result = engine.update("users", Condition::True, updates);
assert!(matches!(result, Err(RelationalError::TypeMismatch { .. })));
}
#[test]
fn update_null_not_allowed() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let mut updates = HashMap::new();
updates.insert("name".to_string(), Value::Null);
let result = engine.update("users", Condition::True, updates);
assert!(matches!(result, Err(RelationalError::NullNotAllowed(_))));
}
#[test]
fn drop_nonexistent_table() {
let engine = RelationalEngine::new();
let result = engine.drop_table("nonexistent");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn join_no_matches() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_posts_table(&engine);
let mut user_values = HashMap::new();
user_values.insert("name".to_string(), Value::String("Alice".into()));
user_values.insert("age".to_string(), Value::Int(30));
engine.insert("users", user_values).unwrap();
let mut post_values = HashMap::new();
post_values.insert("user_id".to_string(), Value::Int(999));
post_values.insert("title".to_string(), Value::String("Orphan".into()));
post_values.insert("views".to_string(), Value::Int(0));
engine.insert("posts", post_values).unwrap();
let joined = engine.join("users", "posts", "_id", "user_id").unwrap();
assert_eq!(joined.len(), 0);
}
#[test]
fn empty_table_select() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let rows = engine.select("users", Condition::True).unwrap();
assert!(rows.is_empty());
}
#[test]
fn value_clone_and_eq() {
let v1 = Value::Null;
let v2 = Value::Int(42);
let v3 = Value::Float(3.15);
let v4 = Value::String("test".into());
let v5 = Value::Bool(true);
assert_eq!(v1.clone(), v1);
assert_eq!(v2.clone(), v2);
assert_eq!(v3.clone(), v3);
assert_eq!(v4.clone(), v4);
assert_eq!(v5.clone(), v5);
}
#[test]
fn column_type_clone_and_eq() {
assert_eq!(ColumnType::Int.clone(), ColumnType::Int);
assert_eq!(ColumnType::Float.clone(), ColumnType::Float);
assert_eq!(ColumnType::String.clone(), ColumnType::String);
assert_eq!(ColumnType::Bool.clone(), ColumnType::Bool);
}
#[test]
fn schema_get_column() {
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
assert!(schema.get_column("id").is_some());
assert!(schema.get_column("name").is_some());
assert!(schema.get_column("nonexistent").is_none());
}
#[test]
fn value_from_bytes_scalar() {
let bytes_scalar = ScalarValue::Bytes(vec![1, 2, 3]);
let value = Value::from_scalar(&bytes_scalar);
assert_eq!(value, Value::Bytes(vec![1, 2, 3]));
}
#[test]
fn condition_debug() {
let c = Condition::True;
let debug_str = format!("{c:?}");
assert!(debug_str.contains("True"));
}
#[test]
fn row_debug_and_clone() {
let values = vec![("name".to_string(), Value::String("test".into()))];
let row = Row { id: 1, values };
let cloned = row.clone();
assert_eq!(cloned.id, 1);
let debug_str = format!("{row:?}");
assert!(debug_str.contains("Row"));
}
#[test]
fn column_debug_and_clone() {
let col = Column::new("test", ColumnType::Int);
let cloned = col.clone();
assert_eq!(cloned.name, "test");
let debug_str = format!("{col:?}");
assert!(debug_str.contains("Column"));
}
#[test]
fn schema_debug_and_clone() {
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let cloned = schema.clone();
assert_eq!(cloned.columns.len(), 1);
let debug_str = format!("{schema:?}");
assert!(debug_str.contains("Schema"));
}
#[test]
fn error_clone_and_eq() {
let e1 = RelationalError::TableNotFound("test".into());
let e2 = RelationalError::TableAlreadyExists("test".into());
let e3 = RelationalError::ColumnNotFound("col".into());
let e4 = RelationalError::TypeMismatch {
column: "age".into(),
expected: ColumnType::Int,
};
let e5 = RelationalError::NullNotAllowed("name".into());
let e6 = RelationalError::StorageError("err".into());
assert_eq!(e1.clone(), e1);
assert_eq!(e2.clone(), e2);
assert_eq!(e3.clone(), e3);
assert_eq!(e4.clone(), e4);
assert_eq!(e5.clone(), e5);
assert_eq!(e6.clone(), e6);
}
#[test]
fn storage_error_from_tensor_store() {
use tensor_store::TensorStoreError;
let tensor_err = TensorStoreError::NotFound("key".into());
let rel_err: RelationalError = tensor_err.into();
assert!(matches!(rel_err, RelationalError::StorageError(_)));
}
#[test]
fn insert_missing_nullable_column() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
let id = engine.insert("users", values).unwrap();
assert!(id > 0);
}
#[test]
fn comparison_with_mismatched_types_returns_false() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let rows = engine
.select(
"users",
Condition::Lt("age".to_string(), Value::String("30".into())),
)
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn comparison_with_null_column_returns_false() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
values.insert("email".to_string(), Value::Null);
engine.insert("users", values).unwrap();
let rows = engine
.select(
"users",
Condition::Lt("email".to_string(), Value::String("z".into())),
)
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn bool_column_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("active", ColumnType::Bool),
]);
engine.create_table("flags", schema).unwrap();
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("active".to_string(), Value::Bool(true));
let id = engine.insert("flags", values).unwrap();
assert!(id > 0);
let rows = engine
.select(
"flags",
Condition::Eq("active".to_string(), Value::Bool(true)),
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn row_counter_initialization_on_insert() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.drop_table("users").unwrap();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
let id = engine.insert("users", values).unwrap();
assert_eq!(id, 1);
}
#[test]
fn value_debug() {
let v = Value::Int(42);
let debug_str = format!("{v:?}");
assert!(debug_str.contains("Int"));
}
#[test]
fn column_type_debug() {
let ct = ColumnType::Float;
let debug_str = format!("{ct:?}");
assert!(debug_str.contains("Float"));
}
#[test]
fn condition_clone() {
let c1 = Condition::Eq("col".into(), Value::Int(1));
let c2 = Condition::Ne("col".into(), Value::Int(2));
let c3 = Condition::Lt("col".into(), Value::Int(3));
let c4 = Condition::Le("col".into(), Value::Int(4));
let c5 = Condition::Gt("col".into(), Value::Int(5));
let c6 = Condition::Ge("col".into(), Value::Int(6));
let c7 = Condition::True;
let _ = c1;
let _ = c2;
let _ = c3;
let _ = c4;
let _ = c5;
let _ = c6;
let _ = c7;
let c8 = Condition::And(Box::new(Condition::True), Box::new(Condition::True));
let c9 = Condition::Or(Box::new(Condition::True), Box::new(Condition::True));
let _ = c8;
let _ = c9;
}
#[test]
fn row_get_nonexistent_column() {
let row = Row {
id: 1,
values: Vec::new(),
};
assert!(row.get("nonexistent").is_none());
assert!(row.get_with_id("nonexistent").is_none());
}
#[test]
fn compare_le_mismatched_types_returns_false() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let rows = engine
.select(
"users",
Condition::Le("age".to_string(), Value::String("30".into())),
)
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn compare_gt_mismatched_types_returns_false() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let rows = engine
.select(
"users",
Condition::Gt("age".to_string(), Value::String("30".into())),
)
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn compare_ge_mismatched_types_returns_false() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let rows = engine
.select(
"users",
Condition::Ge("age".to_string(), Value::String("30".into())),
)
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn next_row_id_without_counter_initialized() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("manual_table", schema).unwrap();
engine.row_counters.remove("manual_table");
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
let id = engine.insert("manual_table", values).unwrap();
assert_eq!(id, 1);
}
#[test]
fn update_with_nullable_null_value() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Test".into()));
values.insert("age".to_string(), Value::Int(30));
values.insert("email".to_string(), Value::String("test@test.com".into()));
engine.insert("users", values).unwrap();
let mut updates = HashMap::new();
updates.insert("email".to_string(), Value::Null);
let count = engine.update("users", Condition::True, updates).unwrap();
assert_eq!(count, 1);
}
#[test]
fn join_with_null_join_column() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let schema = Schema::new(vec![
Column::new("user_id", ColumnType::Int).nullable(),
Column::new("title", ColumnType::String),
]);
engine.create_table("posts", schema).unwrap();
let mut user_values = HashMap::new();
user_values.insert("name".to_string(), Value::String("Alice".into()));
user_values.insert("age".to_string(), Value::Int(30));
engine.insert("users", user_values).unwrap();
let mut post_values = HashMap::new();
post_values.insert("user_id".to_string(), Value::Null);
post_values.insert("title".to_string(), Value::String("Orphan".into()));
engine.insert("posts", post_values).unwrap();
let joined = engine.join("users", "posts", "_id", "user_id").unwrap();
assert_eq!(joined.len(), 0);
}
#[test]
fn create_and_use_index() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..100 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + (i % 10)));
engine.insert("users", values).unwrap();
}
engine.create_index("users", "age").unwrap();
assert!(engine.has_index("users", "age"));
let rows = engine
.select("users", Condition::Eq("age".to_string(), Value::Int(25)))
.unwrap();
assert_eq!(rows.len(), 10); }
#[test]
fn index_accelerates_select() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..1000 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + (i % 50)));
engine.insert("users", values).unwrap();
}
engine.create_index("users", "age").unwrap();
let rows = engine
.select("users", Condition::Eq("age".to_string(), Value::Int(25)))
.unwrap();
assert_eq!(rows.len(), 20);
for row in &rows {
assert_eq!(row.get("age"), Some(&Value::Int(25)));
}
}
#[test]
fn index_on_id_column() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..100 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
}
engine.create_index("users", "_id").unwrap();
assert!(engine.has_index("users", "_id"));
let rows = engine
.select("users", Condition::Eq("_id".to_string(), Value::Int(50)))
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].id, 50);
}
#[test]
fn drop_index() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_index("users", "age").unwrap();
assert!(engine.has_index("users", "age"));
engine.drop_index("users", "age").unwrap();
assert!(!engine.has_index("users", "age"));
}
#[test]
fn index_already_exists_error() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_index("users", "age").unwrap();
let result = engine.create_index("users", "age");
assert!(matches!(
result,
Err(RelationalError::IndexAlreadyExists { .. })
));
}
#[test]
fn index_not_found_error() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let result = engine.drop_index("users", "age");
assert!(matches!(result, Err(RelationalError::IndexNotFound { .. })));
}
#[test]
fn index_on_nonexistent_column_error() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let result = engine.create_index("users", "nonexistent");
assert!(matches!(result, Err(RelationalError::ColumnNotFound(_))));
}
#[test]
fn index_maintained_on_insert() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_index("users", "age").unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
}
let rows = engine
.select("users", Condition::Eq("age".to_string(), Value::Int(25)))
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn index_maintained_on_update() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
}
engine.create_index("users", "age").unwrap();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(30));
engine
.update(
"users",
Condition::Eq("_id".to_string(), Value::Int(5)),
updates,
)
.unwrap();
let rows_25 = engine
.select("users", Condition::Eq("age".to_string(), Value::Int(25)))
.unwrap();
assert_eq!(rows_25.len(), 9);
let rows_30 = engine
.select("users", Condition::Eq("age".to_string(), Value::Int(30)))
.unwrap();
assert_eq!(rows_30.len(), 1);
}
#[test]
fn index_maintained_on_delete() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
}
engine.create_index("users", "age").unwrap();
engine
.delete_rows("users", Condition::Lt("_id".to_string(), Value::Int(5)))
.unwrap();
let rows = engine
.select("users", Condition::Eq("age".to_string(), Value::Int(25)))
.unwrap();
assert_eq!(rows.len(), 6);
}
#[test]
fn get_indexed_columns() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_index("users", "age").unwrap();
engine.create_index("users", "name").unwrap();
let indexed = engine.get_indexed_columns("users");
assert_eq!(indexed.len(), 2);
assert!(indexed.contains(&"age".to_string()));
assert!(indexed.contains(&"name".to_string()));
}
#[test]
fn drop_table_cleans_up_indexes() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_index("users", "age").unwrap();
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".into()));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
engine.drop_table("users").unwrap();
create_users_table(&engine);
assert!(!engine.has_index("users", "age"));
}
#[test]
fn index_with_compound_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..100 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + (i % 10)));
engine.insert("users", values).unwrap();
}
engine.create_index("users", "age").unwrap();
let condition = Condition::Eq("age".to_string(), Value::Int(25))
.and(Condition::Lt("_id".to_string(), Value::Int(50)));
let rows = engine.select("users", condition).unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn value_hash_key_variants() {
assert_eq!(Value::Null.hash_key(), "null");
assert_eq!(Value::Int(42).hash_key(), "i:42");
assert_eq!(Value::Bool(true).hash_key(), "b:true");
assert_eq!(Value::Bool(false).hash_key(), "b:false");
let float_hash = Value::Float(3.15).hash_key();
assert!(float_hash.starts_with("f:"));
let str_hash = Value::String("hello".into()).hash_key();
assert!(str_hash.starts_with("s:"));
}
#[test]
fn index_error_display() {
let err1 = RelationalError::IndexAlreadyExists {
table: "users".into(),
column: "age".into(),
};
assert_eq!(format!("{err1}"), "Index already exists on users.age");
let err2 = RelationalError::IndexNotFound {
table: "users".into(),
column: "age".into(),
};
assert_eq!(format!("{err2}"), "Index not found on users.age");
}
#[test]
fn index_on_string_column() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..50 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("Name{}", i % 5)));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
}
engine.create_index("users", "name").unwrap();
let rows = engine
.select(
"users",
Condition::Eq("name".to_string(), Value::String("Name2".into())),
)
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn btree_index_accelerates_range_query() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..100 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i64::from(i)));
engine.insert("users", values).unwrap();
}
engine.create_btree_index("users", "age").unwrap();
assert!(engine.has_btree_index("users", "age"));
let rows = engine
.select("users", Condition::Ge("age".to_string(), Value::Int(50)))
.unwrap();
assert_eq!(rows.len(), 50);
let rows = engine
.select("users", Condition::Lt("age".to_string(), Value::Int(25)))
.unwrap();
assert_eq!(rows.len(), 25);
let rows = engine
.select("users", Condition::Gt("age".to_string(), Value::Int(90)))
.unwrap();
assert_eq!(rows.len(), 9);
let rows = engine
.select("users", Condition::Le("age".to_string(), Value::Int(10)))
.unwrap();
assert_eq!(rows.len(), 11);
}
#[test]
fn btree_index_maintained_on_insert() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_btree_index("users", "age").unwrap();
for i in 0..50 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i64::from(i)));
engine.insert("users", values).unwrap();
}
let rows = engine
.select("users", Condition::Lt("age".to_string(), Value::Int(10)))
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn btree_index_maintained_on_update() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..20 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i64::from(i)));
engine.insert("users", values).unwrap();
}
engine.create_btree_index("users", "age").unwrap();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(100));
engine
.update(
"users",
Condition::Lt("age".to_string(), Value::Int(10)),
updates,
)
.unwrap();
let rows = engine
.select("users", Condition::Lt("age".to_string(), Value::Int(10)))
.unwrap();
assert_eq!(rows.len(), 0);
let rows = engine
.select("users", Condition::Ge("age".to_string(), Value::Int(100)))
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn btree_index_maintained_on_delete() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..30 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i64::from(i)));
engine.insert("users", values).unwrap();
}
engine.create_btree_index("users", "age").unwrap();
engine
.delete_rows("users", Condition::Lt("age".to_string(), Value::Int(10)))
.unwrap();
let rows = engine
.select("users", Condition::Lt("age".to_string(), Value::Int(15)))
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn btree_index_drop() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_btree_index("users", "age").unwrap();
assert!(engine.has_btree_index("users", "age"));
engine.drop_btree_index("users", "age").unwrap();
assert!(!engine.has_btree_index("users", "age"));
}
#[test]
fn btree_index_with_negative_numbers() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in -50..50 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
engine.create_btree_index("users", "age").unwrap();
let rows = engine
.select("users", Condition::Lt("age".to_string(), Value::Int(0)))
.unwrap();
assert_eq!(rows.len(), 50);
let rows = engine
.select("users", Condition::Ge("age".to_string(), Value::Int(-25)))
.unwrap();
assert_eq!(rows.len(), 75);
}
#[test]
fn drop_table_cleans_up_btree_indexes() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_btree_index("users", "age").unwrap();
assert!(engine.has_btree_index("users", "age"));
engine.drop_table("users").unwrap();
create_users_table(&engine);
assert!(!engine.has_btree_index("users", "age"));
}
#[test]
fn sortable_key_ordering() {
let v1 = Value::Int(-100);
let v2 = Value::Int(-1);
let v3 = Value::Int(0);
let v4 = Value::Int(1);
let v5 = Value::Int(100);
assert!(v1.sortable_key() < v2.sortable_key());
assert!(v2.sortable_key() < v3.sortable_key());
assert!(v3.sortable_key() < v4.sortable_key());
assert!(v4.sortable_key() < v5.sortable_key());
let f1 = Value::Float(-100.5);
let f2 = Value::Float(-0.1);
let f3 = Value::Float(0.0);
let f4 = Value::Float(0.1);
let f5 = Value::Float(100.5);
assert!(f1.sortable_key() < f2.sortable_key());
assert!(f2.sortable_key() < f3.sortable_key());
assert!(f3.sortable_key() < f4.sortable_key());
assert!(f4.sortable_key() < f5.sortable_key());
let s1 = Value::String("aaa".into());
let s2 = Value::String("bbb".into());
let s3 = Value::String("zzz".into());
assert!(s1.sortable_key() < s2.sortable_key());
assert!(s2.sortable_key() < s3.sortable_key());
let null = Value::Null;
let bool_false = Value::Bool(false);
let bool_true = Value::Bool(true);
assert_eq!(null.sortable_key(), "0");
assert!(bool_false.sortable_key() < bool_true.sortable_key());
assert_eq!(bool_false.sortable_key(), "b0");
assert_eq!(bool_true.sortable_key(), "b1");
}
#[test]
fn get_btree_indexed_columns_returns_columns() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let cols = engine.get_btree_indexed_columns("users");
assert!(cols.is_empty());
engine.create_btree_index("users", "age").unwrap();
let cols = engine.get_btree_indexed_columns("users");
assert_eq!(cols.len(), 1);
assert!(cols.contains(&"age".to_string()));
}
#[test]
fn batch_insert_multiple_rows() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let rows: Vec<HashMap<String, Value>> = (0..100)
.map(|i| {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
values
})
.collect();
let ids = engine.batch_insert("users", rows).unwrap();
assert_eq!(ids.len(), 100);
assert_eq!(engine.row_count("users").unwrap(), 100);
let all_rows = engine.select("users", Condition::True).unwrap();
assert_eq!(all_rows.len(), 100);
}
#[test]
fn batch_insert_empty() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let ids = engine.batch_insert("users", Vec::new()).unwrap();
assert!(ids.is_empty());
assert_eq!(engine.row_count("users").unwrap(), 0);
}
#[test]
fn batch_insert_validates_all_rows_upfront() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let rows: Vec<HashMap<String, Value>> = vec![
{
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".into()));
values.insert("age".to_string(), Value::Int(30));
values
},
{
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Bob".into()));
values.insert("age".to_string(), Value::String("not a number".into())); values
},
];
let result = engine.batch_insert("users", rows);
assert!(result.is_err());
assert_eq!(engine.row_count("users").unwrap(), 0);
}
#[test]
fn batch_insert_with_indexes() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_index("users", "age").unwrap();
let rows: Vec<HashMap<String, Value>> = (0..50)
.map(|i| {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(25)); values
})
.collect();
let ids = engine.batch_insert("users", rows).unwrap();
assert_eq!(ids.len(), 50);
let rows = engine
.select("users", Condition::Eq("age".to_string(), Value::Int(25)))
.unwrap();
assert_eq!(rows.len(), 50);
}
#[test]
fn batch_insert_with_btree_index() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_btree_index("users", "age").unwrap();
let rows: Vec<HashMap<String, Value>> = (0..100)
.map(|i| {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i64::from(i)));
values
})
.collect();
engine.batch_insert("users", rows).unwrap();
let rows = engine
.select("users", Condition::Ge("age".to_string(), Value::Int(50)))
.unwrap();
assert_eq!(rows.len(), 50);
}
#[test]
fn batch_insert_null_not_allowed() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let rows: Vec<HashMap<String, Value>> = vec![
{
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".into()));
values.insert("age".to_string(), Value::Int(30));
values
},
{
let mut values = HashMap::new();
values.insert("age".to_string(), Value::Int(25));
values
},
];
let result = engine.batch_insert("users", rows);
assert!(matches!(result, Err(RelationalError::NullNotAllowed(_))));
}
#[test]
fn list_tables_empty() {
let engine = RelationalEngine::new();
let tables = engine.list_tables();
assert!(tables.is_empty());
}
#[test]
fn list_tables_with_tables() {
let engine = RelationalEngine::new();
engine
.create_table(
"users",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.create_table(
"products",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let tables = engine.list_tables();
assert_eq!(tables.len(), 2);
assert!(tables.contains(&"users".to_string()));
assert!(tables.contains(&"products".to_string()));
}
#[test]
fn materialize_int_column() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..100 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + (i % 50)));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
assert!(engine.has_columnar_data("users", "age"));
let col_data = engine.load_column_data("users", "age").unwrap();
assert_eq!(col_data.row_ids.len(), 100);
if let ColumnValues::Int(values) = col_data.values {
assert_eq!(values.len(), 100);
} else {
panic!("Expected Int column values");
}
}
#[test]
fn materialize_string_column_with_dictionary() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..50 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{}", i % 10)));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["name"]).unwrap();
let col_data = engine.load_column_data("users", "name").unwrap();
if let ColumnValues::String { dict, indices } = col_data.values {
assert_eq!(dict.len(), 10);
assert_eq!(indices.len(), 50);
} else {
panic!("Expected String column values");
}
}
#[test]
fn select_columnar_with_vectorized_filter() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..1000 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i % 100));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let rows = engine
.select_columnar(
"users",
Condition::Gt("age".into(), Value::Int(50)),
options,
)
.unwrap();
assert_eq!(rows.len(), 490);
}
#[test]
fn select_columnar_with_projection() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
values.insert(
"email".to_string(),
Value::String(format!("user{i}@test.com")),
);
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions {
projection: Some(vec!["name".into()]),
prefer_columnar: true,
};
let rows = engine
.select_columnar(
"users",
Condition::Gt("age".into(), Value::Int(25)),
options,
)
.unwrap();
assert_eq!(rows.len(), 4);
for row in &rows {
assert!(row.contains("name"));
assert!(!row.contains("age"));
assert!(!row.contains("email"));
}
}
#[test]
fn select_columnar_compound_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..100 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Ge("age".into(), Value::Int(30))
.and(Condition::Lt("age".into(), Value::Int(40)));
let rows = engine.select_columnar("users", condition, options).unwrap();
assert_eq!(rows.len(), 10);
for row in &rows {
let age = row.get("age").unwrap();
if let Value::Int(a) = age {
assert!(*a >= 30 && *a < 40);
}
}
}
#[test]
fn select_columnar_fallback_to_row_based() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
let options = ColumnarScanOptions {
projection: Some(vec!["name".into()]),
prefer_columnar: true,
};
let rows = engine
.select_columnar(
"users",
Condition::Gt("age".into(), Value::Int(25)),
options,
)
.unwrap();
assert_eq!(rows.len(), 4);
}
#[test]
fn drop_columnar_data() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".into()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
assert!(engine.has_columnar_data("users", "age"));
engine.drop_columnar_data("users", "age").unwrap();
assert!(engine.has_columnar_data("users", "age"));
}
#[test]
fn selection_vector_operations() {
let sel_all = SelectionVector::all(100);
assert_eq!(sel_all.count(), 100);
assert!(sel_all.is_selected(0));
assert!(sel_all.is_selected(99));
assert!(!sel_all.is_selected(100));
let sel_none = SelectionVector::none(100);
assert_eq!(sel_none.count(), 0);
assert!(!sel_none.is_selected(0));
let intersected = sel_all.intersect(&sel_none);
assert_eq!(intersected.count(), 0);
let unioned = sel_all.union(&sel_none);
assert_eq!(unioned.count(), 100);
}
#[test]
fn simd_filter_le() {
let values = vec![1, 5, 3, 8, 2, 9, 4, 7];
let mut bitmap = vec![0u64; 1];
simd::filter_le_i64(&values, 5, &mut bitmap);
assert_eq!(bitmap[0] & 0xff, 0b0101_0111);
}
#[test]
fn simd_filter_ge() {
let values = vec![1, 5, 3, 8, 2, 9, 4, 7];
let mut bitmap = vec![0u64; 1];
simd::filter_ge_i64(&values, 5, &mut bitmap);
assert_eq!(bitmap[0] & 0xff, 0b1010_1010);
}
#[test]
fn simd_filter_ne() {
let values = vec![5, 5, 3, 5, 2, 5, 4, 5];
let mut bitmap = vec![0u64; 1];
simd::filter_ne_i64(&values, 5, &mut bitmap);
assert_eq!(bitmap[0] & 0xff, 0b0101_0100);
}
#[test]
fn column_data_get_value_int() {
let col = ColumnData {
name: "age".into(),
row_ids: vec![1, 2, 3],
nulls: NullBitmap::None,
values: ColumnValues::Int(vec![25, 30, 35]),
};
assert_eq!(col.get_value(0), Some(Value::Int(25)));
assert_eq!(col.get_value(1), Some(Value::Int(30)));
assert_eq!(col.get_value(2), Some(Value::Int(35)));
assert_eq!(col.get_value(10), None);
}
#[test]
fn column_data_get_value_float() {
let col = ColumnData {
name: "score".into(),
row_ids: vec![1, 2],
nulls: NullBitmap::None,
values: ColumnValues::Float(vec![1.5, 2.5]),
};
assert_eq!(col.get_value(0), Some(Value::Float(1.5)));
assert_eq!(col.get_value(1), Some(Value::Float(2.5)));
}
#[test]
fn column_data_get_value_string() {
let col = ColumnData {
name: "name".into(),
row_ids: vec![1, 2],
nulls: NullBitmap::None,
values: ColumnValues::String {
dict: vec!["Alice".into(), "Bob".into()],
indices: vec![0, 1],
},
};
assert_eq!(col.get_value(0), Some(Value::String("Alice".into())));
assert_eq!(col.get_value(1), Some(Value::String("Bob".into())));
}
#[test]
fn column_data_get_value_bool() {
let mut values = vec![0u64];
values[0] |= 1 << 0; values[0] |= 1 << 2; let col = ColumnData {
name: "active".into(),
row_ids: vec![1, 2, 3],
nulls: NullBitmap::None,
values: ColumnValues::Bool {
bitmap: values,
count: 3,
},
};
assert_eq!(col.get_value(0), Some(Value::Bool(true)));
assert_eq!(col.get_value(1), Some(Value::Bool(false)));
assert_eq!(col.get_value(2), Some(Value::Bool(true)));
}
#[test]
fn column_data_get_value_with_nulls() {
let col = ColumnData {
name: "age".into(),
row_ids: vec![1, 2, 3],
nulls: NullBitmap::Sparse(vec![1]),
values: ColumnValues::Int(vec![25, 0, 35]),
};
assert_eq!(col.get_value(0), Some(Value::Int(25)));
assert_eq!(col.get_value(1), Some(Value::Null));
assert_eq!(col.get_value(2), Some(Value::Int(35)));
}
#[test]
fn null_bitmap_dense() {
let mut bitmap = vec![0u64; 1];
bitmap[0] |= 1 << 5; let nulls = NullBitmap::Dense(bitmap);
assert!(!nulls.is_null(0));
assert!(nulls.is_null(5));
assert_eq!(nulls.null_count(), 1);
}
#[test]
fn null_bitmap_sparse() {
let nulls = NullBitmap::Sparse(vec![3, 7, 15]);
assert!(!nulls.is_null(0));
assert!(nulls.is_null(3));
assert!(nulls.is_null(7));
assert!(nulls.is_null(15));
assert!(!nulls.is_null(10));
assert_eq!(nulls.null_count(), 3);
}
#[test]
fn null_bitmap_none() {
let nulls = NullBitmap::None;
assert!(!nulls.is_null(0));
assert!(!nulls.is_null(100));
assert_eq!(nulls.null_count(), 0);
}
#[test]
fn column_values_len() {
let int_vals = ColumnValues::Int(vec![1, 2, 3]);
assert_eq!(int_vals.len(), 3);
assert!(!int_vals.is_empty());
let float_vals = ColumnValues::Float(vec![1.0, 2.0]);
assert_eq!(float_vals.len(), 2);
let str_vals = ColumnValues::String {
dict: vec!["a".into()],
indices: vec![0, 0, 0],
};
assert_eq!(str_vals.len(), 3);
let bool_vals = ColumnValues::Bool {
bitmap: vec![0u64; 2],
count: 100,
};
assert_eq!(bool_vals.len(), 100);
}
#[test]
fn pure_columnar_select_all_columns_materialized() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..20 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
engine
.materialize_columns("users", &["name", "age"])
.unwrap();
let options = ColumnarScanOptions {
projection: Some(vec!["name".into(), "age".into()]),
prefer_columnar: true,
};
let rows = engine
.select_columnar(
"users",
Condition::Ge("age".into(), Value::Int(35)),
options,
)
.unwrap();
assert_eq!(rows.len(), 5); for row in &rows {
assert!(row.contains("name"));
assert!(row.contains("age"));
}
}
#[test]
fn select_columnar_with_true_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions {
projection: Some(vec!["age".into()]),
prefer_columnar: true,
};
let rows = engine
.select_columnar("users", Condition::True, options)
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn select_columnar_with_and_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..50 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions {
projection: Some(vec!["age".into()]),
prefer_columnar: true,
};
let condition = Condition::Ge("age".into(), Value::Int(20))
.and(Condition::Lt("age".into(), Value::Int(30)));
let rows = engine.select_columnar("users", condition, options).unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn select_columnar_with_or_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..50 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions {
projection: Some(vec!["age".into()]),
prefer_columnar: true,
};
let condition =
Condition::Lt("age".into(), Value::Int(10)).or(Condition::Ge("age".into(), Value::Int(40)));
let rows = engine.select_columnar("users", condition, options).unwrap();
assert_eq!(rows.len(), 20); }
#[test]
fn select_columnar_ne_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
}
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Special".into()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions {
projection: Some(vec!["age".into()]),
prefer_columnar: true,
};
let rows = engine
.select_columnar(
"users",
Condition::Ne("age".into(), Value::Int(25)),
options,
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn select_columnar_le_condition() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions {
projection: Some(vec!["age".into()]),
prefer_columnar: true,
};
let rows = engine
.select_columnar(
"users",
Condition::Le("age".into(), Value::Int(24)),
options,
)
.unwrap();
assert_eq!(rows.len(), 5); }
#[test]
fn select_with_projection_filters() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
let rows = engine
.select_with_projection(
"users",
Condition::Gt("age".into(), Value::Int(25)),
Some(vec!["name".into()]),
)
.unwrap();
assert_eq!(rows.len(), 4);
for row in &rows {
assert!(row.contains("name"));
assert!(!row.contains("age"));
}
}
#[test]
fn select_with_projection_includes_id() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".into()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let rows = engine
.select_with_projection(
"users",
Condition::True,
Some(vec!["_id".into(), "name".into()]),
)
.unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].contains("name"));
}
#[test]
fn columnar_scan_options_debug_clone() {
let options = ColumnarScanOptions {
projection: Some(vec!["col".into()]),
prefer_columnar: true,
};
let cloned = options.clone();
assert_eq!(cloned.projection, options.projection);
assert_eq!(cloned.prefer_columnar, options.prefer_columnar);
let debug = format!("{options:?}");
assert!(debug.contains("ColumnarScanOptions"));
}
#[test]
fn column_data_debug_clone() {
let col = ColumnData {
name: "test".into(),
row_ids: vec![1],
nulls: NullBitmap::None,
values: ColumnValues::Int(vec![42]),
};
let cloned = col.clone();
assert_eq!(cloned.name, col.name);
let debug = format!("{col:?}");
assert!(debug.contains("ColumnData"));
}
#[test]
fn selection_vector_from_bitmap() {
let bitmap = vec![0b0000_0101u64]; let sel = SelectionVector::from_bitmap(bitmap, 8);
assert!(sel.is_selected(0));
assert!(!sel.is_selected(1));
assert!(sel.is_selected(2));
assert_eq!(sel.count(), 2);
}
#[test]
fn selection_vector_selected_indices() {
let bitmap = vec![0b0000_1010u64]; let sel = SelectionVector::from_bitmap(bitmap, 8);
let indices = sel.selected_indices();
assert_eq!(indices, vec![1, 3]);
}
#[test]
fn selection_vector_bitmap_accessor() {
let mut sel = SelectionVector::all(10);
let bitmap = sel.bitmap_mut();
bitmap[0] = 0; assert_eq!(sel.count(), 0);
let bitmap_ref = sel.bitmap();
assert_eq!(bitmap_ref[0], 0);
}
#[test]
fn materialize_columns_with_bool_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("active", ColumnType::Bool),
]);
engine.create_table("flags", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("Item{i}")));
values.insert("active".to_string(), Value::Bool(i % 2 == 0));
engine.insert("flags", values).unwrap();
}
engine.materialize_columns("flags", &["active"]).unwrap();
assert!(engine.has_columnar_data("flags", "active"));
let col_data = engine.load_column_data("flags", "active").unwrap();
assert_eq!(col_data.row_ids.len(), 10);
}
#[test]
fn materialize_columns_with_null_values() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
if i % 2 == 0 {
values.insert(
"email".to_string(),
Value::String(format!("user{i}@test.com")),
);
} else {
values.insert("email".to_string(), Value::Null);
}
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["email"]).unwrap();
let col_data = engine.load_column_data("users", "email").unwrap();
assert!(col_data.nulls.null_count() > 0);
}
#[test]
fn load_column_data_empty_table() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let result = engine.load_column_data("users", "age");
assert!(result.is_ok());
let col_data = result.unwrap();
assert_eq!(col_data.row_ids.len(), 0);
}
#[test]
fn select_columnar_type_mismatch() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["name"]).unwrap();
let options = ColumnarScanOptions {
projection: Some(vec!["name".into()]),
prefer_columnar: true,
};
let result = engine.select_columnar(
"users",
Condition::Gt("name".into(), Value::Int(25)),
options,
);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn simd_bitmap_words() {
assert_eq!(simd::bitmap_words(0), 0);
assert_eq!(simd::bitmap_words(1), 1);
assert_eq!(simd::bitmap_words(64), 1);
assert_eq!(simd::bitmap_words(65), 2);
assert_eq!(simd::bitmap_words(128), 2);
assert_eq!(simd::bitmap_words(129), 3);
}
#[test]
fn selection_vector_all_edge_cases() {
let sel64 = SelectionVector::all(64);
assert_eq!(sel64.count(), 64);
assert!(sel64.is_selected(63));
assert!(!sel64.is_selected(64));
let sel70 = SelectionVector::all(70);
assert_eq!(sel70.count(), 70);
assert!(sel70.is_selected(69));
assert!(!sel70.is_selected(70));
}
#[test]
fn simd_filter_eq_with_remainder() {
let values = vec![1, 2, 3, 2, 2];
let mut bitmap = vec![0u64; 1];
simd::filter_eq_i64(&values, 2, &mut bitmap);
assert_eq!(bitmap[0] & 0x1f, 0b11010);
}
#[test]
fn simd_filter_ne_with_remainder() {
let values = vec![1, 2, 2, 2, 1, 3];
let mut bitmap = vec![0u64; 1];
simd::filter_ne_i64(&values, 2, &mut bitmap);
assert_eq!(bitmap[0] & 0x3f, 0b11_0001);
}
#[test]
fn simd_filter_with_single_element() {
let values = vec![42];
let mut bitmap = vec![0u64; 1];
simd::filter_eq_i64(&values, 42, &mut bitmap);
assert_eq!(bitmap[0] & 1, 1);
let mut bitmap2 = vec![0u64; 1];
simd::filter_ne_i64(&values, 42, &mut bitmap2);
assert_eq!(bitmap2[0] & 1, 0);
}
#[test]
fn parallel_select_large_dataset() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..2000 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + (i % 50)));
engine.insert("users", values).unwrap();
}
let rows = engine
.select("users", Condition::Gt("age".into(), Value::Int(60)))
.unwrap();
assert_eq!(rows.len(), 360); }
#[test]
fn parallel_update_large_dataset() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..2000 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
}
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(30));
let count = engine
.update(
"users",
Condition::Eq("age".into(), Value::Int(25)),
updates,
)
.unwrap();
assert_eq!(count, 2000);
}
#[test]
fn parallel_delete_large_dataset() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..2000 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert(
"age".to_string(),
Value::Int(if i % 2 == 0 { 25 } else { 30 }),
);
engine.insert("users", values).unwrap();
}
let count = engine
.delete_rows("users", Condition::Eq("age".into(), Value::Int(25)))
.unwrap();
assert_eq!(count, 1000);
}
#[test]
fn parallel_join_large_dataset() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_posts_table(&engine);
for i in 0..500 {
let mut user_values = HashMap::new();
user_values.insert("name".to_string(), Value::String(format!("User{i}")));
user_values.insert("age".to_string(), Value::Int(20 + (i % 50)));
engine.insert("users", user_values).unwrap();
}
for i in 0..1000 {
let mut post_values = HashMap::new();
post_values.insert("user_id".to_string(), Value::Int((i % 500) + 1));
post_values.insert("title".to_string(), Value::String(format!("Post{i}")));
post_values.insert("views".to_string(), Value::Int(i * 10));
engine.insert("posts", post_values).unwrap();
}
let joined = engine.join("users", "posts", "_id", "user_id").unwrap();
assert_eq!(joined.len(), 1000);
}
#[test]
fn btree_index_on_id_column() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
engine.create_btree_index("users", "_id").unwrap();
let rows = engine
.select("users", Condition::Eq("_id".into(), Value::Int(5)))
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].id, 5);
}
#[test]
fn hash_index_on_id_column() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
engine.create_index("users", "_id").unwrap();
let rows = engine
.select("users", Condition::Eq("_id".into(), Value::Int(3)))
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn btree_range_query_no_matches() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
engine.create_btree_index("users", "age").unwrap();
let rows = engine
.select("users", Condition::Gt("age".into(), Value::Int(100)))
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn update_with_btree_index() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..20 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
}
engine.create_btree_index("users", "age").unwrap();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(30));
let count = engine
.update(
"users",
Condition::Eq("age".into(), Value::Int(25)),
updates,
)
.unwrap();
assert_eq!(count, 20);
let rows = engine
.select("users", Condition::Eq("age".into(), Value::Int(30)))
.unwrap();
assert_eq!(rows.len(), 20);
}
#[test]
fn select_columnar_empty_result() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions {
projection: Some(vec!["age".into()]),
prefer_columnar: true,
};
let rows = engine
.select_columnar(
"users",
Condition::Gt("age".into(), Value::Int(100)),
options,
)
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn select_columnar_no_projection_with_filter() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
engine
.materialize_columns("users", &["name", "age"])
.unwrap();
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let rows = engine
.select_columnar(
"users",
Condition::Lt("age".into(), Value::Int(25)),
options,
)
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn materialize_columns_float_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("score", ColumnType::Float),
]);
engine.create_table("scores", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("score".to_string(), Value::Float(f64::from(i) * 1.5));
engine.insert("scores", values).unwrap();
}
engine.materialize_columns("scores", &["score"]).unwrap();
let col_data = engine.load_column_data("scores", "score").unwrap();
assert_eq!(col_data.row_ids.len(), 10);
match col_data.values {
ColumnValues::Float(vals) => assert_eq!(vals.len(), 10),
_ => panic!("Expected Float column"),
}
}
#[test]
fn column_values_empty() {
let empty_int = ColumnValues::Int(vec![]);
assert!(empty_int.is_empty());
assert_eq!(empty_int.len(), 0);
}
#[test]
fn insert_and_update_with_id_index() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_index("users", "_id").unwrap();
engine.create_btree_index("users", "_id").unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(99));
let count = engine
.update(
"users",
Condition::Lt("age".into(), Value::Int(25)),
updates,
)
.unwrap();
assert_eq!(count, 5);
}
#[test]
fn large_parallel_join() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_posts_table(&engine);
for i in 0..1000 {
let mut user_values = HashMap::new();
user_values.insert("name".to_string(), Value::String(format!("User{i}")));
user_values.insert("age".to_string(), Value::Int(20 + (i % 50)));
engine.insert("users", user_values).unwrap();
}
for i in 0..2000 {
let mut post_values = HashMap::new();
post_values.insert("user_id".to_string(), Value::Int((i % 1000) + 1));
post_values.insert("title".to_string(), Value::String(format!("Post{i}")));
post_values.insert("views".to_string(), Value::Int(i * 10));
engine.insert("posts", post_values).unwrap();
}
let joined = engine.join("users", "posts", "_id", "user_id").unwrap();
assert_eq!(joined.len(), 2000);
}
#[test]
fn select_with_condition_returning_none() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..100 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + (i % 10)));
engine.insert("users", values).unwrap();
}
let rows = engine
.select(
"users",
Condition::Eq("name".into(), Value::String("NonExistent".into())),
)
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn btree_index_range_le() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..50 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
engine.create_btree_index("users", "age").unwrap();
let rows = engine
.select("users", Condition::Le("age".into(), Value::Int(10)))
.unwrap();
assert_eq!(rows.len(), 11); }
#[test]
fn btree_index_range_ge() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..50 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
engine.create_btree_index("users", "age").unwrap();
let rows = engine
.select("users", Condition::Ge("age".into(), Value::Int(40)))
.unwrap();
assert_eq!(rows.len(), 10); }
#[test]
fn update_large_dataset_with_index() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..1000 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
}
engine.create_index("users", "age").unwrap();
engine.create_btree_index("users", "age").unwrap();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(30));
let count = engine
.update(
"users",
Condition::Eq("age".into(), Value::Int(25)),
updates,
)
.unwrap();
assert_eq!(count, 1000);
}
#[test]
fn delete_large_dataset_with_index() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..1000 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert(
"age".to_string(),
Value::Int(if i % 2 == 0 { 25 } else { 30 }),
);
engine.insert("users", values).unwrap();
}
engine.create_index("users", "age").unwrap();
engine.create_btree_index("users", "age").unwrap();
let count = engine
.delete_rows("users", Condition::Eq("age".into(), Value::Int(25)))
.unwrap();
assert_eq!(count, 500);
}
fn create_orders_table(engine: &RelationalEngine) {
let schema = Schema::new(vec![
Column::new("order_id", ColumnType::Int),
Column::new("user_id", ColumnType::Int),
Column::new("amount", ColumnType::Float),
]);
engine.create_table("orders", schema).unwrap();
}
#[test]
fn test_inner_join() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_orders_table(&engine);
for (name, age) in [("Alice", 25), ("Bob", 30), ("Carol", 35)] {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(name.to_string()));
values.insert("age".to_string(), Value::Int(age));
engine.insert("users", values).unwrap();
}
let order_data = [(1i64, 100.0), (1i64, 200.0), (2i64, 150.0)];
for (idx, (user_id, amount)) in order_data.iter().enumerate() {
let mut values = HashMap::new();
values.insert("order_id".to_string(), Value::Int(idx as i64));
values.insert("user_id".to_string(), Value::Int(*user_id));
values.insert("amount".to_string(), Value::Float(*amount));
engine.insert("orders", values).unwrap();
}
let results = engine.join("users", "orders", "_id", "user_id").unwrap();
assert_eq!(results.len(), 3);
let alice_joins: Vec<_> = results.iter().filter(|(u, _)| u.id == 1).collect();
assert_eq!(alice_joins.len(), 2);
}
#[test]
fn test_left_join() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_orders_table(&engine);
for (name, age) in [("Alice", 25), ("Bob", 30), ("Carol", 35)] {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(name.to_string()));
values.insert("age".to_string(), Value::Int(age));
engine.insert("users", values).unwrap();
}
let mut values = HashMap::new();
values.insert("order_id".to_string(), Value::Int(1));
values.insert("user_id".to_string(), Value::Int(1)); values.insert("amount".to_string(), Value::Float(100.0));
engine.insert("orders", values).unwrap();
let results = engine
.left_join("users", "orders", "_id", "user_id")
.unwrap();
assert_eq!(results.len(), 3);
let alice = results.iter().find(|(u, _)| u.id == 1).unwrap();
assert!(alice.1.is_some());
let bob = results.iter().find(|(u, _)| u.id == 2).unwrap();
assert!(bob.1.is_none());
}
#[test]
fn test_right_join() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_orders_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".into()));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
for (user_id, amount) in [(1i64, 100.0), (99i64, 200.0)] {
let mut values = HashMap::new();
values.insert("order_id".to_string(), Value::Int(user_id));
values.insert("user_id".to_string(), Value::Int(user_id));
values.insert("amount".to_string(), Value::Float(amount));
engine.insert("orders", values).unwrap();
}
let results = engine
.right_join("users", "orders", "_id", "user_id")
.unwrap();
assert_eq!(results.len(), 2);
let with_user: Vec<_> = results.iter().filter(|(u, _)| u.is_some()).collect();
assert_eq!(with_user.len(), 1);
let without_user: Vec<_> = results.iter().filter(|(u, _)| u.is_none()).collect();
assert_eq!(without_user.len(), 1);
}
#[test]
fn test_full_join() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_orders_table(&engine);
for (name, age) in [("Alice", 25), ("Bob", 30)] {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(name.to_string()));
values.insert("age".to_string(), Value::Int(age));
engine.insert("users", values).unwrap();
}
for (user_id, amount) in [(1i64, 100.0), (99i64, 200.0)] {
let mut values = HashMap::new();
values.insert("order_id".to_string(), Value::Int(user_id));
values.insert("user_id".to_string(), Value::Int(user_id));
values.insert("amount".to_string(), Value::Float(amount));
engine.insert("orders", values).unwrap();
}
let results = engine
.full_join("users", "orders", "_id", "user_id")
.unwrap();
assert_eq!(results.len(), 3);
let matched: Vec<_> = results
.iter()
.filter(|(a, b)| a.is_some() && b.is_some())
.collect();
assert_eq!(matched.len(), 1);
let left_only: Vec<_> = results
.iter()
.filter(|(a, b)| a.is_some() && b.is_none())
.collect();
assert_eq!(left_only.len(), 1);
let right_only: Vec<_> = results
.iter()
.filter(|(a, b)| a.is_none() && b.is_some())
.collect();
assert_eq!(right_only.len(), 1);
}
#[test]
fn test_cross_join() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_orders_table(&engine);
for (name, age) in [("Alice", 25), ("Bob", 30)] {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(name.to_string()));
values.insert("age".to_string(), Value::Int(age));
engine.insert("users", values).unwrap();
}
for i in 0..3 {
let mut values = HashMap::new();
values.insert("order_id".to_string(), Value::Int(i));
values.insert("user_id".to_string(), Value::Int(i));
values.insert("amount".to_string(), Value::Float(100.0));
engine.insert("orders", values).unwrap();
}
let results = engine.cross_join("users", "orders").unwrap();
assert_eq!(results.len(), 6);
}
#[test]
fn test_natural_join() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("dept_id", ColumnType::Int),
]);
engine.create_table("employees", schema_a).unwrap();
let schema_b = Schema::new(vec![
Column::new("dept_id", ColumnType::Int),
Column::new("dept_name", ColumnType::String),
]);
engine.create_table("departments", schema_b).unwrap();
for (name, dept) in [("Alice", 1), ("Bob", 1), ("Carol", 2)] {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(name.to_string()));
values.insert("dept_id".to_string(), Value::Int(dept));
engine.insert("employees", values).unwrap();
}
for (id, name) in [(1, "Engineering"), (2, "Sales")] {
let mut values = HashMap::new();
values.insert("dept_id".to_string(), Value::Int(id));
values.insert("dept_name".to_string(), Value::String(name.to_string()));
engine.insert("departments", values).unwrap();
}
let results = engine.natural_join("employees", "departments").unwrap();
assert_eq!(results.len(), 3);
let eng_count = results
.iter()
.filter(|(_, d)| d.get_with_id("dept_name") == Some(Value::String("Engineering".into())))
.count();
assert_eq!(eng_count, 2);
}
#[test]
fn test_natural_join_no_common_columns() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![Column::new("a_col", ColumnType::Int)]);
engine.create_table("table_a", schema_a).unwrap();
let schema_b = Schema::new(vec![Column::new("b_col", ColumnType::Int)]);
engine.create_table("table_b", schema_b).unwrap();
let mut values = HashMap::new();
values.insert("a_col".to_string(), Value::Int(1));
engine.insert("table_a", values).unwrap();
let mut values = HashMap::new();
values.insert("b_col".to_string(), Value::Int(2));
engine.insert("table_b", values).unwrap();
let results = engine.natural_join("table_a", "table_b").unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_left_join_empty_right() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_orders_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".into()));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
let results = engine
.left_join("users", "orders", "_id", "user_id")
.unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].1.is_none());
}
#[test]
fn test_join_with_multiple_matches() {
let engine = RelationalEngine::new();
create_users_table(&engine);
create_orders_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".into()));
values.insert("age".to_string(), Value::Int(25));
engine.insert("users", values).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("order_id".to_string(), Value::Int(i));
values.insert("user_id".to_string(), Value::Int(1)); values.insert("amount".to_string(), Value::Float(100.0 * (i + 1) as f64));
engine.insert("orders", values).unwrap();
}
let results = engine.join("users", "orders", "_id", "user_id").unwrap();
assert_eq!(results.len(), 5);
}
#[test]
fn test_join_with_null_keys() {
let engine = RelationalEngine::new();
let users_schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("ref_id", ColumnType::Int).nullable(),
]);
engine.create_table("users_null", users_schema).unwrap();
let orders_schema = Schema::new(vec![
Column::new("order_id", ColumnType::Int),
Column::new("user_ref", ColumnType::Int).nullable(),
]);
engine.create_table("orders_null", orders_schema).unwrap();
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Bob".into()));
values.insert("ref_id".to_string(), Value::Null);
engine.insert("users_null", values).unwrap();
let mut order = HashMap::new();
order.insert("order_id".to_string(), Value::Int(1));
order.insert("user_ref".to_string(), Value::Null);
engine.insert("orders_null", order).unwrap();
let results = engine
.join("users_null", "orders_null", "ref_id", "user_ref")
.unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_aggregate_overflow_i64() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("big_nums", schema).unwrap();
let near_max = i64::MAX / 2;
for _ in 0..3 {
let mut values = HashMap::new();
values.insert("value".to_string(), Value::Int(near_max));
engine.insert("big_nums", values).unwrap();
}
let result = engine.sum("big_nums", "value", Condition::True);
assert!(result.is_ok());
}
#[test]
fn test_index_create_duplicate_name() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_index("users", "age").unwrap();
let result = engine.create_index("users", "age");
assert!(result.is_err());
match result {
Err(RelationalError::IndexAlreadyExists { table, column }) => {
assert_eq!(table, "users");
assert_eq!(column, "age");
},
_ => panic!("Expected IndexAlreadyExists error"),
}
}
#[test]
fn test_btree_range_empty_result() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_btree_index("users", "age").unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
let condition = Condition::Gt("age".to_string(), Value::Int(100));
let rows = engine.select("users", condition).unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_condition_evaluate_type_mismatch() {
let row = Row {
id: 1,
values: vec![
("name".to_string(), Value::String("Alice".into())),
("age".to_string(), Value::Int(25)),
],
};
let condition = Condition::Eq("name".to_string(), Value::Int(42));
assert!(!condition.evaluate(&row));
let condition2 = Condition::Eq("age".to_string(), Value::String("twenty".into()));
assert!(!condition2.evaluate(&row));
}
#[test]
fn test_invalid_table_name_empty() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let result = engine.create_table("", schema);
assert!(matches!(result, Err(RelationalError::InvalidName(_))));
}
#[test]
fn test_invalid_table_name_colon() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let result = engine.create_table("foo:bar", schema);
assert!(matches!(result, Err(RelationalError::InvalidName(_))));
}
#[test]
fn test_invalid_table_name_comma() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let result = engine.create_table("foo,bar", schema);
assert!(matches!(result, Err(RelationalError::InvalidName(_))));
}
#[test]
fn test_invalid_table_name_underscore_prefix() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let result = engine.create_table("_reserved", schema);
assert!(matches!(result, Err(RelationalError::InvalidName(_))));
}
#[test]
fn test_invalid_column_name_colon() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("col:umn", ColumnType::Int)]);
let result = engine.create_table("test", schema);
assert!(matches!(result, Err(RelationalError::InvalidName(_))));
}
#[test]
fn test_invalid_column_name_underscore_prefix() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("_reserved", ColumnType::Int)]);
let result = engine.create_table("test", schema);
assert!(matches!(result, Err(RelationalError::InvalidName(_))));
}
#[test]
fn test_valid_names_accepted() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("user_name", ColumnType::String),
Column::new("Age123", ColumnType::Int),
]);
let result = engine.create_table("my_table", schema);
assert!(result.is_ok());
}
#[test]
fn test_index_column_validation() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let result = engine.create_index("test", "_id");
assert!(result.is_ok());
}
#[test]
fn test_transaction_begin_commit() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let tx = engine.begin_transaction();
assert!(engine.is_transaction_active(tx));
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
let row_id = engine.tx_insert(tx, "users", values).unwrap();
assert_eq!(row_id, 1);
engine.commit(tx).unwrap();
assert!(!engine.is_transaction_active(tx));
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_transaction_rollback_insert() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let tx = engine.begin_transaction();
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.tx_insert(tx, "users", values).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
engine.rollback(tx).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn test_transaction_rollback_update() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let tx = engine.begin_transaction();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(31));
engine
.tx_update(tx, "users", Condition::True, updates)
.unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows[0].get("age"), Some(&Value::Int(31)));
engine.rollback(tx).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows[0].get("age"), Some(&Value::Int(30)));
}
#[test]
fn test_transaction_rollback_delete() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let tx = engine.begin_transaction();
engine.tx_delete(tx, "users", Condition::True).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 0);
engine.rollback(tx).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(
rows[0].get("name"),
Some(&Value::String("Alice".to_string()))
);
}
#[test]
fn test_transaction_multiple_inserts_rollback() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let tx = engine.begin_transaction();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.tx_insert(tx, "users", values).unwrap();
}
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 5);
engine.rollback(tx).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn test_transaction_not_found() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let result = engine.commit(999);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(999))
));
let result = engine.rollback(999);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(999))
));
}
#[test]
fn test_transaction_inactive_after_commit() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let tx = engine.begin_transaction();
engine.commit(tx).unwrap();
let result = engine.commit(tx);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_transaction_inactive_after_rollback() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let tx = engine.begin_transaction();
engine.rollback(tx).unwrap();
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
let result = engine.tx_insert(tx, "users", values);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_lock_conflict() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let tx1 = engine.begin_transaction();
let tx2 = engine.begin_transaction();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(31));
engine
.tx_update(tx1, "users", Condition::True, updates.clone())
.unwrap();
let result = engine.tx_update(tx2, "users", Condition::True, updates);
assert!(matches!(result, Err(RelationalError::LockConflict { .. })));
engine.rollback(tx1).unwrap();
engine.rollback(tx2).unwrap();
}
#[test]
fn test_lock_released_after_commit() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let tx1 = engine.begin_transaction();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(31));
engine
.tx_update(tx1, "users", Condition::True, updates)
.unwrap();
engine.commit(tx1).unwrap();
let tx2 = engine.begin_transaction();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(32));
let result = engine.tx_update(tx2, "users", Condition::True, updates);
assert!(result.is_ok());
engine.commit(tx2).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows[0].get("age"), Some(&Value::Int(32)));
}
#[test]
fn test_lock_released_after_rollback() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let tx1 = engine.begin_transaction();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(31));
engine
.tx_update(tx1, "users", Condition::True, updates)
.unwrap();
engine.rollback(tx1).unwrap();
let tx2 = engine.begin_transaction();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(32));
let result = engine.tx_update(tx2, "users", Condition::True, updates);
assert!(result.is_ok());
engine.commit(tx2).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows[0].get("age"), Some(&Value::Int(32)));
}
#[test]
fn test_tx_select() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..3 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users", values).unwrap();
}
let tx = engine.begin_transaction();
let rows = engine.tx_select(tx, "users", Condition::True).unwrap();
assert_eq!(rows.len(), 3);
let rows = engine
.tx_select(
tx,
"users",
Condition::Eq("name".to_string(), Value::String("User1".to_string())),
)
.unwrap();
assert_eq!(rows.len(), 1);
engine.commit(tx).unwrap();
}
#[test]
fn test_tx_delete_with_lock() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let tx1 = engine.begin_transaction();
let tx2 = engine.begin_transaction();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(31));
engine
.tx_update(tx1, "users", Condition::True, updates)
.unwrap();
let result = engine.tx_delete(tx2, "users", Condition::True);
assert!(matches!(result, Err(RelationalError::LockConflict { .. })));
engine.commit(tx1).unwrap();
engine.rollback(tx2).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("age"), Some(&Value::Int(31)));
}
#[test]
fn test_active_transaction_count() {
let engine = RelationalEngine::new();
create_users_table(&engine);
assert_eq!(engine.active_transaction_count(), 0);
let tx1 = engine.begin_transaction();
assert_eq!(engine.active_transaction_count(), 1);
let tx2 = engine.begin_transaction();
assert_eq!(engine.active_transaction_count(), 2);
engine.commit(tx1).unwrap();
assert_eq!(engine.active_transaction_count(), 1);
engine.rollback(tx2).unwrap();
assert_eq!(engine.active_transaction_count(), 0);
}
#[test]
fn test_tx_insert_validation() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let tx = engine.begin_transaction();
let mut values = HashMap::new();
values.insert("age".to_string(), Value::Int(30));
let result = engine.tx_insert(tx, "users", values);
assert!(matches!(result, Err(RelationalError::NullNotAllowed(_))));
engine.rollback(tx).unwrap();
}
#[test]
fn test_tx_update_validation() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let tx = engine.begin_transaction();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::String("not a number".to_string()));
let result = engine.tx_update(tx, "users", Condition::True, updates);
assert!(matches!(result, Err(RelationalError::TypeMismatch { .. })));
engine.rollback(tx).unwrap();
}
#[test]
fn test_transaction_with_index() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_index("users", "name").unwrap();
let tx = engine.begin_transaction();
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.tx_insert(tx, "users", values).unwrap();
engine.rollback(tx).unwrap();
let rows = engine
.select(
"users",
Condition::Eq("name".to_string(), Value::String("Alice".to_string())),
)
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn test_cross_join_limit_exceeded() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("large_a", schema.clone()).unwrap();
engine.create_table("large_b", schema).unwrap();
for i in 0..1001 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("large_a", values.clone()).unwrap();
engine.insert("large_b", values).unwrap();
}
let result = engine.cross_join("large_a", "large_b");
assert!(matches!(
result,
Err(RelationalError::ResultTooLarge { .. })
));
}
#[test]
fn test_cross_join_at_limit_succeeds() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("limit_a", schema.clone()).unwrap();
engine.create_table("limit_b", schema).unwrap();
for i in 0..1000 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("limit_a", values.clone()).unwrap();
engine.insert("limit_b", values).unwrap();
}
let result = engine.cross_join("limit_a", "limit_b");
assert!(result.is_ok());
}
#[test]
fn test_natural_join_no_common_columns_respects_limit() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![Column::new("a_col", ColumnType::Int)]);
let schema_b = Schema::new(vec![Column::new("b_col", ColumnType::Int)]);
engine.create_table("no_common_a", schema_a).unwrap();
engine.create_table("no_common_b", schema_b).unwrap();
for i in 0..1001 {
engine
.insert(
"no_common_a",
HashMap::from([("a_col".to_string(), Value::Int(i))]),
)
.unwrap();
engine
.insert(
"no_common_b",
HashMap::from([("b_col".to_string(), Value::Int(i))]),
)
.unwrap();
}
let result = engine.natural_join("no_common_a", "no_common_b");
assert!(matches!(
result,
Err(RelationalError::ResultTooLarge { .. })
));
}
#[test]
fn test_table_name_length_limit() {
let engine = RelationalEngine::new();
let long_name = "a".repeat(256);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let result = engine.create_table(&long_name, schema);
assert!(matches!(
result,
Err(RelationalError::InvalidName(msg)) if msg.contains("255")
));
}
#[test]
fn test_column_name_length_limit() {
let engine = RelationalEngine::new();
let long_col = "c".repeat(256);
let schema = Schema::new(vec![Column::new(&long_col, ColumnType::Int)]);
let result = engine.create_table("test_tbl", schema);
assert!(matches!(result, Err(RelationalError::InvalidName(_))));
}
#[test]
fn test_name_at_max_length_succeeds() {
let engine = RelationalEngine::new();
let max_name = "a".repeat(255);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
assert!(engine.create_table(&max_name, schema).is_ok());
}
#[test]
fn test_simd_filter_lt_f64() {
let values: Vec<f64> = (0..10).map(f64::from).collect();
let mut result = vec![0u64; 1];
simd::filter_lt_f64(&values, 5.0, &mut result);
for i in 0..5 {
assert!((result[0] & (1u64 << i)) != 0, "Expected bit {i} set");
}
for i in 5..10 {
assert!((result[0] & (1u64 << i)) == 0, "Expected bit {i} unset");
}
}
#[test]
fn test_simd_filter_gt_f64() {
let values: Vec<f64> = (0..10).map(f64::from).collect();
let mut result = vec![0u64; 1];
simd::filter_gt_f64(&values, 5.0, &mut result);
for i in 0..6 {
assert!((result[0] & (1u64 << i)) == 0, "Expected bit {i} unset");
}
for i in 6..10 {
assert!((result[0] & (1u64 << i)) != 0, "Expected bit {i} set");
}
}
#[test]
fn test_simd_filter_eq_f64() {
let values = vec![1.0, 2.0, 3.0, 2.0, 5.0, 2.0, 7.0, 8.0];
let mut result = vec![0u64; 1];
simd::filter_eq_f64(&values, 2.0, &mut result);
assert!((result[0] & (1u64 << 1)) != 0);
assert!((result[0] & (1u64 << 3)) != 0);
assert!((result[0] & (1u64 << 5)) != 0);
assert!((result[0] & (1u64 << 0)) == 0);
assert!((result[0] & (1u64 << 2)) == 0);
}
#[test]
fn test_simd_filter_lt_f64_non_aligned() {
let values: Vec<f64> = (0..7).map(f64::from).collect();
let mut result = vec![0u64; 1];
simd::filter_lt_f64(&values, 3.0, &mut result);
assert_eq!(result[0] & 0b111, 0b111);
assert!((result[0] & (1u64 << 3)) == 0);
}
#[test]
fn test_simd_filter_gt_f64_non_aligned() {
let values: Vec<f64> = (0..5).map(f64::from).collect();
let mut result = vec![0u64; 1];
simd::filter_gt_f64(&values, 2.0, &mut result);
assert!((result[0] & (1u64 << 3)) != 0);
assert!((result[0] & (1u64 << 4)) != 0);
}
#[test]
fn test_simd_filter_eq_f64_non_aligned() {
let values = vec![1.0, 2.0, 2.0, 4.0, 5.0];
let mut result = vec![0u64; 1];
simd::filter_eq_f64(&values, 2.0, &mut result);
assert!((result[0] & (1u64 << 1)) != 0);
assert!((result[0] & (1u64 << 2)) != 0);
}
#[test]
fn test_value_is_truthy() {
assert!(!Value::Null.is_truthy());
assert!(!Value::Bool(false).is_truthy());
assert!(Value::Bool(true).is_truthy());
assert!(!Value::Int(0).is_truthy());
assert!(Value::Int(1).is_truthy());
assert!(Value::Int(-1).is_truthy());
assert!(!Value::Float(0.0).is_truthy());
assert!(Value::Float(1.0).is_truthy());
assert!(!Value::String(String::new()).is_truthy());
assert!(Value::String("hello".to_string()).is_truthy());
}
#[test]
fn test_value_hash_key() {
let null_key = Value::Null.hash_key();
assert_eq!(null_key, "null");
let int_key = Value::Int(42).hash_key();
assert!(int_key.starts_with("i:"));
let float_key = Value::Float(3.15).hash_key();
assert!(float_key.starts_with("f:"));
let string_key = Value::String("test".to_string()).hash_key();
assert!(string_key.starts_with("s:"));
let bool_key = Value::Bool(true).hash_key();
assert!(bool_key.starts_with("b:"));
}
#[test]
fn test_value_sortable_key() {
let null_key = Value::Null.sortable_key();
assert_eq!(null_key, "0");
let int_key = Value::Int(100).sortable_key();
assert!(int_key.starts_with('i'));
let float_key = Value::Float(1.5).sortable_key();
assert!(float_key.starts_with('f'));
let string_key = Value::String("abc".to_string()).sortable_key();
assert_eq!(string_key, "sabc");
let true_key = Value::Bool(true).sortable_key();
assert_eq!(true_key, "b1");
let false_key = Value::Bool(false).sortable_key();
assert_eq!(false_key, "b0");
}
#[test]
fn test_value_sortable_key_ordering() {
let neg_key = Value::Int(-100).sortable_key();
let zero_key = Value::Int(0).sortable_key();
let pos_key = Value::Int(100).sortable_key();
assert!(neg_key < zero_key);
assert!(zero_key < pos_key);
let neg_float = Value::Float(-1.0).sortable_key();
let zero_float = Value::Float(0.0).sortable_key();
let pos_float = Value::Float(1.0).sortable_key();
assert!(neg_float < zero_float);
assert!(zero_float < pos_float);
}
#[test]
fn test_value_partial_cmp() {
assert_eq!(
Value::Int(5).partial_cmp_value(&Value::Int(3)),
Some(std::cmp::Ordering::Greater)
);
assert_eq!(
Value::Float(1.0).partial_cmp_value(&Value::Float(2.0)),
Some(std::cmp::Ordering::Less)
);
assert_eq!(
Value::String("a".to_string()).partial_cmp_value(&Value::String("b".to_string())),
Some(std::cmp::Ordering::Less)
);
assert_eq!(
Value::Int(5).partial_cmp_value(&Value::String("5".to_string())),
None
);
}
#[test]
fn test_value_matches_type() {
assert!(Value::Null.matches_type(&ColumnType::Int)); assert!(Value::Int(42).matches_type(&ColumnType::Int));
assert!(!Value::Int(42).matches_type(&ColumnType::String));
assert!(Value::Float(1.0).matches_type(&ColumnType::Float));
assert!(Value::String("x".to_string()).matches_type(&ColumnType::String));
assert!(Value::Bool(true).matches_type(&ColumnType::Bool));
}
#[test]
fn test_error_display_table_not_found() {
let err = RelationalError::TableNotFound("users".to_string());
assert_eq!(format!("{err}"), "Table not found: users");
}
#[test]
fn test_error_display_table_already_exists() {
let err = RelationalError::TableAlreadyExists("users".to_string());
assert_eq!(format!("{err}"), "Table already exists: users");
}
#[test]
fn test_error_display_column_not_found() {
let err = RelationalError::ColumnNotFound("age".to_string());
assert_eq!(format!("{err}"), "Column not found: age");
}
#[test]
fn test_error_display_type_mismatch() {
let err = RelationalError::TypeMismatch {
column: "age".to_string(),
expected: ColumnType::Int,
};
assert!(format!("{err}").contains("Type mismatch"));
}
#[test]
fn test_error_display_null_not_allowed() {
let err = RelationalError::NullNotAllowed("name".to_string());
assert!(format!("{err}").contains("Null not allowed"));
}
#[test]
fn test_error_display_index_already_exists() {
let err = RelationalError::IndexAlreadyExists {
table: "users".to_string(),
column: "email".to_string(),
};
assert!(format!("{err}").contains("Index already exists"));
}
#[test]
fn test_error_display_index_not_found() {
let err = RelationalError::IndexNotFound {
table: "users".to_string(),
column: "email".to_string(),
};
assert!(format!("{err}").contains("Index not found"));
}
#[test]
fn test_error_display_storage_error() {
let err = RelationalError::StorageError("disk full".to_string());
assert!(format!("{err}").contains("Storage error"));
}
#[test]
fn test_error_display_transaction_not_found() {
let err = RelationalError::TransactionNotFound(123);
assert!(format!("{err}").contains("Transaction not found: 123"));
}
#[test]
fn test_error_display_transaction_inactive() {
let err = RelationalError::TransactionInactive(456);
assert!(format!("{err}").contains("Transaction not active: 456"));
}
#[test]
fn test_error_display_lock_conflict() {
let err = RelationalError::LockConflict {
tx_id: 1,
blocking_tx: 2,
table: "users".to_string(),
row_id: 100,
};
assert!(format!("{err}").contains("Lock conflict"));
}
#[test]
fn test_error_display_lock_timeout() {
let err = RelationalError::LockTimeout {
tx_id: 1,
table: "users".to_string(),
row_ids: vec![1, 2, 3],
};
assert!(format!("{err}").contains("Lock timeout"));
}
#[test]
fn test_error_display_rollback_failed() {
let err = RelationalError::RollbackFailed {
tx_id: 1,
reason: "storage failure".to_string(),
};
assert!(format!("{err}").contains("Rollback failed"));
}
#[test]
fn test_error_display_result_too_large() {
let err = RelationalError::ResultTooLarge {
operation: "CROSS JOIN".to_string(),
actual: 2_000_000,
max: 1_000_000,
};
let msg = format!("{err}");
assert!(msg.contains("CROSS JOIN"));
assert!(msg.contains("2000000"));
assert!(msg.contains("1000000"));
}
#[test]
fn test_error_display_index_corrupted() {
let err = RelationalError::IndexCorrupted {
reason: "ID list has 7 bytes, expected multiple of 8".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("Index data corrupted"));
assert!(msg.contains("7 bytes"));
}
#[test]
fn test_tensor_to_id_list_corrupted_bytes() {
let mut tensor = TensorData::new();
tensor.set(
"ids",
TensorValue::Scalar(ScalarValue::Bytes(vec![1, 2, 3, 4, 5, 6, 7])),
);
let result = RelationalEngine::tensor_to_id_list(&tensor);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, RelationalError::IndexCorrupted { .. }));
}
#[test]
fn test_tensor_to_id_list_wrong_type() {
let mut tensor = TensorData::new();
tensor.set("ids", TensorValue::Scalar(ScalarValue::Int(42)));
let result = RelationalEngine::tensor_to_id_list(&tensor);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, RelationalError::IndexCorrupted { .. }));
}
#[test]
fn test_tensor_to_id_list_valid_bytes() {
let mut tensor = TensorData::new();
let ids: Vec<u64> = vec![1, 2, 3];
let bytes: Vec<u8> = ids.iter().flat_map(|id| id.to_le_bytes()).collect();
tensor.set("ids", TensorValue::Scalar(ScalarValue::Bytes(bytes)));
let result = RelationalEngine::tensor_to_id_list(&tensor);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![1, 2, 3]);
}
#[test]
fn test_tensor_to_id_list_empty() {
let tensor = TensorData::new();
let result = RelationalEngine::tensor_to_id_list(&tensor);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_tensor_to_id_list_legacy_vector_format() {
let mut tensor = TensorData::new();
tensor.set("ids", TensorValue::Vector(vec![1.0, 2.0, 3.0]));
let result = RelationalEngine::tensor_to_id_list(&tensor);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![1, 2, 3]);
}
#[test]
fn test_column_type_to_slab_column_type() {
assert_eq!(SlabColumnType::from(&ColumnType::Int), SlabColumnType::Int);
assert_eq!(
SlabColumnType::from(&ColumnType::Float),
SlabColumnType::Float
);
assert_eq!(
SlabColumnType::from(&ColumnType::String),
SlabColumnType::String
);
assert_eq!(
SlabColumnType::from(&ColumnType::Bool),
SlabColumnType::Bool
);
}
#[test]
fn test_slab_column_type_to_column_type() {
assert_eq!(ColumnType::from(&SlabColumnType::Int), ColumnType::Int);
assert_eq!(ColumnType::from(&SlabColumnType::Float), ColumnType::Float);
assert_eq!(
ColumnType::from(&SlabColumnType::String),
ColumnType::String
);
assert_eq!(ColumnType::from(&SlabColumnType::Bool), ColumnType::Bool);
assert_eq!(ColumnType::from(&SlabColumnType::Bytes), ColumnType::Bytes);
assert_eq!(ColumnType::from(&SlabColumnType::Json), ColumnType::Json);
}
#[test]
fn test_schema_to_slab_table_schema() {
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String).nullable(),
]);
let slab_schema: SlabTableSchema = (&schema).into();
assert_eq!(slab_schema.columns.len(), 2);
}
#[test]
fn test_value_to_slab_column_value() {
assert_eq!(SlabColumnValue::from(&Value::Null), SlabColumnValue::Null);
assert_eq!(
SlabColumnValue::from(&Value::Int(42)),
SlabColumnValue::Int(42)
);
assert_eq!(
SlabColumnValue::from(&Value::Float(3.15)),
SlabColumnValue::Float(3.15)
);
assert_eq!(
SlabColumnValue::from(&Value::String("test".to_string())),
SlabColumnValue::String("test".to_string())
);
assert_eq!(
SlabColumnValue::from(&Value::Bool(true)),
SlabColumnValue::Bool(true)
);
}
#[test]
fn test_slab_column_value_to_value() {
assert_eq!(Value::from(SlabColumnValue::Null), Value::Null);
assert_eq!(Value::from(SlabColumnValue::Int(42)), Value::Int(42));
assert_eq!(
Value::from(SlabColumnValue::Float(3.15)),
Value::Float(3.15)
);
assert_eq!(
Value::from(SlabColumnValue::String("test".to_string())),
Value::String("test".to_string())
);
assert_eq!(Value::from(SlabColumnValue::Bool(true)), Value::Bool(true));
let bytes_val = Value::from(SlabColumnValue::Bytes(vec![1, 2, 3]));
assert_eq!(bytes_val, Value::Bytes(vec![1, 2, 3]));
let json_val = Value::from(SlabColumnValue::Json(r#"{"key": "value"}"#.to_string()));
assert!(matches!(json_val, Value::Json(_)));
}
#[test]
fn test_ordered_float_eq() {
let a = OrderedFloat(1.0);
let b = OrderedFloat(1.0);
let c = OrderedFloat(2.0);
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn test_ordered_float_ord() {
let a = OrderedFloat(1.0);
let b = OrderedFloat(2.0);
let nan = OrderedFloat(f64::NAN);
assert!(a < b);
assert!(nan < a); }
#[test]
fn test_ordered_key_ordering() {
let null = OrderedKey::Null;
let bool_val = OrderedKey::Bool(false);
let int_val = OrderedKey::Int(0);
let float_val = OrderedKey::Float(OrderedFloat(0.0));
let str_val = OrderedKey::String(String::new());
assert!(null < bool_val);
assert!(bool_val < int_val);
assert!(int_val < float_val);
assert!(float_val < str_val);
}
#[test]
fn test_row_get() {
let row = Row {
id: 1,
values: vec![
("name".to_string(), Value::String("Alice".to_string())),
("age".to_string(), Value::Int(30)),
],
};
assert_eq!(row.get("name"), Some(&Value::String("Alice".to_string())));
assert_eq!(row.get("age"), Some(&Value::Int(30)));
assert_eq!(row.get("missing"), None);
assert_eq!(row.get("_id"), None); }
#[test]
fn test_row_get_with_id() {
let row = Row {
id: 42,
values: vec![("name".to_string(), Value::String("Bob".to_string()))],
};
assert_eq!(row.get_with_id("_id"), Some(Value::Int(42)));
assert_eq!(
row.get_with_id("name"),
Some(Value::String("Bob".to_string()))
);
assert_eq!(row.get_with_id("missing"), None);
}
#[test]
fn test_null_bitmap_none() {
let bitmap = NullBitmap::None;
assert_eq!(bitmap.null_count(), 0);
assert!(!bitmap.is_null(0));
assert!(!bitmap.is_null(99));
}
#[test]
fn test_null_bitmap_dense() {
let bitmap = NullBitmap::Dense(vec![0b100101]);
assert_eq!(bitmap.null_count(), 3);
assert!(bitmap.is_null(0));
assert!(!bitmap.is_null(1));
assert!(bitmap.is_null(2));
assert!(bitmap.is_null(5));
}
#[test]
fn test_null_bitmap_sparse() {
let bitmap = NullBitmap::Sparse(vec![1, 5, 10]);
assert_eq!(bitmap.null_count(), 3);
assert!(bitmap.is_null(1));
assert!(bitmap.is_null(5));
assert!(bitmap.is_null(10));
assert!(!bitmap.is_null(0));
assert!(!bitmap.is_null(3));
}
#[test]
fn test_selection_vector_all() {
let sv = SelectionVector::all(10);
assert_eq!(sv.count(), 10);
for i in 0..10 {
assert!(sv.is_selected(i));
}
assert!(!sv.is_selected(10)); }
#[test]
fn test_selection_vector_none() {
let sv = SelectionVector::none(10);
assert_eq!(sv.count(), 0);
for i in 0..10 {
assert!(!sv.is_selected(i));
}
}
#[test]
fn test_selection_vector_from_bitmap() {
let bitmap = vec![0b1010u64]; let sv = SelectionVector::from_bitmap(bitmap, 8);
assert!(sv.is_selected(1));
assert!(sv.is_selected(3));
assert!(!sv.is_selected(0));
assert!(!sv.is_selected(2));
}
#[test]
fn test_selection_vector_intersect() {
let sv1 = SelectionVector::from_bitmap(vec![0b1111], 8);
let sv2 = SelectionVector::from_bitmap(vec![0b1010], 8);
let result = sv1.intersect(&sv2);
assert!(result.is_selected(1));
assert!(result.is_selected(3));
assert!(!result.is_selected(0));
assert!(!result.is_selected(2));
}
#[test]
fn test_selection_vector_union() {
let sv1 = SelectionVector::from_bitmap(vec![0b0101], 8);
let sv2 = SelectionVector::from_bitmap(vec![0b1010], 8);
let result = sv1.union(&sv2);
for i in 0..4 {
assert!(result.is_selected(i));
}
}
#[test]
fn test_selection_vector_selected_indices() {
let sv = SelectionVector::from_bitmap(vec![0b10100101], 8);
let indices = sv.selected_indices();
assert_eq!(indices, vec![0, 2, 5, 7]);
}
#[test]
fn test_selection_vector_bitmap_mut() {
let mut sv = SelectionVector::none(64);
sv.bitmap_mut()[0] = 0b11110000;
assert!(!sv.is_selected(0));
assert!(sv.is_selected(4));
}
#[test]
fn test_column_data_get_value_int() {
let col = ColumnData {
name: "age".to_string(),
row_ids: vec![1, 2, 3],
nulls: NullBitmap::None,
values: ColumnValues::Int(vec![25, 30, 35]),
};
assert_eq!(col.get_value(0), Some(Value::Int(25)));
assert_eq!(col.get_value(1), Some(Value::Int(30)));
assert_eq!(col.get_value(2), Some(Value::Int(35)));
assert_eq!(col.null_count(), 0);
}
#[test]
fn test_column_data_get_value_float() {
let col = ColumnData {
name: "score".to_string(),
row_ids: vec![1, 2],
nulls: NullBitmap::None,
values: ColumnValues::Float(vec![1.5, 2.5]),
};
assert_eq!(col.get_value(0), Some(Value::Float(1.5)));
assert_eq!(col.get_value(1), Some(Value::Float(2.5)));
}
#[test]
fn test_column_data_get_value_string() {
let col = ColumnData {
name: "name".to_string(),
row_ids: vec![1, 2],
nulls: NullBitmap::None,
values: ColumnValues::String {
dict: vec!["Alice".to_string(), "Bob".to_string()],
indices: vec![0, 1],
},
};
assert_eq!(col.get_value(0), Some(Value::String("Alice".to_string())));
assert_eq!(col.get_value(1), Some(Value::String("Bob".to_string())));
}
#[test]
fn test_column_data_get_value_bool() {
let col = ColumnData {
name: "active".to_string(),
row_ids: vec![1, 2, 3],
nulls: NullBitmap::None,
values: ColumnValues::Bool {
bitmap: vec![0b101],
count: 3,
}, };
assert_eq!(col.get_value(0), Some(Value::Bool(true)));
assert_eq!(col.get_value(1), Some(Value::Bool(false)));
assert_eq!(col.get_value(2), Some(Value::Bool(true)));
}
#[test]
fn test_column_data_with_nulls() {
let col = ColumnData {
name: "age".to_string(),
row_ids: vec![1, 2, 3],
nulls: NullBitmap::Dense(vec![0b010]),
values: ColumnValues::Int(vec![25, 0, 35]),
};
assert_eq!(col.get_value(0), Some(Value::Int(25)));
assert_eq!(col.get_value(1), Some(Value::Null));
assert_eq!(col.get_value(2), Some(Value::Int(35)));
assert_eq!(col.null_count(), 1);
}
#[test]
fn test_condition_range_le() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Float),
]);
engine.create_table("scores", schema).unwrap();
for i in 0..10 {
engine
.insert(
"scores",
HashMap::from([
("id".to_string(), Value::Int(i)),
("score".to_string(), Value::Float(i as f64)),
]),
)
.unwrap();
}
let rows = engine
.select(
"scores",
Condition::Le("score".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 6); }
#[test]
fn test_condition_range_ge() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("vals", schema).unwrap();
for i in 0..10 {
engine
.insert("vals", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine
.select("vals", Condition::Ge("val".to_string(), Value::Int(7)))
.unwrap();
assert_eq!(rows.len(), 3); }
#[test]
fn test_condition_range_ne() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("vals", schema).unwrap();
for i in 0..5 {
engine
.insert("vals", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine
.select("vals", Condition::Ne("val".to_string(), Value::Int(2)))
.unwrap();
assert_eq!(rows.len(), 4); }
#[test]
fn test_columnar_scan_options_default() {
let opts = ColumnarScanOptions::default();
assert!(opts.projection.is_none());
assert!(!opts.prefer_columnar);
}
#[test]
fn test_columnar_scan_options_with_projection() {
let opts = ColumnarScanOptions {
projection: Some(vec!["name".to_string(), "age".to_string()]),
prefer_columnar: true,
};
assert_eq!(opts.projection.as_ref().unwrap().len(), 2);
assert!(opts.prefer_columnar);
}
#[test]
fn test_storage_error_from_tensor_store() {
use tensor_store::TensorStoreError;
let store_err = TensorStoreError::NotFound("test".to_string());
let rel_err: RelationalError = store_err.into();
assert!(matches!(rel_err, RelationalError::StorageError(_)));
}
#[test]
fn test_select_float_conditions() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("price", ColumnType::Float),
]);
engine.create_table("products", schema).unwrap();
let prices = [1.0, 2.5, 3.0, 4.5, 5.0, 6.5, 7.0, 8.5, 9.0, 10.0];
for (i, &price) in prices.iter().enumerate() {
engine
.insert(
"products",
HashMap::from([
("id".to_string(), Value::Int(i as i64)),
("price".to_string(), Value::Float(price)),
]),
)
.unwrap();
}
let lt_rows = engine
.select(
"products",
Condition::Lt("price".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(lt_rows.len(), 4);
let gt_rows = engine
.select(
"products",
Condition::Gt("price".to_string(), Value::Float(7.0)),
)
.unwrap();
assert_eq!(gt_rows.len(), 3);
let eq_rows = engine
.select(
"products",
Condition::Eq("price".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(eq_rows.len(), 1);
}
#[test]
fn test_value_from_scalar() {
use tensor_store::ScalarValue;
let null_val = Value::from_scalar(&ScalarValue::Null);
assert_eq!(null_val, Value::Null);
let int_val = Value::from_scalar(&ScalarValue::Int(42));
assert_eq!(int_val, Value::Int(42));
let float_val = Value::from_scalar(&ScalarValue::Float(3.15));
assert_eq!(float_val, Value::Float(3.15));
let str_val = Value::from_scalar(&ScalarValue::String("test".to_string()));
assert_eq!(str_val, Value::String("test".to_string()));
let bool_val = Value::from_scalar(&ScalarValue::Bool(true));
assert_eq!(bool_val, Value::Bool(true));
let bytes_val = Value::from_scalar(&ScalarValue::Bytes(vec![1, 2, 3]));
assert_eq!(bytes_val, Value::Bytes(vec![1, 2, 3]));
}
#[test]
fn test_selection_vector_all_large() {
let sv = SelectionVector::all(100);
assert_eq!(sv.count(), 100);
assert!(sv.is_selected(0));
assert!(sv.is_selected(63));
assert!(sv.is_selected(64));
assert!(sv.is_selected(99));
assert!(!sv.is_selected(100));
}
#[test]
fn test_selection_vector_non_aligned() {
let sv = SelectionVector::all(70);
assert_eq!(sv.count(), 70);
assert!(sv.is_selected(69));
assert!(!sv.is_selected(70));
}
#[test]
fn test_simd_filter_lt_i64() {
let values: Vec<i64> = (0..10).collect();
let mut result = vec![0u64; 1];
simd::filter_lt_i64(&values, 5, &mut result);
for i in 0..5 {
assert!((result[0] & (1u64 << i)) != 0);
}
for i in 5..10 {
assert!((result[0] & (1u64 << i)) == 0);
}
}
#[test]
fn test_simd_filter_le_i64() {
let values: Vec<i64> = (0..10).collect();
let mut result = vec![0u64; 1];
simd::filter_le_i64(&values, 5, &mut result);
for i in 0..6 {
assert!((result[0] & (1u64 << i)) != 0);
}
for i in 6..10 {
assert!((result[0] & (1u64 << i)) == 0);
}
}
#[test]
fn test_simd_filter_gt_i64() {
let values: Vec<i64> = (0..10).collect();
let mut result = vec![0u64; 1];
simd::filter_gt_i64(&values, 5, &mut result);
for i in 0..6 {
assert!((result[0] & (1u64 << i)) == 0);
}
for i in 6..10 {
assert!((result[0] & (1u64 << i)) != 0);
}
}
#[test]
fn test_simd_filter_ge_i64() {
let values: Vec<i64> = (0..10).collect();
let mut result = vec![0u64; 1];
simd::filter_ge_i64(&values, 5, &mut result);
for i in 0..5 {
assert!((result[0] & (1u64 << i)) == 0);
}
for i in 5..10 {
assert!((result[0] & (1u64 << i)) != 0);
}
}
#[test]
fn test_simd_filter_eq_i64() {
let values = vec![1, 2, 3, 2, 5, 2, 7, 8];
let mut result = vec![0u64; 1];
simd::filter_eq_i64(&values, 2, &mut result);
assert!((result[0] & (1u64 << 1)) != 0);
assert!((result[0] & (1u64 << 3)) != 0);
assert!((result[0] & (1u64 << 5)) != 0);
}
#[test]
fn test_simd_filter_i64_non_aligned() {
let values: Vec<i64> = (0..7).collect();
let mut result = vec![0u64; 1];
simd::filter_lt_i64(&values, 3, &mut result);
assert_eq!(result[0] & 0b111, 0b111);
assert!((result[0] & (1u64 << 3)) == 0);
}
#[test]
fn test_ordered_float_nan_comparison() {
let nan1 = OrderedFloat(f64::NAN);
let nan2 = OrderedFloat(f64::NAN);
let regular = OrderedFloat(1.0);
assert_eq!(nan1.cmp(&nan2), std::cmp::Ordering::Equal);
assert_eq!(nan1.cmp(®ular), std::cmp::Ordering::Less);
assert_eq!(regular.cmp(&nan1), std::cmp::Ordering::Greater);
}
#[test]
fn test_ordered_key_from_value() {
let null_key = OrderedKey::from_value(&Value::Null);
assert_eq!(null_key, OrderedKey::Null);
let bool_key = OrderedKey::from_value(&Value::Bool(true));
assert_eq!(bool_key, OrderedKey::Bool(true));
let int_key = OrderedKey::from_value(&Value::Int(42));
assert_eq!(int_key, OrderedKey::Int(42));
let float_key = OrderedKey::from_value(&Value::Float(3.15));
assert!(matches!(float_key, OrderedKey::Float(_)));
let string_key = OrderedKey::from_value(&Value::String("test".to_string()));
assert_eq!(string_key, OrderedKey::String("test".to_string()));
}
#[test]
fn test_select_int_le_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("ints", schema).unwrap();
for i in 0..10 {
engine
.insert("ints", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine
.select("ints", Condition::Le("val".to_string(), Value::Int(5)))
.unwrap();
assert_eq!(rows.len(), 6); }
#[test]
fn test_select_int_gt_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("ints", schema).unwrap();
for i in 0..10 {
engine
.insert("ints", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine
.select("ints", Condition::Gt("val".to_string(), Value::Int(5)))
.unwrap();
assert_eq!(rows.len(), 4); }
#[test]
fn test_select_int_ge_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("ints", schema).unwrap();
for i in 0..10 {
engine
.insert("ints", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine
.select("ints", Condition::Ge("val".to_string(), Value::Int(5)))
.unwrap();
assert_eq!(rows.len(), 5); }
#[test]
fn test_select_int_eq_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("ints", schema).unwrap();
for i in 0..10 {
engine
.insert("ints", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine
.select("ints", Condition::Eq("val".to_string(), Value::Int(5)))
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_select_float_le_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("floats", schema).unwrap();
for i in 0..10 {
engine
.insert(
"floats",
HashMap::from([("val".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
let rows = engine
.select(
"floats",
Condition::Le("val".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 6); }
#[test]
fn test_select_float_ge_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("floats", schema).unwrap();
for i in 0..10 {
engine
.insert(
"floats",
HashMap::from([("val".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
let rows = engine
.select(
"floats",
Condition::Ge("val".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 5); }
#[test]
fn test_open_durable_engine() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let wal_path = dir.path().join("test.wal");
let config = tensor_store::WalConfig::default();
let engine = RelationalEngine::open_durable(&wal_path, config);
assert!(engine.is_ok());
}
#[test]
fn test_recover_engine() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let wal_path = dir.path().join("test.wal");
let config = tensor_store::WalConfig::default();
{
let _engine = RelationalEngine::open_durable(&wal_path, config.clone()).unwrap();
}
let recovered = RelationalEngine::recover(&wal_path, &config, None);
assert!(recovered.is_ok());
}
#[test]
fn test_durable_insert_and_delete() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let wal_path = dir.path().join("durable.wal");
let config = tensor_store::WalConfig::default();
let engine = RelationalEngine::open_durable(&wal_path, config).unwrap();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("durable_test", schema).unwrap();
engine
.insert(
"durable_test",
HashMap::from([("id".to_string(), Value::Int(1))]),
)
.unwrap();
let rows = engine.select("durable_test", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
engine
.delete_rows(
"durable_test",
Condition::Eq("id".to_string(), Value::Int(1)),
)
.unwrap();
let rows = engine.select("durable_test", Condition::True).unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn test_transaction_manager_with_timeout() {
use crate::transaction::TransactionManager;
use std::time::Duration;
let mgr = TransactionManager::with_timeout(Duration::from_secs(30));
let tx = mgr.begin();
assert!(mgr.is_active(tx));
}
#[test]
fn test_simd_popcount() {
let bitmap = vec![0b11111111u64, 0b00001111u64];
assert_eq!(simd::popcount(&bitmap), 12);
}
#[test]
fn test_simd_bitmap_words() {
assert_eq!(simd::bitmap_words(0), 0);
assert_eq!(simd::bitmap_words(1), 1);
assert_eq!(simd::bitmap_words(64), 1);
assert_eq!(simd::bitmap_words(65), 2);
assert_eq!(simd::bitmap_words(128), 2);
}
#[test]
fn test_simd_selected_indices() {
let bitmap = vec![0b10100101u64];
let indices = simd::selected_indices(&bitmap, 8);
assert_eq!(indices, vec![0, 2, 5, 7]);
}
#[test]
fn test_complex_and_or_conditions() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
]);
engine.create_table("complex", schema).unwrap();
for a in 0..5 {
for b in 0..5 {
engine
.insert(
"complex",
HashMap::from([
("a".to_string(), Value::Int(a)),
("b".to_string(), Value::Int(b)),
]),
)
.unwrap();
}
}
let cond = Condition::And(
Box::new(Condition::Lt("a".to_string(), Value::Int(2))),
Box::new(Condition::Gt("b".to_string(), Value::Int(2))),
);
let rows = engine.select("complex", cond).unwrap();
assert_eq!(rows.len(), 4);
let cond = Condition::Or(
Box::new(Condition::Eq("a".to_string(), Value::Int(0))),
Box::new(Condition::Eq("b".to_string(), Value::Int(0))),
);
let rows = engine.select("complex", cond).unwrap();
assert_eq!(rows.len(), 9);
}
#[test]
fn test_row_prefix_format() {
let prefix = RelationalEngine::row_prefix("users");
assert_eq!(prefix, "users:");
}
#[test]
fn test_table_meta_key_format() {
let key = RelationalEngine::table_meta_key("users");
assert_eq!(key, "_meta:table:users");
}
#[test]
fn test_index_meta_key_format() {
let key = RelationalEngine::index_meta_key("users", "email");
assert_eq!(key, "_idx:users:email");
}
#[test]
fn test_batch_insert_basic() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("batch_test", schema).unwrap();
let rows = vec![
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("Alice".to_string())),
]),
HashMap::from([
("id".to_string(), Value::Int(2)),
("name".to_string(), Value::String("Bob".to_string())),
]),
HashMap::from([
("id".to_string(), Value::Int(3)),
("name".to_string(), Value::String("Carol".to_string())),
]),
];
let ids = engine.batch_insert("batch_test", rows).unwrap();
assert_eq!(ids.len(), 3);
let result = engine.select("batch_test", Condition::True).unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn test_batch_insert_empty() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("empty_batch", schema).unwrap();
let ids = engine.batch_insert("empty_batch", vec![]).unwrap();
assert_eq!(ids.len(), 0);
}
#[test]
fn test_batch_insert_with_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("batch_idx", schema).unwrap();
engine.create_index("batch_idx", "val").unwrap();
let rows: Vec<_> = (0..10)
.map(|i| {
HashMap::from([
("id".to_string(), Value::Int(i)),
("val".to_string(), Value::Int(i % 3)),
])
})
.collect();
engine.batch_insert("batch_idx", rows).unwrap();
let result = engine
.select("batch_idx", Condition::Eq("val".to_string(), Value::Int(0)))
.unwrap();
assert_eq!(result.len(), 4);
}
#[test]
fn test_batch_insert_with_btree_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]);
engine.create_table("batch_btree", schema).unwrap();
engine.create_btree_index("batch_btree", "score").unwrap();
let rows: Vec<_> = (0..20)
.map(|i| {
HashMap::from([
("id".to_string(), Value::Int(i)),
("score".to_string(), Value::Int(i)),
])
})
.collect();
engine.batch_insert("batch_btree", rows).unwrap();
let result = engine
.select(
"batch_btree",
Condition::Gt("score".to_string(), Value::Int(15)),
)
.unwrap();
assert_eq!(result.len(), 4);
}
#[test]
fn test_batch_insert_null_validation() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("required", ColumnType::Int)]);
engine.create_table("batch_null", schema).unwrap();
let rows = vec![
HashMap::from([("required".to_string(), Value::Int(1))]),
HashMap::new(), ];
let result = engine.batch_insert("batch_null", rows);
assert!(matches!(result, Err(RelationalError::NullNotAllowed(_))));
}
#[test]
fn test_batch_insert_type_validation() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("num", ColumnType::Int)]);
engine.create_table("batch_type", schema).unwrap();
let rows = vec![
HashMap::from([("num".to_string(), Value::Int(1))]),
HashMap::from([("num".to_string(), Value::String("not an int".to_string()))]),
];
let result = engine.batch_insert("batch_type", rows);
assert!(matches!(result, Err(RelationalError::TypeMismatch { .. })));
}
#[test]
fn test_list_tables() {
let engine = RelationalEngine::new();
let tables = engine.list_tables();
assert_eq!(tables.len(), 0);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("table_a", schema.clone()).unwrap();
engine.create_table("table_b", schema.clone()).unwrap();
engine.create_table("table_c", schema).unwrap();
let tables = engine.list_tables();
assert_eq!(tables.len(), 3);
assert!(tables.contains(&"table_a".to_string()));
assert!(tables.contains(&"table_b".to_string()));
assert!(tables.contains(&"table_c".to_string()));
}
#[test]
fn test_btree_index_range_lt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("btree_lt", schema).unwrap();
engine.create_btree_index("btree_lt", "val").unwrap();
for i in 0..20 {
engine
.insert(
"btree_lt",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let result = engine
.select("btree_lt", Condition::Lt("val".to_string(), Value::Int(5)))
.unwrap();
assert_eq!(result.len(), 5); }
#[test]
fn test_btree_index_range_le() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("btree_le", schema).unwrap();
engine.create_btree_index("btree_le", "val").unwrap();
for i in 0..20 {
engine
.insert(
"btree_le",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let result = engine
.select("btree_le", Condition::Le("val".to_string(), Value::Int(5)))
.unwrap();
assert_eq!(result.len(), 6); }
#[test]
fn test_btree_index_range_gt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("btree_gt", schema).unwrap();
engine.create_btree_index("btree_gt", "val").unwrap();
for i in 0..20 {
engine
.insert(
"btree_gt",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let result = engine
.select("btree_gt", Condition::Gt("val".to_string(), Value::Int(15)))
.unwrap();
assert_eq!(result.len(), 4); }
#[test]
fn test_btree_index_range_ge() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("btree_ge", schema).unwrap();
engine.create_btree_index("btree_ge", "val").unwrap();
for i in 0..20 {
engine
.insert(
"btree_ge",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let result = engine
.select("btree_ge", Condition::Ge("val".to_string(), Value::Int(15)))
.unwrap();
assert_eq!(result.len(), 5); }
#[test]
fn test_index_lookup_with_and_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
]);
engine.create_table("and_idx", schema).unwrap();
engine.create_index("and_idx", "a").unwrap();
for a in 0..5 {
for b in 0..5 {
engine
.insert(
"and_idx",
HashMap::from([
("a".to_string(), Value::Int(a)),
("b".to_string(), Value::Int(b)),
]),
)
.unwrap();
}
}
let cond = Condition::And(
Box::new(Condition::Eq("a".to_string(), Value::Int(2))),
Box::new(Condition::Lt("b".to_string(), Value::Int(3))),
);
let rows = engine.select("and_idx", cond).unwrap();
assert_eq!(rows.len(), 3);
}
#[test]
fn test_btree_index_with_and_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
]);
engine.create_table("btree_and", schema).unwrap();
engine.create_btree_index("btree_and", "a").unwrap();
for a in 0..10 {
for b in 0..10 {
engine
.insert(
"btree_and",
HashMap::from([
("a".to_string(), Value::Int(a)),
("b".to_string(), Value::Int(b)),
]),
)
.unwrap();
}
}
let cond = Condition::And(
Box::new(Condition::Lt("a".to_string(), Value::Int(3))),
Box::new(Condition::Gt("b".to_string(), Value::Int(5))),
);
let rows = engine.select("btree_and", cond).unwrap();
assert_eq!(rows.len(), 12);
}
#[test]
fn test_index_on_id_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("id_idx", schema).unwrap();
engine.create_index("id_idx", "_id").unwrap();
for i in 0..10 {
engine
.insert(
"id_idx",
HashMap::from([("name".to_string(), Value::String(format!("user{i}")))]),
)
.unwrap();
}
let result = engine
.select("id_idx", Condition::Eq("_id".to_string(), Value::Int(5)))
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].id, 5);
}
#[test]
fn test_btree_index_on_id_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("btree_id", schema).unwrap();
engine.create_btree_index("btree_id", "_id").unwrap();
for i in 0..20 {
engine
.insert(
"btree_id",
HashMap::from([("name".to_string(), Value::String(format!("user{i}")))]),
)
.unwrap();
}
let result = engine
.select("btree_id", Condition::Lt("_id".to_string(), Value::Int(6)))
.unwrap();
assert_eq!(result.len(), 5); }
#[test]
fn test_drop_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("drop_idx", schema).unwrap();
engine.create_index("drop_idx", "val").unwrap();
for i in 0..5 {
engine
.insert(
"drop_idx",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let result = engine.drop_index("drop_idx", "val");
assert!(result.is_ok());
let rows = engine
.select("drop_idx", Condition::Eq("val".to_string(), Value::Int(2)))
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_drop_btree_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("drop_btree", schema).unwrap();
engine.create_btree_index("drop_btree", "val").unwrap();
for i in 0..5 {
engine
.insert(
"drop_btree",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let result = engine.drop_btree_index("drop_btree", "val");
assert!(result.is_ok());
let rows = engine
.select(
"drop_btree",
Condition::Lt("val".to_string(), Value::Int(3)),
)
.unwrap();
assert_eq!(rows.len(), 3);
}
#[test]
fn test_update_with_hash_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("update_idx", schema).unwrap();
engine.create_index("update_idx", "val").unwrap();
for i in 0..10 {
engine
.insert(
"update_idx",
HashMap::from([
("id".to_string(), Value::Int(i)),
("val".to_string(), Value::Int(i % 3)),
]),
)
.unwrap();
}
let updated = engine
.update(
"update_idx",
Condition::Eq("val".to_string(), Value::Int(0)),
HashMap::from([("val".to_string(), Value::Int(99))]),
)
.unwrap();
assert!(updated > 0);
let result = engine
.select(
"update_idx",
Condition::Eq("val".to_string(), Value::Int(0)),
)
.unwrap();
assert_eq!(result.len(), 0);
let result = engine
.select(
"update_idx",
Condition::Eq("val".to_string(), Value::Int(99)),
)
.unwrap();
assert_eq!(result.len(), updated);
}
#[test]
fn test_select_columnar() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
Column::new("c", ColumnType::Int),
]);
engine.create_table("columnar", schema).unwrap();
for i in 0..100 {
engine
.insert(
"columnar",
HashMap::from([
("a".to_string(), Value::Int(i)),
("b".to_string(), Value::Int(i * 2)),
("c".to_string(), Value::Int(i * 3)),
]),
)
.unwrap();
}
let opts = ColumnarScanOptions {
projection: Some(vec!["a".to_string(), "b".to_string()]),
prefer_columnar: true,
};
let result = engine
.select_columnar(
"columnar",
Condition::Lt("a".to_string(), Value::Int(10)),
opts,
)
.unwrap();
assert_eq!(result.len(), 10);
}
#[test]
fn test_count_rows() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("count_test", schema).unwrap();
for i in 0..50 {
engine
.insert(
"count_test",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let count = engine.count("count_test", Condition::True).unwrap();
assert_eq!(count, 50);
let count = engine
.count(
"count_test",
Condition::Lt("val".to_string(), Value::Int(10)),
)
.unwrap();
assert_eq!(count, 10);
}
#[test]
fn test_sum_aggregate() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("sum_test", schema).unwrap();
for i in 1..=10 {
engine
.insert(
"sum_test",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let sum = engine.sum("sum_test", "val", Condition::True).unwrap();
assert_eq!(sum, 55.0);
}
#[test]
fn test_avg_aggregate() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("avg_test", schema).unwrap();
for i in 0..10 {
engine
.insert(
"avg_test",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let avg = engine.avg("avg_test", "val", Condition::True).unwrap();
assert!((avg.unwrap() - 4.5).abs() < f64::EPSILON);
}
#[test]
fn test_min_aggregate() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("min_test", schema).unwrap();
for i in [5, 3, 8, 1, 9, 2] {
engine
.insert(
"min_test",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let min = engine.min("min_test", "val", Condition::True).unwrap();
assert_eq!(min, Some(Value::Int(1)));
}
#[test]
fn test_max_aggregate() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("max_test", schema).unwrap();
for i in [5, 3, 8, 1, 9, 2] {
engine
.insert(
"max_test",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let max = engine.max("max_test", "val", Condition::True).unwrap();
assert_eq!(max, Some(Value::Int(9)));
}
#[test]
fn test_count_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("cnt_col", schema).unwrap();
engine
.insert(
"cnt_col",
HashMap::from([("val".to_string(), Value::Int(1))]),
)
.unwrap();
engine
.insert("cnt_col", HashMap::from([("val".to_string(), Value::Null)]))
.unwrap();
engine
.insert(
"cnt_col",
HashMap::from([("val".to_string(), Value::Int(3))]),
)
.unwrap();
let count = engine
.count_column("cnt_col", "val", Condition::True)
.unwrap();
assert_eq!(count, 2);
}
#[test]
fn test_parallel_sum_threshold() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("psum", schema).unwrap();
for i in 0..1001 {
engine
.insert("psum", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
let sum = engine.sum("psum", "val", Condition::True).unwrap();
assert_eq!(sum, 500500.0);
}
#[test]
fn test_parallel_avg_threshold() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("pavg", schema).unwrap();
for i in 0..1001 {
engine
.insert(
"pavg",
HashMap::from([("val".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
let avg = engine.avg("pavg", "val", Condition::True).unwrap().unwrap();
assert!((avg - 500.0).abs() < 0.001);
}
#[test]
fn test_parallel_min_threshold() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("pmin", schema).unwrap();
for i in 0..1001 {
engine
.insert(
"pmin",
HashMap::from([("val".to_string(), Value::Int(1000 - i))]),
)
.unwrap();
}
let min = engine.min("pmin", "val", Condition::True).unwrap();
assert_eq!(min, Some(Value::Int(0)));
}
#[test]
fn test_parallel_max_threshold() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("pmax", schema).unwrap();
for i in 0..1001 {
engine
.insert("pmax", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
let max = engine.max("pmax", "val", Condition::True).unwrap();
assert_eq!(max, Some(Value::Int(1000)));
}
#[test]
fn test_tx_rollback_inserted_row() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_rb_ins", schema).unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_insert(
tx_id,
"tx_rb_ins",
HashMap::from([("val".to_string(), Value::Int(42))]),
)
.unwrap();
let rows = engine.select("tx_rb_ins", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
engine.rollback(tx_id).unwrap();
let rows = engine.select("tx_rb_ins", Condition::True).unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn test_tx_rollback_updated_row() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_rb_upd", schema).unwrap();
engine
.insert(
"tx_rb_upd",
HashMap::from([("val".to_string(), Value::Int(10))]),
)
.unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_update(
tx_id,
"tx_rb_upd",
Condition::True,
HashMap::from([("val".to_string(), Value::Int(99))]),
)
.unwrap();
let rows = engine.select("tx_rb_upd", Condition::True).unwrap();
assert_eq!(rows[0].get("val"), Some(&Value::Int(99)));
engine.rollback(tx_id).unwrap();
let rows = engine.select("tx_rb_upd", Condition::True).unwrap();
assert_eq!(rows[0].get("val"), Some(&Value::Int(10)));
}
#[test]
fn test_tx_rollback_deleted_row() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_rb_del", schema).unwrap();
engine
.insert(
"tx_rb_del",
HashMap::from([("val".to_string(), Value::Int(42))]),
)
.unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_delete(tx_id, "tx_rb_del", Condition::True)
.unwrap();
let rows = engine.select("tx_rb_del", Condition::True).unwrap();
assert_eq!(rows.len(), 0);
engine.rollback(tx_id).unwrap();
let rows = engine.select("tx_rb_del", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("val"), Some(&Value::Int(42)));
}
#[test]
fn test_tx_not_found_commit() {
let engine = RelationalEngine::new();
let result = engine.commit(99999);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(99999))
));
}
#[test]
fn test_tx_not_found_rollback() {
let engine = RelationalEngine::new();
let result = engine.rollback(99999);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(99999))
));
}
#[test]
fn test_tx_inactive_commit() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_inact", schema).unwrap();
let tx_id = engine.begin_transaction();
engine.commit(tx_id).unwrap();
let result = engine.commit(tx_id);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_slab_vectorized_filter_float_lt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("slab_flt_lt", schema).unwrap();
for i in 0..10 {
engine
.insert(
"slab_flt_lt",
HashMap::from([("val".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
let rows = engine
.select(
"slab_flt_lt",
Condition::Lt("val".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 5); }
#[test]
fn test_slab_vectorized_filter_float_gt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("slab_flt_gt", schema).unwrap();
for i in 0..10 {
engine
.insert(
"slab_flt_gt",
HashMap::from([("val".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
let rows = engine
.select(
"slab_flt_gt",
Condition::Gt("val".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 4); }
#[test]
fn test_slab_vectorized_filter_float_eq() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("slab_flt_eq", schema).unwrap();
for i in 0..10 {
engine
.insert(
"slab_flt_eq",
HashMap::from([("val".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
let rows = engine
.select(
"slab_flt_eq",
Condition::Eq("val".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_slab_vectorized_filter_int_ne() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("slab_int_ne", schema).unwrap();
for i in 0..10 {
engine
.insert(
"slab_int_ne",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"slab_int_ne",
Condition::Ne("val".to_string(), Value::Int(5)),
)
.unwrap();
assert_eq!(rows.len(), 9); }
#[test]
fn test_slab_vectorized_filter_int_le() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("slab_int_le", schema).unwrap();
for i in 0..10 {
engine
.insert(
"slab_int_le",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"slab_int_le",
Condition::Le("val".to_string(), Value::Int(5)),
)
.unwrap();
assert_eq!(rows.len(), 6); }
#[test]
fn test_slab_vectorized_filter_int_ge() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("slab_int_ge", schema).unwrap();
for i in 0..10 {
engine
.insert(
"slab_int_ge",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"slab_int_ge",
Condition::Ge("val".to_string(), Value::Int(5)),
)
.unwrap();
assert_eq!(rows.len(), 5); }
#[test]
fn test_slab_vectorized_filter_int_lt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("slab_int_lt", schema).unwrap();
for i in 0..10 {
engine
.insert(
"slab_int_lt",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"slab_int_lt",
Condition::Lt("val".to_string(), Value::Int(5)),
)
.unwrap();
assert_eq!(rows.len(), 5); }
#[test]
fn test_slab_vectorized_filter_int_gt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("slab_int_gt", schema).unwrap();
for i in 0..10 {
engine
.insert(
"slab_int_gt",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"slab_int_gt",
Condition::Gt("val".to_string(), Value::Int(5)),
)
.unwrap();
assert_eq!(rows.len(), 4); }
#[test]
fn test_slab_vectorized_filter_and() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("slab_and", schema).unwrap();
for i in 0..10 {
engine
.insert(
"slab_and",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let cond = Condition::And(
Box::new(Condition::Gt("val".to_string(), Value::Int(3))),
Box::new(Condition::Lt("val".to_string(), Value::Int(7))),
);
let rows = engine.select("slab_and", cond).unwrap();
assert_eq!(rows.len(), 3); }
#[test]
fn test_slab_vectorized_filter_or() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("slab_or", schema).unwrap();
for i in 0..10 {
engine
.insert(
"slab_or",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let cond = Condition::Or(
Box::new(Condition::Lt("val".to_string(), Value::Int(2))),
Box::new(Condition::Gt("val".to_string(), Value::Int(7))),
);
let rows = engine.select("slab_or", cond).unwrap();
assert_eq!(rows.len(), 4); }
#[test]
fn test_natural_join_missing_column() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![
Column::new("id", ColumnType::Int).nullable(),
Column::new("val", ColumnType::Int),
]);
let schema_b = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("other", ColumnType::Int),
]);
engine.create_table("nj_miss_a", schema_a).unwrap();
engine.create_table("nj_miss_b", schema_b).unwrap();
engine
.insert(
"nj_miss_a",
HashMap::from([
("id".to_string(), Value::Null),
("val".to_string(), Value::Int(1)),
]),
)
.unwrap();
engine
.insert(
"nj_miss_b",
HashMap::from([
("id".to_string(), Value::Int(1)),
("other".to_string(), Value::Int(2)),
]),
)
.unwrap();
let results = engine.natural_join("nj_miss_a", "nj_miss_b").unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_sum_with_float_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("sum_float", schema).unwrap();
for i in 0..5 {
engine
.insert(
"sum_float",
HashMap::from([("val".to_string(), Value::Float(f64::from(i) * 1.5))]),
)
.unwrap();
}
let sum = engine.sum("sum_float", "val", Condition::True).unwrap();
assert!((sum - 15.0).abs() < 0.001);
}
#[test]
fn test_avg_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("avg_empty", schema).unwrap();
let avg = engine.avg("avg_empty", "val", Condition::True).unwrap();
assert_eq!(avg, None);
}
#[test]
fn test_min_float_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("min_float", schema).unwrap();
for v in [5.5, 3.3, 8.8, 1.1, 9.9] {
engine
.insert(
"min_float",
HashMap::from([("val".to_string(), Value::Float(v))]),
)
.unwrap();
}
let min = engine.min("min_float", "val", Condition::True).unwrap();
assert_eq!(min, Some(Value::Float(1.1)));
}
#[test]
fn test_max_float_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("max_float", schema).unwrap();
for v in [5.5, 3.3, 8.8, 1.1, 9.9] {
engine
.insert(
"max_float",
HashMap::from([("val".to_string(), Value::Float(v))]),
)
.unwrap();
}
let max = engine.max("max_float", "val", Condition::True).unwrap();
assert_eq!(max, Some(Value::Float(9.9)));
}
#[test]
fn test_columnar_select_with_projection() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
Column::new("c", ColumnType::Int),
]);
engine.create_table("col_proj", schema).unwrap();
for i in 0..5 {
engine
.insert(
"col_proj",
HashMap::from([
("a".to_string(), Value::Int(i)),
("b".to_string(), Value::Int(i * 2)),
("c".to_string(), Value::Int(i * 3)),
]),
)
.unwrap();
}
let opts = ColumnarScanOptions {
projection: Some(vec!["a".to_string(), "c".to_string()]),
..Default::default()
};
let rows = engine
.select_columnar("col_proj", Condition::True, opts)
.unwrap();
assert_eq!(rows.len(), 5);
for row in &rows {
assert!(row.get("a").is_some());
assert!(row.get("c").is_some());
assert!(row.get("b").is_none());
}
}
#[test]
fn test_tx_rollback_with_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_rb_idx", schema).unwrap();
engine.create_index("tx_rb_idx", "val").unwrap();
engine
.insert(
"tx_rb_idx",
HashMap::from([("val".to_string(), Value::Int(10))]),
)
.unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_insert(
tx_id,
"tx_rb_idx",
HashMap::from([("val".to_string(), Value::Int(20))]),
)
.unwrap();
engine.rollback(tx_id).unwrap();
let rows = engine.select("tx_rb_idx", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("val"), Some(&Value::Int(10)));
}
#[test]
fn test_tx_rollback_with_btree_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_rb_btree", schema).unwrap();
engine.create_btree_index("tx_rb_btree", "val").unwrap();
engine
.insert(
"tx_rb_btree",
HashMap::from([("val".to_string(), Value::Int(10))]),
)
.unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_update(
tx_id,
"tx_rb_btree",
Condition::True,
HashMap::from([("val".to_string(), Value::Int(99))]),
)
.unwrap();
engine.rollback(tx_id).unwrap();
let rows = engine.select("tx_rb_btree", Condition::True).unwrap();
assert_eq!(rows[0].get("val"), Some(&Value::Int(10)));
}
#[test]
fn test_tx_insert_rollback_cleans_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("idx_clean", schema).unwrap();
engine.create_index("idx_clean", "val").unwrap();
let tx = engine.begin_transaction();
engine
.tx_insert(
tx,
"idx_clean",
HashMap::from([("val".to_string(), Value::Int(42))]),
)
.unwrap();
engine.rollback(tx).unwrap();
let rows = engine
.select(
"idx_clean",
Condition::Eq("val".to_string(), Value::Int(42)),
)
.unwrap();
assert!(
rows.is_empty(),
"Index should not contain stale entries after rollback"
);
let engine2 = RelationalEngine::new();
let schema2 = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine2.create_table("btree_clean", schema2).unwrap();
engine2.create_btree_index("btree_clean", "val").unwrap();
let tx2 = engine2.begin_transaction();
engine2
.tx_insert(
tx2,
"btree_clean",
HashMap::from([("val".to_string(), Value::Int(99))]),
)
.unwrap();
engine2.rollback(tx2).unwrap();
let rows2 = engine2
.select(
"btree_clean",
Condition::Eq("val".to_string(), Value::Int(99)),
)
.unwrap();
assert!(
rows2.is_empty(),
"BTree index should not contain stale entries after rollback"
);
}
#[test]
fn test_empty_row_count_selection() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("empty_sel", schema).unwrap();
let rows = engine
.select_columnar("empty_sel", Condition::True, ColumnarScanOptions::default())
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn test_tx_update_not_found() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_upd_nf", schema).unwrap();
let result = engine.tx_update(
99999,
"tx_upd_nf",
Condition::True,
HashMap::from([("val".to_string(), Value::Int(42))]),
);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(99999))
));
}
#[test]
fn test_tx_delete_not_found() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_del_nf", schema).unwrap();
let result = engine.tx_delete(99999, "tx_del_nf", Condition::True);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(99999))
));
}
#[test]
fn test_tx_insert_not_found() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_ins_nf", schema).unwrap();
let result = engine.tx_insert(
99999,
"tx_ins_nf",
HashMap::from([("val".to_string(), Value::Int(42))]),
);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(99999))
));
}
#[test]
fn test_tx_insert_with_index_on_id() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_ins_id_idx", schema).unwrap();
engine.create_index("tx_ins_id_idx", "_id").unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_insert(
tx_id,
"tx_ins_id_idx",
HashMap::from([("val".to_string(), Value::Int(42))]),
)
.unwrap();
engine.commit(tx_id).unwrap();
let rows = engine.select("tx_ins_id_idx", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_tx_insert_with_btree_index_on_id() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_ins_bt_id", schema).unwrap();
engine.create_btree_index("tx_ins_bt_id", "_id").unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_insert(
tx_id,
"tx_ins_bt_id",
HashMap::from([("val".to_string(), Value::Int(42))]),
)
.unwrap();
engine.commit(tx_id).unwrap();
let rows = engine.select("tx_ins_bt_id", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_rollback_insert_with_id_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("rb_ins_id", schema).unwrap();
engine.create_index("rb_ins_id", "_id").unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_insert(
tx_id,
"rb_ins_id",
HashMap::from([("val".to_string(), Value::Int(42))]),
)
.unwrap();
engine.rollback(tx_id).unwrap();
let rows = engine.select("rb_ins_id", Condition::True).unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn test_rollback_update_with_id_btree() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("rb_upd_bt", schema).unwrap();
engine.create_btree_index("rb_upd_bt", "_id").unwrap();
engine
.insert(
"rb_upd_bt",
HashMap::from([("val".to_string(), Value::Int(10))]),
)
.unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_update(
tx_id,
"rb_upd_bt",
Condition::True,
HashMap::from([("val".to_string(), Value::Int(99))]),
)
.unwrap();
engine.rollback(tx_id).unwrap();
let rows = engine.select("rb_upd_bt", Condition::True).unwrap();
assert_eq!(rows[0].get("val"), Some(&Value::Int(10)));
}
#[test]
fn test_rollback_delete_with_indexes() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("rb_del_idx", schema).unwrap();
engine.create_index("rb_del_idx", "val").unwrap();
engine.create_btree_index("rb_del_idx", "val").unwrap();
engine
.insert(
"rb_del_idx",
HashMap::from([("val".to_string(), Value::Int(42))]),
)
.unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_delete(tx_id, "rb_del_idx", Condition::True)
.unwrap();
engine.rollback(tx_id).unwrap();
let rows = engine.select("rb_del_idx", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("val"), Some(&Value::Int(42)));
}
#[test]
fn test_sum_with_non_numeric_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::String)]);
engine.create_table("sum_str", schema).unwrap();
engine
.insert(
"sum_str",
HashMap::from([("val".to_string(), Value::String("hello".to_string()))]),
)
.unwrap();
let sum = engine.sum("sum_str", "val", Condition::True).unwrap();
assert_eq!(sum, 0.0);
}
#[test]
fn test_avg_with_non_numeric_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::String)]);
engine.create_table("avg_str", schema).unwrap();
engine
.insert(
"avg_str",
HashMap::from([("val".to_string(), Value::String("hello".to_string()))]),
)
.unwrap();
let avg = engine.avg("avg_str", "val", Condition::True).unwrap();
assert_eq!(avg, None);
}
#[test]
fn test_min_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("min_empty", schema).unwrap();
let min = engine.min("min_empty", "val", Condition::True).unwrap();
assert_eq!(min, None);
}
#[test]
fn test_max_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("max_empty", schema).unwrap();
let max = engine.max("max_empty", "val", Condition::True).unwrap();
assert_eq!(max, None);
}
#[test]
fn test_columnar_scan_with_null_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("col_null_int", schema).unwrap();
engine
.insert(
"col_null_int",
HashMap::from([("val".to_string(), Value::Int(1))]),
)
.unwrap();
engine
.insert(
"col_null_int",
HashMap::from([("val".to_string(), Value::Null)]),
)
.unwrap();
engine
.insert(
"col_null_int",
HashMap::from([("val".to_string(), Value::Int(3))]),
)
.unwrap();
let rows = engine.select("col_null_int", Condition::True).unwrap();
assert_eq!(rows.len(), 3);
}
#[test]
fn test_columnar_scan_with_null_float() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float).nullable()]);
engine.create_table("col_null_flt", schema).unwrap();
engine
.insert(
"col_null_flt",
HashMap::from([("val".to_string(), Value::Float(1.1))]),
)
.unwrap();
engine
.insert(
"col_null_flt",
HashMap::from([("val".to_string(), Value::Null)]),
)
.unwrap();
engine
.insert(
"col_null_flt",
HashMap::from([("val".to_string(), Value::Float(3.3))]),
)
.unwrap();
let rows = engine.select("col_null_flt", Condition::True).unwrap();
assert_eq!(rows.len(), 3);
}
#[test]
fn test_tx_is_active() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_active", schema).unwrap();
let tx_id = engine.begin_transaction();
assert!(engine.is_transaction_active(tx_id));
engine.commit(tx_id).unwrap();
assert!(!engine.is_transaction_active(tx_id));
}
#[test]
fn test_parallel_join_large() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
let schema_b = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("other", ColumnType::Int),
]);
engine.create_table("pjoin_a", schema_a).unwrap();
engine.create_table("pjoin_b", schema_b).unwrap();
for i in 0..1001 {
engine
.insert(
"pjoin_a",
HashMap::from([
("id".to_string(), Value::Int(i)),
("val".to_string(), Value::Int(i * 2)),
]),
)
.unwrap();
}
for i in 0..10 {
engine
.insert(
"pjoin_b",
HashMap::from([
("id".to_string(), Value::Int(i)),
("other".to_string(), Value::Int(i + 100)),
]),
)
.unwrap();
}
let results = engine.natural_join("pjoin_a", "pjoin_b").unwrap();
assert_eq!(results.len(), 10);
}
#[test]
fn test_select_with_condition_on_deleted_rows() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("del_cond", schema).unwrap();
for i in 0..5 {
engine
.insert(
"del_cond",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine
.delete_rows("del_cond", Condition::Lt("val".to_string(), Value::Int(3)))
.unwrap();
let rows = engine.select("del_cond", Condition::True).unwrap();
assert_eq!(rows.len(), 2); }
#[test]
fn test_result_too_large_error_display() {
let err = RelationalError::ResultTooLarge {
operation: "CROSS JOIN".to_string(),
actual: 2000000,
max: 1000000,
};
let display = format!("{err}");
assert!(display.contains("CROSS JOIN"));
assert!(display.contains("2000000"));
assert!(display.contains("1000000"));
}
#[test]
fn test_select_with_string_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("str_cond", schema).unwrap();
engine
.insert(
"str_cond",
HashMap::from([("name".to_string(), Value::String("alice".to_string()))]),
)
.unwrap();
engine
.insert(
"str_cond",
HashMap::from([("name".to_string(), Value::String("bob".to_string()))]),
)
.unwrap();
let rows = engine
.select(
"str_cond",
Condition::Eq("name".to_string(), Value::String("alice".to_string())),
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_tx_update_column_not_found() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_upd_cnf", schema).unwrap();
engine
.insert(
"tx_upd_cnf",
HashMap::from([("val".to_string(), Value::Int(1))]),
)
.unwrap();
let tx_id = engine.begin_transaction();
let result = engine.tx_update(
tx_id,
"tx_upd_cnf",
Condition::True,
HashMap::from([("nonexistent".to_string(), Value::Int(42))]),
);
assert!(matches!(result, Err(RelationalError::ColumnNotFound(_))));
engine.rollback(tx_id).unwrap();
}
#[test]
fn test_tx_update_type_mismatch() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_upd_type", schema).unwrap();
engine
.insert(
"tx_upd_type",
HashMap::from([("val".to_string(), Value::Int(1))]),
)
.unwrap();
let tx_id = engine.begin_transaction();
let result = engine.tx_update(
tx_id,
"tx_upd_type",
Condition::True,
HashMap::from([("val".to_string(), Value::String("wrong".to_string()))]),
);
assert!(matches!(result, Err(RelationalError::TypeMismatch { .. })));
engine.rollback(tx_id).unwrap();
}
#[test]
fn test_tx_update_null_not_allowed() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_upd_null", schema).unwrap();
engine
.insert(
"tx_upd_null",
HashMap::from([("val".to_string(), Value::Int(1))]),
)
.unwrap();
let tx_id = engine.begin_transaction();
let result = engine.tx_update(
tx_id,
"tx_upd_null",
Condition::True,
HashMap::from([("val".to_string(), Value::Null)]),
);
assert!(matches!(result, Err(RelationalError::NullNotAllowed(_))));
engine.rollback(tx_id).unwrap();
}
#[test]
fn test_tx_inactive_rollback() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("tx_inact_rb", schema).unwrap();
let tx_id = engine.begin_transaction();
engine.commit(tx_id).unwrap();
let result = engine.rollback(tx_id);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_min_with_null_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("min_nulls", schema).unwrap();
engine
.insert(
"min_nulls",
HashMap::from([("val".to_string(), Value::Null)]),
)
.unwrap();
engine
.insert(
"min_nulls",
HashMap::from([("val".to_string(), Value::Int(10))]),
)
.unwrap();
engine
.insert(
"min_nulls",
HashMap::from([("val".to_string(), Value::Int(5))]),
)
.unwrap();
let min = engine.min("min_nulls", "val", Condition::True).unwrap();
assert_eq!(min, Some(Value::Int(5)));
}
#[test]
fn test_max_with_null_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("max_nulls", schema).unwrap();
engine
.insert(
"max_nulls",
HashMap::from([("val".to_string(), Value::Null)]),
)
.unwrap();
engine
.insert(
"max_nulls",
HashMap::from([("val".to_string(), Value::Int(10))]),
)
.unwrap();
engine
.insert(
"max_nulls",
HashMap::from([("val".to_string(), Value::Int(5))]),
)
.unwrap();
let max = engine.max("max_nulls", "val", Condition::True).unwrap();
assert_eq!(max, Some(Value::Int(10)));
}
#[test]
fn test_sum_with_null_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("sum_nulls", schema).unwrap();
engine
.insert(
"sum_nulls",
HashMap::from([("val".to_string(), Value::Null)]),
)
.unwrap();
engine
.insert(
"sum_nulls",
HashMap::from([("val".to_string(), Value::Int(10))]),
)
.unwrap();
engine
.insert(
"sum_nulls",
HashMap::from([("val".to_string(), Value::Int(5))]),
)
.unwrap();
let sum = engine.sum("sum_nulls", "val", Condition::True).unwrap();
assert_eq!(sum, 15.0);
}
#[test]
fn test_avg_with_null_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float).nullable()]);
engine.create_table("avg_nulls", schema).unwrap();
engine
.insert(
"avg_nulls",
HashMap::from([("val".to_string(), Value::Null)]),
)
.unwrap();
engine
.insert(
"avg_nulls",
HashMap::from([("val".to_string(), Value::Float(10.0))]),
)
.unwrap();
engine
.insert(
"avg_nulls",
HashMap::from([("val".to_string(), Value::Float(20.0))]),
)
.unwrap();
let avg = engine.avg("avg_nulls", "val", Condition::True).unwrap();
assert!(avg.is_some());
}
#[test]
fn test_min_string() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::String)]);
engine.create_table("min_str", schema).unwrap();
engine
.insert(
"min_str",
HashMap::from([("val".to_string(), Value::String("banana".to_string()))]),
)
.unwrap();
engine
.insert(
"min_str",
HashMap::from([("val".to_string(), Value::String("apple".to_string()))]),
)
.unwrap();
engine
.insert(
"min_str",
HashMap::from([("val".to_string(), Value::String("cherry".to_string()))]),
)
.unwrap();
let min = engine.min("min_str", "val", Condition::True).unwrap();
assert_eq!(min, Some(Value::String("apple".to_string())));
}
#[test]
fn test_max_string() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::String)]);
engine.create_table("max_str", schema).unwrap();
engine
.insert(
"max_str",
HashMap::from([("val".to_string(), Value::String("banana".to_string()))]),
)
.unwrap();
engine
.insert(
"max_str",
HashMap::from([("val".to_string(), Value::String("apple".to_string()))]),
)
.unwrap();
engine
.insert(
"max_str",
HashMap::from([("val".to_string(), Value::String("cherry".to_string()))]),
)
.unwrap();
let max = engine.max("max_str", "val", Condition::True).unwrap();
assert_eq!(max, Some(Value::String("cherry".to_string())));
}
#[test]
fn test_update_with_condition_no_match() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("upd_no_match", schema).unwrap();
engine
.insert(
"upd_no_match",
HashMap::from([("val".to_string(), Value::Int(10))]),
)
.unwrap();
let updated = engine
.update(
"upd_no_match",
Condition::Eq("val".to_string(), Value::Int(99)),
HashMap::from([("val".to_string(), Value::Int(20))]),
)
.unwrap();
assert_eq!(updated, 0);
let rows = engine.select("upd_no_match", Condition::True).unwrap();
assert_eq!(rows[0].get("val"), Some(&Value::Int(10)));
}
#[test]
fn test_delete_with_condition_no_match() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("del_no_match", schema).unwrap();
engine
.insert(
"del_no_match",
HashMap::from([("val".to_string(), Value::Int(10))]),
)
.unwrap();
let deleted = engine
.delete_rows(
"del_no_match",
Condition::Eq("val".to_string(), Value::Int(99)),
)
.unwrap();
assert_eq!(deleted, 0);
let rows = engine.select("del_no_match", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_simd_filter_non_aligned_lt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("simd_na_lt", schema).unwrap();
for i in 0..5 {
engine
.insert(
"simd_na_lt",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"simd_na_lt",
Condition::Lt("val".to_string(), Value::Int(3)),
)
.unwrap();
assert_eq!(rows.len(), 3); }
#[test]
fn test_simd_filter_non_aligned_le() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("simd_na_le", schema).unwrap();
for i in 0..5 {
engine
.insert(
"simd_na_le",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"simd_na_le",
Condition::Le("val".to_string(), Value::Int(3)),
)
.unwrap();
assert_eq!(rows.len(), 4); }
#[test]
fn test_simd_filter_non_aligned_gt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("simd_na_gt", schema).unwrap();
for i in 0..5 {
engine
.insert(
"simd_na_gt",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"simd_na_gt",
Condition::Gt("val".to_string(), Value::Int(2)),
)
.unwrap();
assert_eq!(rows.len(), 2); }
#[test]
fn test_simd_filter_non_aligned_ge() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("simd_na_ge", schema).unwrap();
for i in 0..5 {
engine
.insert(
"simd_na_ge",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"simd_na_ge",
Condition::Ge("val".to_string(), Value::Int(2)),
)
.unwrap();
assert_eq!(rows.len(), 3); }
#[test]
fn test_simd_filter_non_aligned_eq() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("simd_na_eq", schema).unwrap();
for i in 0..5 {
engine
.insert(
"simd_na_eq",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"simd_na_eq",
Condition::Eq("val".to_string(), Value::Int(4)),
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_simd_filter_non_aligned_ne() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("simd_na_ne", schema).unwrap();
for i in 0..5 {
engine
.insert(
"simd_na_ne",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select(
"simd_na_ne",
Condition::Ne("val".to_string(), Value::Int(4)),
)
.unwrap();
assert_eq!(rows.len(), 4); }
#[test]
fn test_simd_filter_float_non_aligned_lt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("simd_fna_lt", schema).unwrap();
for i in 0..5 {
engine
.insert(
"simd_fna_lt",
HashMap::from([("val".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
let rows = engine
.select(
"simd_fna_lt",
Condition::Lt("val".to_string(), Value::Float(3.0)),
)
.unwrap();
assert_eq!(rows.len(), 3); }
#[test]
fn test_simd_filter_float_non_aligned_gt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("simd_fna_gt", schema).unwrap();
for i in 0..5 {
engine
.insert(
"simd_fna_gt",
HashMap::from([("val".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
let rows = engine
.select(
"simd_fna_gt",
Condition::Gt("val".to_string(), Value::Float(2.0)),
)
.unwrap();
assert_eq!(rows.len(), 2); }
#[test]
fn test_simd_filter_float_non_aligned_eq() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("simd_fna_eq", schema).unwrap();
for i in 0..5 {
engine
.insert(
"simd_fna_eq",
HashMap::from([("val".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
let rows = engine
.select(
"simd_fna_eq",
Condition::Eq("val".to_string(), Value::Float(4.0)),
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_invalid_name_error_display() {
let err = RelationalError::InvalidName("test message".to_string());
let display = format!("{err}");
assert!(display.contains("Invalid name"));
assert!(display.contains("test message"));
}
#[test]
fn test_durable_engine_delete() {
use tensor_store::WalConfig;
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test_del.wal");
let config = WalConfig::default();
let engine = RelationalEngine::open_durable(&wal_path, config).unwrap();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("dur_del", schema).unwrap();
engine
.insert(
"dur_del",
HashMap::from([("val".to_string(), Value::Int(42))]),
)
.unwrap();
let deleted = engine.delete_rows("dur_del", Condition::True).unwrap();
assert_eq!(deleted, 1);
let rows = engine.select("dur_del", Condition::True).unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn test_durable_engine_drop_table() {
use tensor_store::WalConfig;
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test_drop.wal");
let config = WalConfig::default();
let engine = RelationalEngine::open_durable(&wal_path, config).unwrap();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("dur_drop", schema).unwrap();
engine
.insert(
"dur_drop",
HashMap::from([("val".to_string(), Value::Int(42))]),
)
.unwrap();
engine.drop_table("dur_drop").unwrap();
let result = engine.select("dur_drop", Condition::True);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_concurrent_create_table_same_name() {
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use std::thread;
let engine = Arc::new(RelationalEngine::new());
let success = Arc::new(AtomicUsize::new(0));
let error = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let eng = Arc::clone(&engine);
let s = Arc::clone(&success);
let e = Arc::clone(&error);
thread::spawn(move || {
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
match eng.create_table("contested", schema) {
Ok(()) => {
s.fetch_add(1, Ordering::SeqCst);
},
Err(RelationalError::TableAlreadyExists(_)) => {
e.fetch_add(1, Ordering::SeqCst);
},
Err(err) => panic!("unexpected error: {err:?}"),
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
assert_eq!(success.load(Ordering::SeqCst), 1);
assert_eq!(error.load(Ordering::SeqCst), 9);
}
#[test]
fn test_concurrent_create_index_same_column() {
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use std::thread;
let engine = Arc::new(RelationalEngine::new());
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("idx_test", schema).unwrap();
let success = Arc::new(AtomicUsize::new(0));
let error = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let eng = Arc::clone(&engine);
let s = Arc::clone(&success);
let e = Arc::clone(&error);
thread::spawn(move || match eng.create_index("idx_test", "value") {
Ok(()) => {
s.fetch_add(1, Ordering::SeqCst);
},
Err(RelationalError::IndexAlreadyExists { .. }) => {
e.fetch_add(1, Ordering::SeqCst);
},
Err(err) => panic!("unexpected error: {err:?}"),
})
})
.collect();
for h in handles {
h.join().unwrap();
}
assert_eq!(success.load(Ordering::SeqCst), 1);
assert_eq!(error.load(Ordering::SeqCst), 9);
}
#[test]
fn test_concurrent_create_btree_index_same_column() {
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use std::thread;
let engine = Arc::new(RelationalEngine::new());
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("btree_test", schema).unwrap();
let success = Arc::new(AtomicUsize::new(0));
let error = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let eng = Arc::clone(&engine);
let s = Arc::clone(&success);
let e = Arc::clone(&error);
thread::spawn(
move || match eng.create_btree_index("btree_test", "value") {
Ok(()) => {
s.fetch_add(1, Ordering::SeqCst);
},
Err(RelationalError::IndexAlreadyExists { .. }) => {
e.fetch_add(1, Ordering::SeqCst);
},
Err(err) => panic!("unexpected error: {err:?}"),
},
)
})
.collect();
for h in handles {
h.join().unwrap();
}
assert_eq!(success.load(Ordering::SeqCst), 1);
assert_eq!(error.load(Ordering::SeqCst), 9);
}
#[test]
fn test_concurrent_drop_table_same_name() {
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use std::thread;
let engine = Arc::new(RelationalEngine::new());
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("drop_test", schema).unwrap();
let success = Arc::new(AtomicUsize::new(0));
let error = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let eng = Arc::clone(&engine);
let s = Arc::clone(&success);
let e = Arc::clone(&error);
thread::spawn(move || match eng.drop_table("drop_test") {
Ok(()) => {
s.fetch_add(1, Ordering::SeqCst);
},
Err(RelationalError::TableNotFound(_)) => {
e.fetch_add(1, Ordering::SeqCst);
},
Err(err) => panic!("unexpected error: {err:?}"),
})
})
.collect();
for h in handles {
h.join().unwrap();
}
assert_eq!(success.load(Ordering::SeqCst), 1);
assert_eq!(error.load(Ordering::SeqCst), 9);
}
mod condition_tensor_tests {
use super::*;
use tensor_store::{ScalarValue, TensorData, TensorValue};
fn make_test_tensor(id: i64, val: i64) -> TensorData {
let mut data = TensorData::new();
data.set("_id".to_string(), TensorValue::Scalar(ScalarValue::Int(id)));
data.set(
"val".to_string(),
TensorValue::Scalar(ScalarValue::Int(val)),
);
data
}
#[test]
fn test_condition_tensor_true() {
let tensor = make_test_tensor(1, 42);
assert!(Condition::True.evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_eq() {
let tensor = make_test_tensor(1, 42);
assert!(Condition::Eq("val".to_string(), Value::Int(42)).evaluate_tensor(&tensor));
assert!(!Condition::Eq("val".to_string(), Value::Int(99)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_ne() {
let tensor = make_test_tensor(1, 42);
assert!(Condition::Ne("val".to_string(), Value::Int(99)).evaluate_tensor(&tensor));
assert!(!Condition::Ne("val".to_string(), Value::Int(42)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_lt() {
let tensor = make_test_tensor(1, 42);
assert!(Condition::Lt("val".to_string(), Value::Int(50)).evaluate_tensor(&tensor));
assert!(!Condition::Lt("val".to_string(), Value::Int(30)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_le() {
let tensor = make_test_tensor(1, 42);
assert!(Condition::Le("val".to_string(), Value::Int(42)).evaluate_tensor(&tensor));
assert!(Condition::Le("val".to_string(), Value::Int(50)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_gt() {
let tensor = make_test_tensor(1, 42);
assert!(Condition::Gt("val".to_string(), Value::Int(30)).evaluate_tensor(&tensor));
assert!(!Condition::Gt("val".to_string(), Value::Int(50)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_ge() {
let tensor = make_test_tensor(1, 42);
assert!(Condition::Ge("val".to_string(), Value::Int(42)).evaluate_tensor(&tensor));
assert!(Condition::Ge("val".to_string(), Value::Int(30)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_and() {
let tensor = make_test_tensor(1, 42);
let cond = Condition::And(
Box::new(Condition::Gt("val".to_string(), Value::Int(30))),
Box::new(Condition::Lt("val".to_string(), Value::Int(50))),
);
assert!(cond.evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_or() {
let tensor = make_test_tensor(1, 42);
let cond = Condition::Or(
Box::new(Condition::Eq("val".to_string(), Value::Int(99))),
Box::new(Condition::Eq("val".to_string(), Value::Int(42))),
);
assert!(cond.evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_id_field() {
let tensor = make_test_tensor(100, 42);
assert!(Condition::Eq("_id".to_string(), Value::Int(100)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_missing_column() {
let tensor = make_test_tensor(1, 42);
assert!(!Condition::Eq("missing".to_string(), Value::Int(42)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_float() {
let mut tensor = TensorData::new();
tensor.set(
"score".to_string(),
TensorValue::Scalar(ScalarValue::Float(3.15)),
);
assert!(Condition::Eq("score".to_string(), Value::Float(3.15)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_string() {
let mut tensor = TensorData::new();
tensor.set(
"name".to_string(),
TensorValue::Scalar(ScalarValue::String("test".to_string())),
);
assert!(
Condition::Eq("name".to_string(), Value::String("test".to_string()))
.evaluate_tensor(&tensor)
);
}
#[test]
fn test_condition_tensor_missing_id() {
let mut tensor = TensorData::new();
tensor.set("val".to_string(), TensorValue::Scalar(ScalarValue::Int(42)));
assert!(!Condition::Eq("_id".to_string(), Value::Int(1)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_le_missing() {
let tensor = make_test_tensor(1, 42);
assert!(!Condition::Le("missing".to_string(), Value::Int(50)).evaluate_tensor(&tensor));
}
#[test]
fn test_condition_tensor_ge_missing() {
let tensor = make_test_tensor(1, 42);
assert!(!Condition::Ge("missing".to_string(), Value::Int(30)).evaluate_tensor(&tensor));
}
}
#[test]
fn test_update_atomicity_on_failure() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("atomic_test", schema).unwrap();
engine.create_index("atomic_test", "val").unwrap();
engine
.insert(
"atomic_test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("val".to_string(), Value::Int(100)),
]),
)
.unwrap();
let rows = engine
.select(
"atomic_test",
Condition::Eq("val".to_string(), Value::Int(100)),
)
.unwrap();
assert_eq!(rows.len(), 1);
let result = engine.update(
"atomic_test",
Condition::True,
HashMap::from([("val".to_string(), Value::String("not_int".to_string()))]),
);
assert!(result.is_err());
let rows = engine
.select(
"atomic_test",
Condition::Eq("val".to_string(), Value::Int(100)),
)
.unwrap();
assert_eq!(
rows.len(),
1,
"Data should be unchanged after failed update"
);
}
#[test]
fn test_insert_atomicity() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("insert_atomic", schema).unwrap();
engine.create_index("insert_atomic", "val").unwrap();
let row_id = engine
.insert(
"insert_atomic",
HashMap::from([("val".to_string(), Value::Int(42))]),
)
.unwrap();
assert_eq!(row_id, 1);
let rows = engine
.select(
"insert_atomic",
Condition::Eq("val".to_string(), Value::Int(42)),
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_delete_atomicity() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("delete_atomic", schema).unwrap();
engine.create_index("delete_atomic", "val").unwrap();
engine
.insert(
"delete_atomic",
HashMap::from([("val".to_string(), Value::Int(1))]),
)
.unwrap();
engine
.insert(
"delete_atomic",
HashMap::from([("val".to_string(), Value::Int(2))]),
)
.unwrap();
let count = engine
.delete_rows(
"delete_atomic",
Condition::Eq("val".to_string(), Value::Int(1)),
)
.unwrap();
assert_eq!(count, 1);
let rows = engine.select("delete_atomic", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
let indexed = engine
.select(
"delete_atomic",
Condition::Eq("val".to_string(), Value::Int(2)),
)
.unwrap();
assert_eq!(indexed.len(), 1);
}
#[test]
fn test_index_lock_sharding_bounded_memory() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("users", schema).unwrap();
engine.create_index("users", "name").unwrap();
for i in 0..1000 {
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String(format!("user_{i}"))),
("age".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 1000);
}
#[test]
fn test_concurrent_index_operations_with_lock_striping() {
use std::sync::Arc;
use std::thread;
let engine = Arc::new(RelationalEngine::new());
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("users", schema).unwrap();
engine.create_index("users", "age").unwrap();
let handles: Vec<_> = (0..10)
.map(|t| {
let eng = Arc::clone(&engine);
thread::spawn(move || {
for i in 0..100 {
let mut values = HashMap::new();
values.insert(
"name".to_string(),
Value::String(format!("thread_{t}_user_{i}")),
);
values.insert("age".to_string(), Value::Int(i64::from(t * 1000 + i)));
eng.insert("users", values).unwrap();
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 1000);
}
#[test]
fn test_btree_index_lock_ordering_no_deadlock() {
use std::sync::Arc;
use std::thread;
use std::time::Duration;
let engine = Arc::new(RelationalEngine::new());
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("lock_test", schema).unwrap();
engine.create_btree_index("lock_test", "value").unwrap();
for i in 0..100 {
let mut values = HashMap::new();
values.insert("value".to_string(), Value::Int(i));
engine.insert("lock_test", values).unwrap();
}
let mut handles = vec![];
for t in 0..4 {
let eng = Arc::clone(&engine);
handles.push(thread::spawn(move || {
for i in 0..50 {
let val = i64::from(t * 1000 + i);
let mut values = HashMap::new();
values.insert("value".to_string(), Value::Int(val));
let _ = eng.insert("lock_test", values);
}
}));
}
for _ in 0..2 {
let eng = Arc::clone(&engine);
handles.push(thread::spawn(move || {
for i in 0..50 {
let _ = eng.delete_rows(
"lock_test",
Condition::Eq("value".to_string(), Value::Int(i)),
);
}
}));
}
let start = std::time::Instant::now();
for handle in handles {
handle.join().expect("Thread panicked");
}
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_secs(10),
"Possible deadlock: took {elapsed:?}"
);
}
#[test]
fn test_rollback_always_releases_locks_even_on_undo_failure() {
use std::sync::Arc;
let engine = Arc::new(RelationalEngine::new());
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let tx1 = engine.begin_transaction();
let mut values = HashMap::new();
values.insert("value".to_string(), Value::Int(42));
engine.tx_insert(tx1, "test", values).unwrap();
let _ = engine.rollback(tx1);
assert!(
engine.tx_manager.get(tx1).is_none(),
"Transaction should be removed after rollback"
);
let tx2 = engine.begin_transaction();
let mut values2 = HashMap::new();
values2.insert("value".to_string(), Value::Int(100));
let result = engine.tx_insert(tx2, "test", values2);
assert!(
result.is_ok(),
"New transaction should succeed after rollback"
);
engine.commit(tx2).unwrap();
}
#[test]
fn test_rollback_completes_all_undo_entries_even_with_errors() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
]);
engine.create_table("multi", schema).unwrap();
let tx = engine.begin_transaction();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("a".to_string(), Value::Int(i));
values.insert("b".to_string(), Value::Int(i * 10));
engine.tx_insert(tx, "multi", values).unwrap();
}
let result = engine.rollback(tx);
assert!(
engine.tx_manager.get(tx).is_none(),
"Transaction must be removed after rollback"
);
assert!(result.is_ok());
let rows = engine.select("multi", Condition::True).unwrap();
assert_eq!(rows.len(), 0, "All inserted rows should be undone");
}
#[test]
fn test_rollback_returns_error_but_still_cleans_up() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("cleanup_test", schema).unwrap();
let tx = engine.begin_transaction();
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Int(1));
engine.tx_insert(tx, "cleanup_test", values).unwrap();
let _ = engine.rollback(tx);
let result = engine.rollback(tx);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_get_schema_missing_column_metadata_returns_error() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("test", schema).unwrap();
let meta_key = "_meta:table:test";
let mut meta = engine.store.get(meta_key).unwrap();
meta.set(
"_columns".to_string(),
TensorValue::Scalar(ScalarValue::String("name,ghost_column".to_string())),
);
engine.store.put(meta_key, meta).unwrap();
let result = engine.get_schema("test");
assert!(result.is_err());
let err = result.unwrap_err();
match err {
RelationalError::SchemaCorrupted { table, reason } => {
assert_eq!(table, "test");
assert!(reason.contains("ghost_column"));
assert!(reason.contains("missing metadata"));
},
other => panic!("Expected SchemaCorrupted, got: {other}"),
}
}
#[test]
fn test_get_schema_malformed_type_string_returns_error() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("test", schema).unwrap();
let meta_key = "_meta:table:test";
let mut meta = engine.store.get(meta_key).unwrap();
meta.set(
"_col:name".to_string(),
TensorValue::Scalar(ScalarValue::String("string".to_string())), );
engine.store.put(meta_key, meta).unwrap();
let result = engine.get_schema("test");
assert!(result.is_err());
let err = result.unwrap_err();
match err {
RelationalError::SchemaCorrupted { table, reason } => {
assert_eq!(table, "test");
assert!(reason.contains("malformed type string"));
assert!(reason.contains("name"));
},
other => panic!("Expected SchemaCorrupted, got: {other}"),
}
}
#[test]
fn test_get_schema_unknown_column_type_returns_error() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("data", ColumnType::String)]);
engine.create_table("test", schema).unwrap();
let meta_key = "_meta:table:test";
let mut meta = engine.store.get(meta_key).unwrap();
meta.set(
"_col:data".to_string(),
TensorValue::Scalar(ScalarValue::String("blob:notnull".to_string())),
);
engine.store.put(meta_key, meta).unwrap();
let result = engine.get_schema("test");
assert!(result.is_err());
let err = result.unwrap_err();
match err {
RelationalError::SchemaCorrupted { table, reason } => {
assert_eq!(table, "test");
assert!(reason.contains("unknown column type"));
assert!(reason.contains("blob"));
},
other => panic!("Expected SchemaCorrupted, got: {other}"),
}
}
#[test]
fn test_btree_index_respects_entry_limit() {
let mut engine = RelationalEngine::new();
engine.max_btree_entries = 3;
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine.create_btree_index("test", "val").unwrap();
for i in 0..3 {
engine
.insert("test", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
assert_eq!(engine.btree_entry_count.load(Ordering::Relaxed), 3);
let result = engine.insert(
"test",
HashMap::from([("val".to_string(), Value::Int(100))]),
);
assert!(result.is_err());
match result.unwrap_err() {
RelationalError::ResultTooLarge {
operation,
actual,
max,
} => {
assert_eq!(operation, "btree_index_add");
assert_eq!(actual, 4);
assert_eq!(max, 3);
},
other => panic!("Expected ResultTooLarge, got: {other}"),
}
engine
.insert("test", HashMap::from([("val".to_string(), Value::Int(0))]))
.unwrap();
assert_eq!(engine.btree_entry_count.load(Ordering::Relaxed), 3);
}
#[test]
fn test_btree_index_remove_decrements_counter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine.create_btree_index("test", "val").unwrap();
for i in 0..3 {
engine
.insert("test", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
assert_eq!(engine.btree_entry_count.load(Ordering::Relaxed), 3);
engine
.delete_rows("test", Condition::Eq("val".to_string(), Value::Int(1)))
.unwrap();
assert_eq!(engine.btree_entry_count.load(Ordering::Relaxed), 2);
engine
.delete_rows("test", Condition::Eq("val".to_string(), Value::Int(0)))
.unwrap();
assert_eq!(engine.btree_entry_count.load(Ordering::Relaxed), 1);
}
#[test]
fn test_drop_btree_index_clears_counter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine.create_btree_index("test", "val").unwrap();
for i in 0..5 {
engine
.insert("test", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
assert_eq!(engine.btree_entry_count.load(Ordering::Relaxed), 5);
engine.drop_btree_index("test", "val").unwrap();
assert_eq!(engine.btree_entry_count.load(Ordering::Relaxed), 0);
}
#[test]
fn test_error_display_too_many_tables() {
let err = RelationalError::TooManyTables {
current: 10,
max: 5,
};
let msg = err.to_string();
assert!(msg.contains("10"));
assert!(msg.contains('5'));
assert!(msg.contains("Too many tables"));
}
#[test]
fn test_error_display_too_many_indexes() {
let err = RelationalError::TooManyIndexes {
table: "users".to_string(),
current: 8,
max: 5,
};
let msg = err.to_string();
assert!(msg.contains("users"));
assert!(msg.contains('8'));
assert!(msg.contains('5'));
}
#[test]
fn test_error_display_query_timeout() {
let err = RelationalError::QueryTimeout {
operation: "SELECT".to_string(),
timeout_ms: 5000,
};
let msg = err.to_string();
assert!(msg.contains("SELECT"));
assert!(msg.contains("5000"));
assert!(msg.contains("timeout"));
}
#[test]
fn test_with_store_and_config() {
let store = TensorStore::new();
let config = RelationalConfig::new().with_max_tables(3);
let engine = RelationalEngine::with_store_and_config(store, config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("t1", schema.clone()).unwrap();
engine.create_table("t2", schema.clone()).unwrap();
engine.create_table("t3", schema.clone()).unwrap();
let result = engine.create_table("t4", schema);
assert!(matches!(result, Err(RelationalError::TooManyTables { .. })));
}
#[test]
fn test_resolve_timeout_uses_default_30_seconds() {
let config = RelationalConfig::default();
let engine = RelationalEngine::with_config(config);
let resolved = engine.resolve_timeout(QueryOptions::new());
assert_eq!(resolved, Some(30_000));
}
#[test]
fn test_resolve_timeout_uses_default() {
let config = RelationalConfig::new().with_default_timeout_ms(3000);
let engine = RelationalEngine::with_config(config);
let resolved = engine.resolve_timeout(QueryOptions::new());
assert_eq!(resolved, Some(3000));
}
#[test]
fn test_resolve_timeout_clamps_query_options_to_max() {
let config = RelationalConfig::new().with_max_timeout_ms(5000);
let engine = RelationalEngine::with_config(config);
let resolved = engine.resolve_timeout(QueryOptions::new().with_timeout_ms(10000));
assert_eq!(resolved, Some(5000));
}
#[test]
fn test_resolve_timeout_no_clamping_without_max() {
let config = RelationalConfig::new().with_default_timeout_ms(1000);
let engine = RelationalEngine::with_config(config);
let resolved = engine.resolve_timeout(QueryOptions::new().with_timeout_ms(50000));
assert_eq!(resolved, Some(50000));
}
#[test]
fn test_config_validation_error_message() {
let config = RelationalConfig::new()
.with_default_timeout_ms(10000)
.with_max_timeout_ms(5000);
let err = config.validate().unwrap_err();
assert!(err.contains("10000"));
assert!(err.contains("5000"));
}
#[test]
fn test_config_all_builder_methods() {
let config = RelationalConfig::new()
.with_max_tables(50)
.with_max_indexes_per_table(8)
.with_max_btree_entries(2_000_000)
.with_default_timeout_ms(5000)
.with_max_timeout_ms(30000);
assert_eq!(config.max_tables, Some(50));
assert_eq!(config.max_indexes_per_table, Some(8));
assert_eq!(config.max_btree_entries, 2_000_000);
assert_eq!(config.default_query_timeout_ms, Some(5000));
assert_eq!(config.max_query_timeout_ms, Some(30000));
assert!(config.validate().is_ok());
}
#[test]
fn test_query_options_is_copy() {
let opts = QueryOptions::new().with_timeout_ms(1000);
let opts2 = opts; let opts3 = opts; assert_eq!(opts2.timeout_ms, opts3.timeout_ms);
}
#[test]
fn test_select_with_options_success() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
let result = engine.select_with_options(
"test",
Condition::True,
QueryOptions::new().with_timeout_ms(60000),
);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_update_with_options_success() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
engine
.insert(
"test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("val".to_string(), Value::Int(100)),
]),
)
.unwrap();
let result = engine.update_with_options(
"test",
Condition::True,
HashMap::from([("val".to_string(), Value::Int(200))]),
QueryOptions::new().with_timeout_ms(60000),
);
assert!(result.is_ok());
}
#[test]
fn test_delete_with_options_success() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
let result = engine.delete_rows_with_options(
"test",
Condition::True,
QueryOptions::new().with_timeout_ms(60000),
);
assert!(result.is_ok());
}
#[test]
fn test_join_with_options_success() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("a", schema.clone()).unwrap();
engine.create_table("b", schema).unwrap();
engine
.insert(
"a",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("x".to_string())),
]),
)
.unwrap();
engine
.insert(
"b",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("y".to_string())),
]),
)
.unwrap();
let result = engine.join_with_options(
"a",
"b",
"id",
"id",
QueryOptions::new().with_timeout_ms(60000),
);
assert!(result.is_ok());
}
#[test]
fn test_check_table_limit_no_limit() {
let config = RelationalConfig::default();
let engine = RelationalEngine::with_config(config);
for i in 0..5 {
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table(&format!("t{i}"), schema).unwrap();
}
assert_eq!(engine.table_count(), 5);
}
#[test]
fn test_check_index_limit_no_limit() {
let config = RelationalConfig::default();
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
Column::new("c", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
engine.create_index("test", "a").unwrap();
engine.create_index("test", "b").unwrap();
engine.create_index("test", "c").unwrap();
}
#[test]
fn test_btree_index_limit_enforced() {
let config = RelationalConfig::new().with_max_indexes_per_table(2);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
Column::new("c", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
engine.create_btree_index("test", "a").unwrap();
engine.create_btree_index("test", "b").unwrap();
let result = engine.create_btree_index("test", "c");
assert!(matches!(
result,
Err(RelationalError::TooManyIndexes { .. })
));
}
#[test]
fn test_drop_table_decrements_count() {
let config = RelationalConfig::new().with_max_tables(5);
let engine = RelationalEngine::with_config(config);
for i in 0..5 {
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table(&format!("t{i}"), schema).unwrap();
}
assert_eq!(engine.table_count(), 5);
engine.drop_table("t0").unwrap();
assert_eq!(engine.table_count(), 4);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("t5", schema).unwrap();
assert_eq!(engine.table_count(), 5);
}
#[test]
fn test_select_with_btree_index_and_timeout() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
engine.create_btree_index("test", "id").unwrap();
engine
.insert(
"test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("val".to_string(), Value::Int(100)),
]),
)
.unwrap();
let result = engine.select_with_options(
"test",
Condition::Eq("id".to_string(), Value::Int(1)),
QueryOptions::new().with_timeout_ms(60000),
);
assert!(result.is_ok());
}
#[test]
fn test_config_debug_display() {
let config = RelationalConfig::new().with_max_tables(10);
let debug_str = format!("{config:?}");
assert!(debug_str.contains("max_tables"));
assert!(debug_str.contains("10"));
}
#[test]
fn test_query_options_debug_display() {
let opts = QueryOptions::new().with_timeout_ms(5000);
let debug_str = format!("{opts:?}");
assert!(debug_str.contains("timeout_ms"));
assert!(debug_str.contains("5000"));
}
#[test]
fn test_config_clone() {
let config = RelationalConfig::new()
.with_max_tables(10)
.with_max_indexes_per_table(5);
let cloned = config.clone();
assert_eq!(cloned.max_tables, config.max_tables);
assert_eq!(cloned.max_indexes_per_table, config.max_indexes_per_table);
}
#[test]
fn test_query_options_default_timeout() {
let opts = QueryOptions::default();
assert!(opts.timeout_ms.is_none());
}
#[test]
fn test_config_presets() {
let high = RelationalConfig::high_throughput();
assert_eq!(high.max_btree_entries, 20_000_000);
assert!(high.max_tables.is_none());
let low = RelationalConfig::low_memory();
assert_eq!(low.max_tables, Some(100));
assert_eq!(low.max_btree_entries, 1_000_000);
}
#[test]
fn test_select_timeout_after_sleep() {
use std::thread;
use std::time::Duration;
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..100 {
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(i))]))
.unwrap();
}
thread::sleep(Duration::from_millis(1));
let result = engine.select_with_options(
"test",
Condition::True,
QueryOptions::new().with_timeout_ms(0),
);
match result {
Ok(_) => (),
Err(RelationalError::QueryTimeout { .. }) => (),
Err(e) => panic!("Unexpected error: {e}"),
}
}
#[test]
fn test_update_with_timeout_immediate() {
let config = RelationalConfig::new();
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
engine
.insert(
"test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("val".to_string(), Value::Int(100)),
]),
)
.unwrap();
let result = engine.update_with_options(
"test",
Condition::True,
HashMap::from([("val".to_string(), Value::Int(200))]),
QueryOptions::new().with_timeout_ms(0),
);
match result {
Ok(_) => (),
Err(RelationalError::QueryTimeout { .. }) => (),
Err(e) => panic!("Unexpected error: {e}"),
}
}
#[test]
fn test_delete_with_timeout_immediate() {
let config = RelationalConfig::new();
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
let result = engine.delete_rows_with_options(
"test",
Condition::True,
QueryOptions::new().with_timeout_ms(0),
);
match result {
Ok(_) => (),
Err(RelationalError::QueryTimeout { .. }) => (),
Err(e) => panic!("Unexpected error: {e}"),
}
}
#[test]
fn test_join_with_timeout_immediate() {
let config = RelationalConfig::new();
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("a", schema.clone()).unwrap();
engine.create_table("b", schema).unwrap();
engine
.insert(
"a",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("x".to_string())),
]),
)
.unwrap();
engine
.insert(
"b",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("y".to_string())),
]),
)
.unwrap();
let result =
engine.join_with_options("a", "b", "id", "id", QueryOptions::new().with_timeout_ms(0));
match result {
Ok(_) => (),
Err(RelationalError::QueryTimeout { .. }) => (),
Err(e) => panic!("Unexpected error: {e}"),
}
}
#[test]
fn test_select_with_index_and_timeout() {
let config = RelationalConfig::new();
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
engine.create_index("test", "id").unwrap();
engine
.insert(
"test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("val".to_string(), Value::Int(100)),
]),
)
.unwrap();
let result = engine.select_with_options(
"test",
Condition::Eq("id".to_string(), Value::Int(1)),
QueryOptions::new().with_timeout_ms(0),
);
match result {
Ok(rows) => assert!(!rows.is_empty() || rows.is_empty()),
Err(RelationalError::QueryTimeout { .. }) => (),
Err(e) => panic!("Unexpected error: {e}"),
}
}
#[test]
fn test_slow_query_threshold_config() {
let config = RelationalConfig::new().with_slow_query_threshold_ms(50);
assert_eq!(config.slow_query_threshold_ms, 50);
let engine = RelationalEngine::with_config(config);
assert_eq!(engine.config().slow_query_threshold_ms, 50);
}
#[test]
fn test_slow_query_threshold_default() {
let config = RelationalConfig::default();
assert_eq!(config.slow_query_threshold_ms, 100);
}
#[test]
fn test_max_query_result_rows_config() {
let config = RelationalConfig::new().with_max_query_result_rows(1000);
assert_eq!(config.max_query_result_rows, Some(1000));
let engine = RelationalEngine::with_config(config);
assert_eq!(engine.config().max_query_result_rows, Some(1000));
}
#[test]
fn test_max_query_result_rows_enforcement() {
let config = RelationalConfig::new().with_max_query_result_rows(5);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let result = engine.select("test", Condition::True);
assert!(result.is_err());
match result {
Err(RelationalError::ResultTooLarge { actual, max, .. }) => {
assert_eq!(actual, 10);
assert_eq!(max, 5);
},
_ => panic!("expected ResultTooLarge error"),
}
}
#[test]
fn test_max_query_result_rows_under_limit_succeeds() {
let config = RelationalConfig::new().with_max_query_result_rows(100);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let result = engine.select("test", Condition::True);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 10);
}
#[test]
fn test_transaction_timeout_config() {
let config = RelationalConfig::new()
.with_transaction_timeout_secs(120)
.with_lock_timeout_secs(60);
assert_eq!(config.transaction_timeout_secs, 120);
assert_eq!(config.lock_timeout_secs, 60);
let engine = RelationalEngine::with_config(config);
assert_eq!(engine.config().transaction_timeout_secs, 120);
assert_eq!(engine.config().lock_timeout_secs, 60);
}
#[test]
fn test_transaction_timeout_defaults() {
let config = RelationalConfig::default();
assert_eq!(config.transaction_timeout_secs, 60);
assert_eq!(config.lock_timeout_secs, 30);
}
#[test]
fn test_cursor_options_default() {
let options = CursorOptions::default();
assert_eq!(options.batch_size, 1000);
assert_eq!(options.offset, 0);
}
#[test]
fn test_cursor_options_builder() {
let options = CursorOptions::new().with_batch_size(500).with_offset(100);
assert_eq!(options.batch_size, 500);
assert_eq!(options.offset, 100);
}
#[test]
fn test_select_iter_basic() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let cursor = engine
.select_iter("test", Condition::True, CursorOptions::default())
.unwrap();
assert_eq!(cursor.total_rows(), 10);
assert_eq!(cursor.rows_processed(), 0);
assert!(!cursor.is_exhausted());
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 10);
assert!(rows.iter().all(std::result::Result::is_ok));
}
#[test]
fn test_select_iter_with_offset() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let options = CursorOptions::new().with_offset(5);
let cursor = engine
.select_iter("test", Condition::True, options)
.unwrap();
assert_eq!(cursor.total_rows(), 5);
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_select_iter_with_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let cursor = engine
.select_iter(
"test",
Condition::Ge("id".to_string(), Value::Int(5)),
CursorOptions::default(),
)
.unwrap();
assert_eq!(cursor.total_rows(), 5);
}
#[test]
fn test_select_iter_empty_result() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let cursor = engine
.select_iter("test", Condition::True, CursorOptions::default())
.unwrap();
assert_eq!(cursor.total_rows(), 0);
assert!(cursor.is_exhausted());
let rows: Vec<_> = cursor.collect();
assert!(rows.is_empty());
}
#[test]
fn test_row_cursor_exact_size_iterator() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let mut cursor = engine
.select_iter("test", Condition::True, CursorOptions::default())
.unwrap();
assert_eq!(cursor.len(), 5);
cursor.next();
assert_eq!(cursor.len(), 4);
cursor.next();
cursor.next();
assert_eq!(cursor.len(), 2);
}
#[test]
fn test_row_cursor_debug() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let cursor = engine
.select_iter("test", Condition::True, CursorOptions::default())
.unwrap();
let debug_str = format!("{cursor:?}");
assert!(debug_str.contains("RowCursor"));
assert!(debug_str.contains("rows_processed"));
assert!(debug_str.contains("total_rows"));
}
#[test]
fn test_ordered_key_from_sortable_key_null() {
let key = OrderedKey::from_sortable_key("0");
assert_eq!(key, Some(OrderedKey::Null));
}
#[test]
fn test_ordered_key_from_sortable_key_bool() {
let true_key = OrderedKey::from_sortable_key("b1");
assert_eq!(true_key, Some(OrderedKey::Bool(true)));
let false_key = OrderedKey::from_sortable_key("b0");
assert_eq!(false_key, Some(OrderedKey::Bool(false)));
}
#[test]
fn test_ordered_key_from_sortable_key_string() {
let key = OrderedKey::from_sortable_key("shello");
assert_eq!(key, Some(OrderedKey::String("hello".to_string())));
let empty_key = OrderedKey::from_sortable_key("s");
assert_eq!(empty_key, Some(OrderedKey::String(String::new())));
}
#[test]
fn test_ordered_key_from_sortable_key_int_positive() {
let max_key = OrderedKey::from_sortable_key("iffffffffffffffff");
assert_eq!(max_key, Some(OrderedKey::Int(i64::MAX)));
}
#[test]
fn test_ordered_key_from_sortable_key_int_zero() {
let zero_key = OrderedKey::from_sortable_key("i8000000000000000");
assert_eq!(zero_key, Some(OrderedKey::Int(0)));
}
#[test]
fn test_ordered_key_from_sortable_key_int_negative() {
let min_key = OrderedKey::from_sortable_key("i0000000000000000");
assert_eq!(min_key, Some(OrderedKey::Int(i64::MIN)));
}
#[test]
fn test_ordered_key_from_sortable_key_float_positive() {
let parsed = OrderedKey::from_sortable_key("fbff0000000000000");
assert!(parsed.is_some());
if let Some(OrderedKey::Float(f)) = parsed {
assert!((f.0 - 1.0).abs() < f64::EPSILON);
} else {
panic!("expected Float, got {parsed:?}");
}
}
#[test]
fn test_ordered_key_from_sortable_key_float_zero() {
let parsed = OrderedKey::from_sortable_key("f8000000000000000");
assert_eq!(parsed, Some(OrderedKey::Float(OrderedFloat(0.0))));
}
#[test]
fn test_ordered_key_from_sortable_key_invalid() {
assert_eq!(OrderedKey::from_sortable_key("i"), None);
assert_eq!(OrderedKey::from_sortable_key("x123"), None);
assert_eq!(OrderedKey::from_sortable_key("b2"), None);
assert_eq!(OrderedKey::from_sortable_key("inotahex"), None);
assert_eq!(OrderedKey::from_sortable_key("fnotahex"), None);
}
#[test]
fn test_ordered_key_parsing_consistent() {
let test_cases = vec![
("0", OrderedKey::Null),
("b1", OrderedKey::Bool(true)),
("b0", OrderedKey::Bool(false)),
("i8000000000000000", OrderedKey::Int(0)),
("iffffffffffffffff", OrderedKey::Int(i64::MAX)),
("i0000000000000000", OrderedKey::Int(i64::MIN)),
("shello", OrderedKey::String("hello".to_string())),
("s", OrderedKey::String(String::new())),
];
for (sortable, expected) in test_cases {
let parsed = OrderedKey::from_sortable_key(sortable);
assert_eq!(
parsed,
Some(expected.clone()),
"parsing failed for {sortable:?} -> expected {expected:?}"
);
}
}
#[test]
fn test_bytes_column_type_roundtrip() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes),
]);
engine.create_table("test_bytes", schema).unwrap();
let mut row = HashMap::new();
row.insert("id".to_string(), Value::Int(1));
row.insert(
"data".to_string(),
Value::Bytes(vec![0x01, 0x02, 0x03, 0xFF]),
);
engine.insert("test_bytes", row).unwrap();
let rows = engine.select("test_bytes", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(
rows[0].get("data"),
Some(&Value::Bytes(vec![0x01, 0x02, 0x03, 0xFF]))
);
}
#[test]
fn test_json_column_type_roundtrip() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("metadata", ColumnType::Json),
]);
engine.create_table("test_json", schema).unwrap();
let json_val = serde_json::json!({"key": "value", "nested": {"num": 42}});
let mut row = HashMap::new();
row.insert("id".to_string(), Value::Int(1));
row.insert("metadata".to_string(), Value::Json(json_val));
engine.insert("test_json", row).unwrap();
let rows = engine.select("test_json", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
if let Some(Value::Json(j)) = rows[0].get("metadata") {
assert_eq!(j["key"], "value");
assert_eq!(j["nested"]["num"], 42);
} else {
panic!("Expected Json value");
}
}
#[test]
fn test_bytes_value_hash_key() {
let v1 = Value::Bytes(vec![1, 2, 3]);
let v2 = Value::Bytes(vec![1, 2, 3]);
let v3 = Value::Bytes(vec![4, 5, 6]);
assert_eq!(v1.hash_key(), v2.hash_key());
assert_ne!(v1.hash_key(), v3.hash_key());
}
#[test]
fn test_json_value_hash_key() {
let v1 = Value::Json(serde_json::json!({"a": 1}));
let v2 = Value::Json(serde_json::json!({"a": 1}));
let v3 = Value::Json(serde_json::json!({"b": 2}));
assert_eq!(v1.hash_key(), v2.hash_key());
assert_ne!(v1.hash_key(), v3.hash_key());
}
#[test]
fn test_bytes_value_matches_type() {
let v = Value::Bytes(vec![1, 2, 3]);
assert!(v.matches_type(&ColumnType::Bytes));
assert!(!v.matches_type(&ColumnType::String));
assert!(!v.matches_type(&ColumnType::Int));
}
#[test]
fn test_json_value_matches_type() {
let v = Value::Json(serde_json::json!({"a": 1}));
assert!(v.matches_type(&ColumnType::Json));
assert!(!v.matches_type(&ColumnType::String));
assert!(!v.matches_type(&ColumnType::Int));
}
#[test]
fn test_bytes_value_partial_cmp() {
let v1 = Value::Bytes(vec![1, 2, 3]);
let v2 = Value::Bytes(vec![1, 2, 4]);
let v3 = Value::Bytes(vec![1, 2, 3]);
assert_eq!(v1.partial_cmp_value(&v3), Some(std::cmp::Ordering::Equal));
assert_eq!(v1.partial_cmp_value(&v2), Some(std::cmp::Ordering::Less));
assert_eq!(v2.partial_cmp_value(&v1), Some(std::cmp::Ordering::Greater));
}
#[test]
fn test_json_value_partial_cmp() {
let v1 = Value::Json(serde_json::json!({"a": 1}));
let v2 = Value::Json(serde_json::json!({"b": 2}));
assert!(v1.partial_cmp_value(&v2).is_some());
}
#[test]
fn test_bytes_value_sortable_key() {
let v1 = Value::Bytes(vec![0x01, 0x02]);
let v2 = Value::Bytes(vec![0x01, 0x03]);
let key1 = v1.sortable_key();
let key2 = v2.sortable_key();
assert!(key1.starts_with('y'));
assert!(key1 < key2);
}
#[test]
fn test_json_value_sortable_key() {
let v = Value::Json(serde_json::json!({"a": 1}));
let key = v.sortable_key();
assert!(key.starts_with('j'));
}
#[test]
fn test_bytes_value_is_truthy() {
assert!(Value::Bytes(vec![1, 2, 3]).is_truthy());
assert!(!Value::Bytes(vec![]).is_truthy());
}
#[test]
fn test_json_value_is_truthy() {
assert!(Value::Json(serde_json::json!({"a": 1})).is_truthy());
assert!(Value::Json(serde_json::json!([1, 2, 3])).is_truthy());
assert!(Value::Json(serde_json::json!("hello")).is_truthy());
assert!(!Value::Json(serde_json::Value::Null).is_truthy());
}
#[test]
fn test_ordered_key_from_bytes_value() {
let v = Value::Bytes(vec![1, 2, 3]);
let key = OrderedKey::from_value(&v);
assert!(matches!(key, OrderedKey::Bytes(_)));
}
#[test]
fn test_ordered_key_from_json_value() {
let v = Value::Json(serde_json::json!({"a": 1}));
let key = OrderedKey::from_value(&v);
assert!(matches!(key, OrderedKey::Json(_)));
}
#[test]
fn test_ordered_key_bytes_sortable_roundtrip() {
let original = Value::Bytes(vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]);
let sortable_key = original.sortable_key();
let parsed = OrderedKey::from_sortable_key(&sortable_key);
assert_eq!(
parsed,
Some(OrderedKey::Bytes(vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]))
);
}
#[test]
fn test_ordered_key_json_sortable_roundtrip() {
let original = Value::Json(serde_json::json!({"key": "value"}));
let sortable_key = original.sortable_key();
let parsed = OrderedKey::from_sortable_key(&sortable_key);
assert!(matches!(parsed, Some(OrderedKey::Json(_))));
}
#[test]
fn test_ordered_key_empty_bytes_sortable() {
let key = OrderedKey::from_sortable_key("y");
assert_eq!(key, Some(OrderedKey::Bytes(Vec::new())));
}
#[test]
fn test_ordered_key_empty_json_sortable() {
let key = OrderedKey::from_sortable_key("j");
assert_eq!(key, Some(OrderedKey::Json(String::new())));
}
#[test]
fn test_bytes_column_to_slab_type() {
let col_type = ColumnType::Bytes;
let slab_type: SlabColumnType = (&col_type).into();
assert_eq!(slab_type, SlabColumnType::Bytes);
}
#[test]
fn test_json_column_to_slab_type() {
let col_type = ColumnType::Json;
let slab_type: SlabColumnType = (&col_type).into();
assert_eq!(slab_type, SlabColumnType::Json);
}
#[test]
fn test_bytes_value_to_slab_value() {
let val = Value::Bytes(vec![1, 2, 3]);
let slab_val: SlabColumnValue = (&val).into();
assert_eq!(slab_val, SlabColumnValue::Bytes(vec![1, 2, 3]));
}
#[test]
fn test_json_value_to_slab_value() {
let val = Value::Json(serde_json::json!({"a": 1}));
let slab_val: SlabColumnValue = (&val).into();
assert!(matches!(slab_val, SlabColumnValue::Json(_)));
}
#[test]
fn test_bytes_nullable_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes).nullable(),
]);
engine.create_table("test_bytes_null", schema).unwrap();
let mut row = HashMap::new();
row.insert("id".to_string(), Value::Int(1));
row.insert("data".to_string(), Value::Null);
engine.insert("test_bytes_null", row).unwrap();
let mut row2 = HashMap::new();
row2.insert("id".to_string(), Value::Int(2));
row2.insert("data".to_string(), Value::Bytes(vec![0xFF]));
engine.insert("test_bytes_null", row2).unwrap();
let rows = engine.select("test_bytes_null", Condition::True).unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn test_json_nullable_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("metadata", ColumnType::Json).nullable(),
]);
engine.create_table("test_json_null", schema).unwrap();
let mut row = HashMap::new();
row.insert("id".to_string(), Value::Int(1));
row.insert("metadata".to_string(), Value::Null);
engine.insert("test_json_null", row).unwrap();
let rows = engine.select("test_json_null", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("metadata"), Some(&Value::Null));
}
#[test]
fn test_bytes_type_mismatch_error() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes),
]);
engine.create_table("test_bytes_err", schema).unwrap();
let mut row = HashMap::new();
row.insert("id".to_string(), Value::Int(1));
row.insert("data".to_string(), Value::String("not bytes".to_string()));
let result = engine.insert("test_bytes_err", row);
assert!(matches!(result, Err(RelationalError::TypeMismatch { .. })));
}
#[test]
fn test_json_type_mismatch_error() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("metadata", ColumnType::Json),
]);
engine.create_table("test_json_err", schema).unwrap();
let mut row = HashMap::new();
row.insert("id".to_string(), Value::Int(1));
row.insert("metadata".to_string(), Value::Int(42));
let result = engine.insert("test_json_err", row);
assert!(matches!(result, Err(RelationalError::TypeMismatch { .. })));
}
#[test]
fn test_bytes_update() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes),
]);
engine.create_table("test_bytes_upd", schema).unwrap();
let mut row = HashMap::new();
row.insert("id".to_string(), Value::Int(1));
row.insert("data".to_string(), Value::Bytes(vec![1, 2, 3]));
engine.insert("test_bytes_upd", row).unwrap();
let mut updates = HashMap::new();
updates.insert("data".to_string(), Value::Bytes(vec![4, 5, 6]));
engine
.update("test_bytes_upd", Condition::True, updates)
.unwrap();
let rows = engine.select("test_bytes_upd", Condition::True).unwrap();
assert_eq!(rows[0].get("data"), Some(&Value::Bytes(vec![4, 5, 6])));
}
#[test]
fn test_json_update() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("metadata", ColumnType::Json),
]);
engine.create_table("test_json_upd", schema).unwrap();
let mut row = HashMap::new();
row.insert("id".to_string(), Value::Int(1));
row.insert(
"metadata".to_string(),
Value::Json(serde_json::json!({"old": true})),
);
engine.insert("test_json_upd", row).unwrap();
let mut updates = HashMap::new();
updates.insert(
"metadata".to_string(),
Value::Json(serde_json::json!({"new": true})),
);
engine
.update("test_json_upd", Condition::True, updates)
.unwrap();
let rows = engine.select("test_json_upd", Condition::True).unwrap();
if let Some(Value::Json(j)) = rows[0].get("metadata") {
assert_eq!(j["new"], true);
} else {
panic!("Expected Json value");
}
}
#[test]
fn test_bytes_schema_persistence() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes),
]);
engine.create_table("test_bytes_schema", schema).unwrap();
let retrieved = engine.get_schema("test_bytes_schema").unwrap();
let data_col = retrieved.get_column("data").unwrap();
assert_eq!(data_col.column_type, ColumnType::Bytes);
}
#[test]
fn test_json_schema_persistence() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("metadata", ColumnType::Json),
]);
engine.create_table("test_json_schema", schema).unwrap();
let retrieved = engine.get_schema("test_json_schema").unwrap();
let meta_col = retrieved.get_column("metadata").unwrap();
assert_eq!(meta_col.column_type, ColumnType::Json);
}
#[test]
fn test_select_distinct_single_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("dept", ColumnType::String),
]);
engine.create_table("employees", schema).unwrap();
engine
.insert(
"employees",
HashMap::from([
("name".to_string(), Value::String("Alice".into())),
("dept".to_string(), Value::String("Engineering".into())),
]),
)
.unwrap();
engine
.insert(
"employees",
HashMap::from([
("name".to_string(), Value::String("Bob".into())),
("dept".to_string(), Value::String("Engineering".into())),
]),
)
.unwrap();
engine
.insert(
"employees",
HashMap::from([
("name".to_string(), Value::String("Charlie".into())),
("dept".to_string(), Value::String("Sales".into())),
]),
)
.unwrap();
let distinct =
engine.select_distinct("employees", Condition::True, Some(&["dept".to_string()]));
let rows = distinct.unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn test_select_distinct_all_columns() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("x", ColumnType::Int),
Column::new("y", ColumnType::Int),
]);
engine.create_table("points", schema).unwrap();
engine
.insert(
"points",
HashMap::from([
("x".to_string(), Value::Int(1)),
("y".to_string(), Value::Int(2)),
]),
)
.unwrap();
engine
.insert(
"points",
HashMap::from([
("x".to_string(), Value::Int(1)),
("y".to_string(), Value::Int(2)),
]),
)
.unwrap();
engine
.insert(
"points",
HashMap::from([
("x".to_string(), Value::Int(1)),
("y".to_string(), Value::Int(3)),
]),
)
.unwrap();
let distinct = engine.select_distinct("points", Condition::True, None);
assert_eq!(distinct.unwrap().len(), 2);
}
#[test]
fn test_select_distinct_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("data", schema).unwrap();
engine
.insert("data", HashMap::from([("val".to_string(), Value::Int(1))]))
.unwrap();
engine
.insert("data", HashMap::from([("val".to_string(), Value::Null)]))
.unwrap();
engine
.insert("data", HashMap::from([("val".to_string(), Value::Null)]))
.unwrap();
engine
.insert("data", HashMap::from([("val".to_string(), Value::Int(1))]))
.unwrap();
let distinct = engine.select_distinct("data", Condition::True, None);
assert_eq!(distinct.unwrap().len(), 2);
}
#[test]
fn test_select_distinct_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("empty", schema).unwrap();
let distinct = engine.select_distinct("empty", Condition::True, None);
assert_eq!(distinct.unwrap().len(), 0);
}
#[test]
fn test_select_distinct_with_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("category", ColumnType::String),
Column::new("value", ColumnType::Int),
]);
engine.create_table("items", schema).unwrap();
engine
.insert(
"items",
HashMap::from([
("category".to_string(), Value::String("A".into())),
("value".to_string(), Value::Int(10)),
]),
)
.unwrap();
engine
.insert(
"items",
HashMap::from([
("category".to_string(), Value::String("A".into())),
("value".to_string(), Value::Int(20)),
]),
)
.unwrap();
engine
.insert(
"items",
HashMap::from([
("category".to_string(), Value::String("B".into())),
("value".to_string(), Value::Int(5)),
]),
)
.unwrap();
let distinct = engine.select_distinct(
"items",
Condition::Gt("value".into(), Value::Int(5)),
Some(&["category".to_string()]),
);
assert_eq!(distinct.unwrap().len(), 1);
}
#[test]
fn test_select_distinct_multiple_columns() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
Column::new("c", ColumnType::Int),
]);
engine.create_table("multi", schema).unwrap();
engine
.insert(
"multi",
HashMap::from([
("a".to_string(), Value::Int(1)),
("b".to_string(), Value::Int(2)),
("c".to_string(), Value::Int(100)),
]),
)
.unwrap();
engine
.insert(
"multi",
HashMap::from([
("a".to_string(), Value::Int(1)),
("b".to_string(), Value::Int(2)),
("c".to_string(), Value::Int(200)),
]),
)
.unwrap();
engine
.insert(
"multi",
HashMap::from([
("a".to_string(), Value::Int(1)),
("b".to_string(), Value::Int(3)),
("c".to_string(), Value::Int(100)),
]),
)
.unwrap();
let distinct = engine.select_distinct(
"multi",
Condition::True,
Some(&["a".to_string(), "b".to_string()]),
);
assert_eq!(distinct.unwrap().len(), 2);
}
#[test]
fn test_select_distinct_nonexistent_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("t", schema).unwrap();
let result = engine.select_distinct("t", Condition::True, Some(&["nonexistent".to_string()]));
assert!(matches!(result, Err(RelationalError::ColumnNotFound(_))));
}
#[test]
fn test_select_distinct_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.select_distinct("nonexistent", Condition::True, None);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_select_grouped_count_all() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("dept", ColumnType::String),
Column::new("name", ColumnType::String),
]);
engine.create_table("employees", schema).unwrap();
engine
.insert(
"employees",
HashMap::from([
("dept".to_string(), Value::String("Engineering".into())),
("name".to_string(), Value::String("Alice".into())),
]),
)
.unwrap();
engine
.insert(
"employees",
HashMap::from([
("dept".to_string(), Value::String("Engineering".into())),
("name".to_string(), Value::String("Bob".into())),
]),
)
.unwrap();
engine
.insert(
"employees",
HashMap::from([
("dept".to_string(), Value::String("Sales".into())),
("name".to_string(), Value::String("Charlie".into())),
]),
)
.unwrap();
let groups = engine.select_grouped(
"employees",
Condition::True,
&["dept".to_string()],
&[AggregateExpr::CountAll],
None,
);
let results = groups.unwrap();
assert_eq!(results.len(), 2);
for group in &results {
let dept = group.get_key("dept").unwrap();
let count = group.get_aggregate("count_all").unwrap();
match dept {
Value::String(s) if s == "Engineering" => {
assert_eq!(count, &AggregateValue::Count(2));
},
Value::String(s) if s == "Sales" => {
assert_eq!(count, &AggregateValue::Count(1));
},
_ => panic!("unexpected dept"),
}
}
}
#[test]
fn test_select_grouped_sum() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("category", ColumnType::String),
Column::new("amount", ColumnType::Int),
]);
engine.create_table("sales", schema).unwrap();
engine
.insert(
"sales",
HashMap::from([
("category".to_string(), Value::String("A".into())),
("amount".to_string(), Value::Int(100)),
]),
)
.unwrap();
engine
.insert(
"sales",
HashMap::from([
("category".to_string(), Value::String("A".into())),
("amount".to_string(), Value::Int(200)),
]),
)
.unwrap();
engine
.insert(
"sales",
HashMap::from([
("category".to_string(), Value::String("B".into())),
("amount".to_string(), Value::Int(50)),
]),
)
.unwrap();
let groups = engine.select_grouped(
"sales",
Condition::True,
&["category".to_string()],
&[AggregateExpr::Sum("amount".to_string())],
None,
);
let results = groups.unwrap();
for group in &results {
let cat = group.get_key("category").unwrap();
let sum = group.get_aggregate("sum_amount").unwrap();
match cat {
Value::String(s) if s == "A" => {
assert_eq!(sum, &AggregateValue::Sum(300.0));
},
Value::String(s) if s == "B" => {
assert_eq!(sum, &AggregateValue::Sum(50.0));
},
_ => panic!("unexpected category"),
}
}
}
#[test]
fn test_select_grouped_avg() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String),
Column::new("score", ColumnType::Float),
]);
engine.create_table("scores", schema).unwrap();
engine
.insert(
"scores",
HashMap::from([
("group".to_string(), Value::String("X".into())),
("score".to_string(), Value::Float(10.0)),
]),
)
.unwrap();
engine
.insert(
"scores",
HashMap::from([
("group".to_string(), Value::String("X".into())),
("score".to_string(), Value::Float(20.0)),
]),
)
.unwrap();
let groups = engine.select_grouped(
"scores",
Condition::True,
&["group".to_string()],
&[AggregateExpr::Avg("score".to_string())],
None,
);
let results = groups.unwrap();
assert_eq!(results.len(), 1);
let avg = results[0].get_aggregate("avg_score").unwrap();
assert_eq!(avg, &AggregateValue::Avg(Some(15.0)));
}
#[test]
fn test_select_grouped_min_max() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("data", schema).unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("G".into())),
("val".to_string(), Value::Int(5)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("G".into())),
("val".to_string(), Value::Int(15)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("G".into())),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
let groups = engine.select_grouped(
"data",
Condition::True,
&["group".to_string()],
&[
AggregateExpr::Min("val".to_string()),
AggregateExpr::Max("val".to_string()),
],
None,
);
let results = groups.unwrap();
let min = results[0].get_aggregate("min_val").unwrap();
let max = results[0].get_aggregate("max_val").unwrap();
assert_eq!(min, &AggregateValue::Min(Some(Value::Int(5))));
assert_eq!(max, &AggregateValue::Max(Some(Value::Int(15))));
}
#[test]
fn test_select_grouped_count_column_excludes_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String),
Column::new("val", ColumnType::Int).nullable(),
]);
engine.create_table("data", schema).unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("G".into())),
("val".to_string(), Value::Int(1)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("G".into())),
("val".to_string(), Value::Null),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("G".into())),
("val".to_string(), Value::Int(2)),
]),
)
.unwrap();
let groups = engine.select_grouped(
"data",
Condition::True,
&["group".to_string()],
&[
AggregateExpr::CountAll,
AggregateExpr::Count("val".to_string()),
],
None,
);
let results = groups.unwrap();
let count_all = results[0].get_aggregate("count_all").unwrap();
let count_val = results[0].get_aggregate("count_val").unwrap();
assert_eq!(count_all, &AggregateValue::Count(3));
assert_eq!(count_val, &AggregateValue::Count(2));
}
#[test]
fn test_select_grouped_multiple_group_columns() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("region", ColumnType::String),
Column::new("category", ColumnType::String),
Column::new("sales", ColumnType::Int),
]);
engine.create_table("data", schema).unwrap();
engine
.insert(
"data",
HashMap::from([
("region".to_string(), Value::String("East".into())),
("category".to_string(), Value::String("A".into())),
("sales".to_string(), Value::Int(100)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("region".to_string(), Value::String("East".into())),
("category".to_string(), Value::String("A".into())),
("sales".to_string(), Value::Int(150)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("region".to_string(), Value::String("East".into())),
("category".to_string(), Value::String("B".into())),
("sales".to_string(), Value::Int(200)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("region".to_string(), Value::String("West".into())),
("category".to_string(), Value::String("A".into())),
("sales".to_string(), Value::Int(300)),
]),
)
.unwrap();
let groups = engine.select_grouped(
"data",
Condition::True,
&["region".to_string(), "category".to_string()],
&[AggregateExpr::Sum("sales".to_string())],
None,
);
let results = groups.unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_select_grouped_null_in_group_key() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String).nullable(),
Column::new("val", ColumnType::Int),
]);
engine.create_table("data", schema).unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(1)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::Null),
("val".to_string(), Value::Int(2)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::Null),
("val".to_string(), Value::Int(3)),
]),
)
.unwrap();
let groups = engine.select_grouped(
"data",
Condition::True,
&["group".to_string()],
&[AggregateExpr::CountAll],
None,
);
let results = groups.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_select_grouped_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("empty", schema).unwrap();
let groups = engine.select_grouped(
"empty",
Condition::True,
&["group".to_string()],
&[AggregateExpr::CountAll],
None,
);
assert_eq!(groups.unwrap().len(), 0);
}
#[test]
fn test_select_grouped_nonexistent_group_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("t", schema).unwrap();
let result = engine.select_grouped(
"t",
Condition::True,
&["nonexistent".to_string()],
&[AggregateExpr::CountAll],
None,
);
assert!(matches!(result, Err(RelationalError::ColumnNotFound(_))));
}
#[test]
fn test_select_grouped_nonexistent_aggregate_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("t", schema).unwrap();
let result = engine.select_grouped(
"t",
Condition::True,
&["group".to_string()],
&[AggregateExpr::Sum("nonexistent".to_string())],
None,
);
assert!(matches!(result, Err(RelationalError::ColumnNotFound(_))));
}
#[test]
fn test_select_grouped_having_gt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("data", schema).unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(1)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(2)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("B".into())),
("val".to_string(), Value::Int(1)),
]),
)
.unwrap();
let groups = engine.select_grouped(
"data",
Condition::True,
&["group".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Gt(AggregateRef::CountAll, Value::Int(1))),
);
let results = groups.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(
results[0].get_key("group"),
Some(&Value::String("A".into()))
);
}
#[test]
fn test_select_grouped_having_filters_all() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("data", schema).unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(1)),
]),
)
.unwrap();
let groups = engine.select_grouped(
"data",
Condition::True,
&["group".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Gt(AggregateRef::CountAll, Value::Int(100))),
);
assert_eq!(groups.unwrap().len(), 0);
}
#[test]
fn test_select_grouped_having_and() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("data", schema).unwrap();
for _ in 0..5 {
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
}
for _ in 0..3 {
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("B".into())),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
}
for _ in 0..7 {
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("C".into())),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
}
let groups = engine.select_grouped(
"data",
Condition::True,
&["group".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::And(
Box::new(HavingCondition::Gt(AggregateRef::CountAll, Value::Int(3))),
Box::new(HavingCondition::Lt(AggregateRef::CountAll, Value::Int(6))),
)),
);
let results = groups.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(
results[0].get_key("group"),
Some(&Value::String("A".into()))
);
}
#[test]
fn test_select_grouped_having_or() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("data", schema).unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
for _ in 0..3 {
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("B".into())),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
}
for _ in 0..10 {
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("C".into())),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
}
let groups = engine.select_grouped(
"data",
Condition::True,
&["group".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Or(
Box::new(HavingCondition::Eq(AggregateRef::CountAll, Value::Int(1))),
Box::new(HavingCondition::Eq(AggregateRef::CountAll, Value::Int(10))),
)),
);
let results = groups.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_aggregate_expr_result_name() {
assert_eq!(AggregateExpr::CountAll.result_name(), "count_all");
assert_eq!(
AggregateExpr::Count("x".to_string()).result_name(),
"count_x"
);
assert_eq!(AggregateExpr::Sum("y".to_string()).result_name(), "sum_y");
assert_eq!(AggregateExpr::Avg("z".to_string()).result_name(), "avg_z");
assert_eq!(AggregateExpr::Min("a".to_string()).result_name(), "min_a");
assert_eq!(AggregateExpr::Max("b".to_string()).result_name(), "max_b");
}
#[test]
fn test_aggregate_ref_result_name() {
assert_eq!(AggregateRef::CountAll.result_name(), "count_all");
assert_eq!(
AggregateRef::Count("x".to_string()).result_name(),
"count_x"
);
assert_eq!(AggregateRef::Sum("y".to_string()).result_name(), "sum_y");
assert_eq!(AggregateRef::Avg("z".to_string()).result_name(), "avg_z");
assert_eq!(AggregateRef::Min("a".to_string()).result_name(), "min_a");
assert_eq!(AggregateRef::Max("b".to_string()).result_name(), "max_b");
}
#[test]
fn test_aggregate_value_to_value() {
assert_eq!(AggregateValue::Count(42).to_value(), Value::Int(42));
assert_eq!(AggregateValue::Sum(3.15).to_value(), Value::Float(3.15));
assert_eq!(AggregateValue::Avg(Some(2.5)).to_value(), Value::Float(2.5));
assert_eq!(AggregateValue::Avg(None).to_value(), Value::Null);
assert_eq!(
AggregateValue::Min(Some(Value::Int(1))).to_value(),
Value::Int(1)
);
assert_eq!(AggregateValue::Min(None).to_value(), Value::Null);
assert_eq!(
AggregateValue::Max(Some(Value::String("hi".into()))).to_value(),
Value::String("hi".into())
);
assert_eq!(AggregateValue::Max(None).to_value(), Value::Null);
}
#[test]
fn test_grouped_row_accessors() {
let row = GroupedRow {
group_key: vec![
("a".to_string(), Value::Int(1)),
("b".to_string(), Value::String("x".into())),
],
aggregates: vec![
("count_all".to_string(), AggregateValue::Count(5)),
("sum_c".to_string(), AggregateValue::Sum(100.0)),
],
};
assert_eq!(row.get_key("a"), Some(&Value::Int(1)));
assert_eq!(row.get_key("b"), Some(&Value::String("x".into())));
assert_eq!(row.get_key("nonexistent"), None);
assert_eq!(
row.get_aggregate("count_all"),
Some(&AggregateValue::Count(5))
);
assert_eq!(
row.get_aggregate("sum_c"),
Some(&AggregateValue::Sum(100.0))
);
assert_eq!(row.get_aggregate("nonexistent"), None);
}
#[test]
fn test_hashable_value_equality() {
let a = HashableValue(Value::Int(42));
let b = HashableValue(Value::Int(42));
let c = HashableValue(Value::Int(43));
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn test_hashable_value_hash_consistency() {
use std::collections::hash_map::DefaultHasher;
let val1 = HashableValue(Value::String("test".into()));
let val2 = HashableValue(Value::String("test".into()));
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
val1.hash(&mut hasher1);
val2.hash(&mut hasher2);
assert_eq!(hasher1.finish(), hasher2.finish());
}
#[test]
fn test_select_grouped_with_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("data", schema).unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(5)),
]),
)
.unwrap();
engine
.insert(
"data",
HashMap::from([
("group".to_string(), Value::String("B".into())),
("val".to_string(), Value::Int(20)),
]),
)
.unwrap();
let groups = engine.select_grouped(
"data",
Condition::Gt("val".into(), Value::Int(5)),
&["group".to_string()],
&[AggregateExpr::CountAll],
None,
);
let results = groups.unwrap();
assert_eq!(results.len(), 2);
for group in &results {
let count = group.get_aggregate("count_all").unwrap();
assert_eq!(count, &AggregateValue::Count(1));
}
}
#[test]
fn test_select_grouped_large_dataset() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("category", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("data", schema).unwrap();
for i in 0..1000 {
engine
.insert(
"data",
HashMap::from([
("category".to_string(), Value::Int(i % 10)),
("value".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let groups = engine.select_grouped(
"data",
Condition::True,
&["category".to_string()],
&[
AggregateExpr::CountAll,
AggregateExpr::Sum("value".to_string()),
],
None,
);
let results = groups.unwrap();
assert_eq!(results.len(), 10);
for group in &results {
let count = group.get_aggregate("count_all").unwrap();
assert_eq!(count, &AggregateValue::Count(100));
}
}
#[test]
fn test_add_column_to_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.add_column("test", Column::new("name", ColumnType::String))
.unwrap();
let schema = engine.get_schema("test").unwrap();
assert_eq!(schema.columns.len(), 2);
assert!(schema.get_column("name").is_some());
}
#[test]
fn test_add_nullable_column_to_populated_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
engine
.add_column("test", Column::new("name", ColumnType::String).nullable())
.unwrap();
let schema = engine.get_schema("test").unwrap();
assert_eq!(schema.columns.len(), 2);
}
#[test]
fn test_add_non_nullable_column_to_populated_table_fails() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
let result = engine.add_column("test", Column::new("name", ColumnType::String));
assert!(matches!(
result,
Err(RelationalError::CannotAddColumn { .. })
));
}
#[test]
fn test_add_column_already_exists() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let result = engine.add_column("test", Column::new("id", ColumnType::Int));
assert!(matches!(
result,
Err(RelationalError::ColumnAlreadyExists { .. })
));
}
#[test]
fn test_drop_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("test", schema).unwrap();
engine.drop_column("test", "name").unwrap();
let schema = engine.get_schema("test").unwrap();
assert_eq!(schema.columns.len(), 1);
assert!(schema.get_column("name").is_none());
}
#[test]
fn test_drop_column_not_found() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let result = engine.drop_column("test", "nonexistent");
assert!(matches!(result, Err(RelationalError::ColumnNotFound(_))));
}
#[test]
fn test_rename_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("old_name", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.rename_column("test", "old_name", "new_name")
.unwrap();
let schema = engine.get_schema("test").unwrap();
assert!(schema.get_column("old_name").is_none());
assert!(schema.get_column("new_name").is_some());
}
#[test]
fn test_rename_column_not_found() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let result = engine.rename_column("test", "nonexistent", "new_name");
assert!(matches!(result, Err(RelationalError::ColumnNotFound(_))));
}
#[test]
fn test_rename_column_target_exists() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("col1", ColumnType::Int),
Column::new("col2", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
let result = engine.rename_column("test", "col1", "col2");
assert!(matches!(
result,
Err(RelationalError::ColumnAlreadyExists { .. })
));
}
#[test]
fn test_add_primary_key_constraint() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.add_constraint(
"test",
Constraint::primary_key("pk_test", vec!["id".to_string()]),
)
.unwrap();
let constraints = engine.get_constraints("test").unwrap();
assert_eq!(constraints.len(), 1);
assert!(matches!(&constraints[0], Constraint::PrimaryKey { name, .. } if name == "pk_test"));
}
#[test]
fn test_primary_key_violation_on_existing_data() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
let result = engine.add_constraint(
"test",
Constraint::primary_key("pk_test", vec!["id".to_string()]),
);
assert!(matches!(
result,
Err(RelationalError::PrimaryKeyViolation { .. })
));
}
#[test]
fn test_add_unique_constraint() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("email", ColumnType::String)]);
engine.create_table("users", schema).unwrap();
engine
.add_constraint(
"users",
Constraint::unique("uq_email", vec!["email".to_string()]),
)
.unwrap();
let constraints = engine.get_constraints("users").unwrap();
assert_eq!(constraints.len(), 1);
}
#[test]
fn test_unique_violation_on_existing_data() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("email", ColumnType::String)]);
engine.create_table("users", schema).unwrap();
engine
.insert(
"users",
HashMap::from([("email".to_string(), Value::String("a@b.com".into()))]),
)
.unwrap();
engine
.insert(
"users",
HashMap::from([("email".to_string(), Value::String("a@b.com".into()))]),
)
.unwrap();
let result = engine.add_constraint(
"users",
Constraint::unique("uq_email", vec!["email".to_string()]),
);
assert!(matches!(
result,
Err(RelationalError::UniqueViolation { .. })
));
}
#[test]
fn test_add_foreign_key_constraint() {
let engine = RelationalEngine::new();
let parent_schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("parent", parent_schema).unwrap();
engine
.insert("parent", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
let child_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int),
]);
engine.create_table("child", child_schema).unwrap();
let fk = ForeignKeyConstraint::new(
"fk_parent",
vec!["parent_id".to_string()],
"parent",
vec!["id".to_string()],
);
engine
.add_constraint("child", Constraint::foreign_key(fk))
.unwrap();
let constraints = engine.get_constraints("child").unwrap();
assert_eq!(constraints.len(), 1);
}
#[test]
fn test_foreign_key_violation_on_existing_data() {
let engine = RelationalEngine::new();
let parent_schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("parent", parent_schema).unwrap();
let child_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int),
]);
engine.create_table("child", child_schema).unwrap();
engine
.insert(
"child",
HashMap::from([
("id".to_string(), Value::Int(1)),
("parent_id".to_string(), Value::Int(999)),
]),
)
.unwrap();
let fk = ForeignKeyConstraint::new(
"fk_parent",
vec!["parent_id".to_string()],
"parent",
vec!["id".to_string()],
);
let result = engine.add_constraint("child", Constraint::foreign_key(fk));
assert!(matches!(
result,
Err(RelationalError::ForeignKeyViolation { .. })
));
}
#[test]
fn test_foreign_key_null_allowed() {
let engine = RelationalEngine::new();
let parent_schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("parent", parent_schema).unwrap();
let child_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int).nullable(),
]);
engine.create_table("child", child_schema).unwrap();
engine
.insert(
"child",
HashMap::from([
("id".to_string(), Value::Int(1)),
("parent_id".to_string(), Value::Null),
]),
)
.unwrap();
let fk = ForeignKeyConstraint::new(
"fk_parent",
vec!["parent_id".to_string()],
"parent",
vec!["id".to_string()],
);
engine
.add_constraint("child", Constraint::foreign_key(fk))
.unwrap();
let constraints = engine.get_constraints("child").unwrap();
assert_eq!(constraints.len(), 1);
}
#[test]
fn test_drop_constraint() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.add_constraint(
"test",
Constraint::primary_key("pk_test", vec!["id".to_string()]),
)
.unwrap();
engine.drop_constraint("test", "pk_test").unwrap();
let constraints = engine.get_constraints("test").unwrap();
assert!(constraints.is_empty());
}
#[test]
fn test_drop_constraint_not_found() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let result = engine.drop_constraint("test", "nonexistent");
assert!(matches!(
result,
Err(RelationalError::ConstraintNotFound { .. })
));
}
#[test]
fn test_constraint_already_exists() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.add_constraint(
"test",
Constraint::primary_key("pk_test", vec!["id".to_string()]),
)
.unwrap();
let result = engine.add_constraint(
"test",
Constraint::unique("pk_test", vec!["id".to_string()]),
);
assert!(matches!(
result,
Err(RelationalError::ConstraintAlreadyExists { .. })
));
}
#[test]
fn test_drop_column_with_constraint_fails() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.add_constraint(
"test",
Constraint::primary_key("pk_test", vec!["id".to_string()]),
)
.unwrap();
let result = engine.drop_column("test", "id");
assert!(matches!(
result,
Err(RelationalError::ColumnHasConstraint { .. })
));
}
#[test]
fn test_composite_primary_key() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("order_id", ColumnType::Int),
Column::new("product_id", ColumnType::Int),
Column::new("quantity", ColumnType::Int),
]);
engine.create_table("order_items", schema).unwrap();
engine
.insert(
"order_items",
HashMap::from([
("order_id".to_string(), Value::Int(1)),
("product_id".to_string(), Value::Int(100)),
("quantity".to_string(), Value::Int(5)),
]),
)
.unwrap();
engine
.insert(
"order_items",
HashMap::from([
("order_id".to_string(), Value::Int(1)),
("product_id".to_string(), Value::Int(200)),
("quantity".to_string(), Value::Int(3)),
]),
)
.unwrap();
engine
.add_constraint(
"order_items",
Constraint::primary_key(
"pk_order_items",
vec!["order_id".to_string(), "product_id".to_string()],
),
)
.unwrap();
let constraints = engine.get_constraints("order_items").unwrap();
assert_eq!(constraints.len(), 1);
}
#[test]
fn test_composite_primary_key_violation() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("order_id", ColumnType::Int),
Column::new("product_id", ColumnType::Int),
]);
engine.create_table("order_items", schema).unwrap();
engine
.insert(
"order_items",
HashMap::from([
("order_id".to_string(), Value::Int(1)),
("product_id".to_string(), Value::Int(100)),
]),
)
.unwrap();
engine
.insert(
"order_items",
HashMap::from([
("order_id".to_string(), Value::Int(1)),
("product_id".to_string(), Value::Int(100)),
]),
)
.unwrap();
let result = engine.add_constraint(
"order_items",
Constraint::primary_key(
"pk_order_items",
vec!["order_id".to_string(), "product_id".to_string()],
),
);
assert!(matches!(
result,
Err(RelationalError::PrimaryKeyViolation { .. })
));
}
#[test]
fn test_create_table_with_constraints() {
let engine = RelationalEngine::new();
let schema = Schema::with_constraints(
vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
],
vec![Constraint::primary_key("pk_users", vec!["id".to_string()])],
);
engine.create_table("users", schema).unwrap();
let constraints = engine.get_constraints("users").unwrap();
assert_eq!(constraints.len(), 1);
}
#[test]
fn test_rename_column_updates_constraints() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.add_constraint(
"test",
Constraint::primary_key("pk_test", vec!["id".to_string()]),
)
.unwrap();
engine.rename_column("test", "id", "new_id").unwrap();
let constraints = engine.get_constraints("test").unwrap();
match &constraints[0] {
Constraint::PrimaryKey { columns, .. } => {
assert_eq!(columns, &vec!["new_id".to_string()]);
},
_ => panic!("Expected PrimaryKey constraint"),
}
}
#[test]
fn test_referential_action_default() {
let action = ReferentialAction::default();
assert_eq!(action, ReferentialAction::Restrict);
}
#[test]
fn test_foreign_key_builder() {
let fk = ForeignKeyConstraint::new(
"fk_test",
vec!["col1".to_string()],
"other_table",
vec!["id".to_string()],
)
.on_delete(ReferentialAction::Cascade)
.on_update(ReferentialAction::SetNull);
assert_eq!(fk.on_delete, ReferentialAction::Cascade);
assert_eq!(fk.on_update, ReferentialAction::SetNull);
}
#[test]
fn test_constraint_name() {
let pk = Constraint::primary_key("pk_test", vec!["id".to_string()]);
assert_eq!(pk.name(), "pk_test");
let uq = Constraint::unique("uq_email", vec!["email".to_string()]);
assert_eq!(uq.name(), "uq_email");
let fk = ForeignKeyConstraint::new(
"fk_parent",
vec!["parent_id".to_string()],
"parent",
vec!["id".to_string()],
);
let fk_constraint = Constraint::foreign_key(fk);
assert_eq!(fk_constraint.name(), "fk_parent");
let nn = Constraint::not_null("nn_col", "col");
assert_eq!(nn.name(), "nn_col");
}
#[test]
fn test_error_display_constraint_errors() {
let err = RelationalError::PrimaryKeyViolation {
table: "users".to_string(),
columns: vec!["id".to_string()],
value: "1".to_string(),
};
assert!(err.to_string().contains("Primary key violation"));
let err = RelationalError::UniqueViolation {
constraint_name: "uq_email".to_string(),
columns: vec!["email".to_string()],
value: "a@b.com".to_string(),
};
assert!(err.to_string().contains("Unique constraint"));
let err = RelationalError::ForeignKeyViolation {
constraint_name: "fk_parent".to_string(),
table: "child".to_string(),
referenced_table: "parent".to_string(),
};
assert!(err.to_string().contains("Foreign key constraint"));
let err = RelationalError::ConstraintNotFound {
table: "test".to_string(),
constraint_name: "pk_test".to_string(),
};
assert!(err.to_string().contains("not found"));
let err = RelationalError::ColumnAlreadyExists {
table: "test".to_string(),
column: "id".to_string(),
};
assert!(err.to_string().contains("already exists"));
let err = RelationalError::CannotAddColumn {
column: "col".to_string(),
reason: "table has rows".to_string(),
};
assert!(err.to_string().contains("Cannot add column"));
}
#[test]
fn test_hash_index_survives_recovery() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let wal_path = dir.path().join("hash_idx.wal");
let config = tensor_store::WalConfig::default();
{
let engine = RelationalEngine::open_durable(&wal_path, config.clone()).unwrap();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("email", ColumnType::String),
]);
engine.create_table("users", schema).unwrap();
engine.create_index("users", "email").unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert(
"email".to_string(),
Value::String(format!("user{i}@test.com")),
);
engine.insert("users", values).unwrap();
}
assert!(engine.has_index("users", "email"));
}
let recovered = RelationalEngine::recover(&wal_path, &config, None).unwrap();
assert!(recovered.has_index("users", "email"));
}
#[test]
fn test_hash_index_queries_work_after_recovery() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let wal_path = dir.path().join("hash_query.wal");
let config = tensor_store::WalConfig::default();
{
let engine = RelationalEngine::open_durable(&wal_path, config.clone()).unwrap();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("email", ColumnType::String),
]);
engine.create_table("users", schema).unwrap();
engine.create_index("users", "email").unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert(
"email".to_string(),
Value::String(format!("user{i}@test.com")),
);
engine.insert("users", values).unwrap();
}
}
let recovered = RelationalEngine::recover(&wal_path, &config, None).unwrap();
assert!(recovered.get_schema("users").is_ok());
assert!(recovered.has_index("users", "email"));
let index_keys = recovered.store().scan("_idx:users:email:");
assert!(
!index_keys.is_empty(),
"index entries should survive recovery"
);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert(
"email".to_string(),
Value::String(format!("user{i}@test.com")),
);
recovered.insert("users", values).unwrap();
}
let rows = recovered
.select(
"users",
Condition::Eq(
"email".to_string(),
Value::String("user3@test.com".to_string()),
),
)
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("id"), Some(&Value::Int(3)));
}
#[test]
fn test_mixed_hash_and_btree_indexes_recover() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let wal_path = dir.path().join("mixed_idx.wal");
let config = tensor_store::WalConfig::default();
{
let engine = RelationalEngine::open_durable(&wal_path, config.clone()).unwrap();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("people", schema).unwrap();
engine.create_index("people", "name").unwrap();
engine.create_btree_index("people", "age").unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String(format!("Person{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("people", values).unwrap();
}
}
let recovered = RelationalEngine::recover(&wal_path, &config, None).unwrap();
assert!(recovered.has_index("people", "name"));
assert!(recovered.has_btree_index("people", "age"));
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String(format!("Person{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
recovered.insert("people", values).unwrap();
}
let by_name = recovered
.select(
"people",
Condition::Eq("name".to_string(), Value::String("Person5".to_string())),
)
.unwrap();
assert_eq!(by_name.len(), 1);
let by_age = recovered
.select("people", Condition::Ge("age".to_string(), Value::Int(25)))
.unwrap();
assert_eq!(by_age.len(), 5);
}
#[test]
fn test_recovery_with_no_indexes_is_noop() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let wal_path = dir.path().join("no_idx.wal");
let config = tensor_store::WalConfig::default();
{
let engine = RelationalEngine::open_durable(&wal_path, config.clone()).unwrap();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("simple", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("simple", values).unwrap();
}
}
let recovered = RelationalEngine::recover(&wal_path, &config, None).unwrap();
let schema = recovered.get_schema("simple").unwrap();
assert_eq!(schema.columns.len(), 1);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
recovered.insert("simple", values).unwrap();
}
let rows = recovered.select("simple", Condition::True).unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_hash_index_get_indexed_columns_after_recovery() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let wal_path = dir.path().join("indexed_cols.wal");
let config = tensor_store::WalConfig::default();
{
let engine = RelationalEngine::open_durable(&wal_path, config.clone()).unwrap();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("email", ColumnType::String),
Column::new("name", ColumnType::String),
]);
engine.create_table("users", schema).unwrap();
engine.create_index("users", "email").unwrap();
engine.create_index("users", "name").unwrap();
engine
.insert(
"users",
HashMap::from([
("id".to_string(), Value::Int(1)),
("email".to_string(), Value::String("a@b.com".to_string())),
("name".to_string(), Value::String("Alice".to_string())),
]),
)
.unwrap();
}
let recovered = RelationalEngine::recover(&wal_path, &config, None).unwrap();
let indexed = recovered.get_indexed_columns("users");
assert!(indexed.contains(&"email".to_string()));
assert!(indexed.contains(&"name".to_string()));
}
#[test]
fn test_basic_table_metadata_recovery() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let wal_path = dir.path().join("basic_recovery.wal");
let config = tensor_store::WalConfig::default();
{
let engine = RelationalEngine::open_durable(&wal_path, config.clone()).unwrap();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_table", schema).unwrap();
assert!(engine.get_schema("test_table").is_ok());
}
let recovered = RelationalEngine::recover(&wal_path, &config, None).unwrap();
let table_keys = recovered.store().scan("_meta:table:");
eprintln!("Recovered table keys: {table_keys:?}");
let schema_result = recovered.get_schema("test_table");
assert!(
schema_result.is_ok(),
"get_schema failed: {:?}",
schema_result.err()
);
}
#[test]
fn test_select_with_limit_basic() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..100 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let rows = engine
.select_with_limit("test", Condition::True, 10, 0)
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_select_with_limit_and_offset() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..100 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let rows = engine
.select_with_limit("test", Condition::True, 10, 5)
.unwrap();
assert_eq!(rows.len(), 10);
let ids: Vec<u64> = rows.iter().map(|r| r.id).collect();
assert_eq!(ids, vec![6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
}
#[test]
fn test_select_with_limit_exceeds_rows() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let rows = engine
.select_with_limit("test", Condition::True, 100, 0)
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_select_with_limit_zero() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let rows = engine
.select_with_limit("test", Condition::True, 0, 0)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_select_with_offset_beyond_rows() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let rows = engine
.select_with_limit("test", Condition::True, 10, 20)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_select_iter_with_limit() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..50 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let options = CursorOptions::new().with_limit(10);
let cursor = engine
.select_iter("test", Condition::True, options)
.unwrap();
assert_eq!(cursor.total_rows(), 10);
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_select_iter_with_limit_and_offset() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..100 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let options = CursorOptions::new().with_limit(5).with_offset(10);
let cursor = engine
.select_iter("test", Condition::True, options)
.unwrap();
assert_eq!(cursor.total_rows(), 5);
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_select_with_limit_uses_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("test", schema).unwrap();
engine.create_index("test", "name").unwrap();
for i in 0..50 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String("Alice".to_string()));
engine.insert("test", values).unwrap();
}
let rows = engine
.select_with_limit(
"test",
Condition::Eq("name".to_string(), Value::String("Alice".to_string())),
10,
0,
)
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_select_with_limit_with_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("age", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
for i in 0..100 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("age".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let rows = engine
.select_with_limit(
"test",
Condition::Ge("age".to_string(), Value::Int(50)),
10,
0,
)
.unwrap();
assert_eq!(rows.len(), 10);
for row in &rows {
let age = match row.get("age") {
Some(Value::Int(a)) => *a,
_ => panic!("expected int"),
};
assert!(age >= 50);
}
}
#[test]
fn test_select_with_limit_pagination() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..50 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("test", values).unwrap();
}
let mut all_ids: Vec<u64> = Vec::new();
for page in 0..5 {
let rows = engine
.select_with_limit("test", Condition::True, 10, page * 10)
.unwrap();
assert_eq!(rows.len(), 10, "page {page} should have 10 rows");
all_ids.extend(rows.iter().map(|r| r.id));
}
let unique_ids: HashSet<u64> = all_ids.into_iter().collect();
assert_eq!(unique_ids.len(), 50);
}
#[test]
fn test_select_with_limit_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let rows = engine
.select_with_limit("test", Condition::True, 10, 0)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_select_with_limit_nonexistent_table() {
let engine = RelationalEngine::new();
let result = engine.select_with_limit("nonexistent", Condition::True, 10, 0);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TableNotFound(_)
));
}
#[test]
fn test_slow_query_warning_insert() {
let config = RelationalConfig::new().with_slow_query_threshold_ms(0);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
engine.insert("test", values).unwrap();
assert_eq!(engine.config().slow_query_threshold_ms, 0);
}
#[test]
fn test_slow_query_warning_batch_insert() {
let config = RelationalConfig::new().with_slow_query_threshold_ms(0);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let rows: Vec<HashMap<String, Value>> = (0..100)
.map(|i| HashMap::from([("id".to_string(), Value::Int(i))]))
.collect();
let result = engine.batch_insert("test", rows).unwrap();
assert_eq!(result.len(), 100);
}
#[test]
fn test_slow_query_warning_select_iter() {
let config = RelationalConfig::new().with_slow_query_threshold_ms(0);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(i))]))
.unwrap();
}
let cursor = engine
.select_iter("test", Condition::True, CursorOptions::default())
.unwrap();
assert_eq!(cursor.total_rows(), 10);
}
#[test]
fn test_slow_query_warning_select_distinct() {
let config = RelationalConfig::new().with_slow_query_threshold_ms(0);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("category", ColumnType::String),
Column::new("value", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test",
HashMap::from([
("category".to_string(), Value::String("A".into())),
("value".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let result = engine
.select_distinct("test", Condition::True, Some(&["category".to_string()]))
.unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_slow_query_warning_select_grouped() {
let config = RelationalConfig::new().with_slow_query_threshold_ms(0);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("category", ColumnType::String),
Column::new("amount", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test",
HashMap::from([
(
"category".to_string(),
Value::String(if i % 2 == 0 { "A" } else { "B" }.into()),
),
("amount".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let result = engine
.select_grouped(
"test",
Condition::True,
&["category".to_string()],
&[AggregateExpr::CountAll],
None,
)
.unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_slow_query_no_warning_under_threshold() {
let config = RelationalConfig::new().with_slow_query_threshold_ms(10_000);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
assert_eq!(engine.config().slow_query_threshold_ms, 10_000);
}
#[test]
fn test_schema_constraints_accessor() {
let mut schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
assert!(schema.constraints().is_empty());
schema.add_constraint(Constraint::PrimaryKey {
columns: vec!["id".to_string()],
name: "pk_id".to_string(),
});
assert_eq!(schema.constraints().len(), 1);
}
#[test]
fn test_schema_add_constraint() {
let mut schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
schema.add_constraint(Constraint::Unique {
columns: vec!["name".to_string()],
name: "uq_name".to_string(),
});
assert_eq!(schema.constraints().len(), 1);
match &schema.constraints()[0] {
Constraint::Unique { columns, name } => {
assert_eq!(columns, &vec!["name".to_string()]);
assert_eq!(name, "uq_name");
},
_ => panic!("expected Unique constraint"),
}
}
#[test]
fn test_having_condition_ge() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("category", ColumnType::String),
Column::new("amount", ColumnType::Int),
]);
engine.create_table("sales", schema).unwrap();
for i in 0..20 {
engine
.insert(
"sales",
HashMap::from([
("category".to_string(), Value::String("A".into())),
("amount".to_string(), Value::Int(i * 10)),
]),
)
.unwrap();
}
let result = engine
.select_grouped(
"sales",
Condition::True,
&["category".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Ge(AggregateRef::CountAll, Value::Int(20))),
)
.unwrap();
assert_eq!(result.len(), 1);
let result = engine
.select_grouped(
"sales",
Condition::True,
&["category".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Ge(AggregateRef::CountAll, Value::Int(21))),
)
.unwrap();
assert!(result.is_empty());
}
#[test]
fn test_having_condition_le() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("category", ColumnType::String),
Column::new("amount", ColumnType::Int),
]);
engine.create_table("sales", schema).unwrap();
for i in 0..5 {
engine
.insert(
"sales",
HashMap::from([
("category".to_string(), Value::String("B".into())),
("amount".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let result = engine
.select_grouped(
"sales",
Condition::True,
&["category".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Le(AggregateRef::CountAll, Value::Int(5))),
)
.unwrap();
assert_eq!(result.len(), 1);
let result = engine
.select_grouped(
"sales",
Condition::True,
&["category".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Le(AggregateRef::CountAll, Value::Int(4))),
)
.unwrap();
assert!(result.is_empty());
}
#[test]
fn test_having_condition_ne() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("category", ColumnType::String),
Column::new("amount", ColumnType::Int),
]);
engine.create_table("sales", schema).unwrap();
for cat in &["A", "B"] {
for i in 0..3 {
engine
.insert(
"sales",
HashMap::from([
("category".to_string(), Value::String((*cat).into())),
("amount".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
}
let result = engine
.select_grouped(
"sales",
Condition::True,
&["category".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Ne(AggregateRef::CountAll, Value::Int(3))),
)
.unwrap();
assert!(result.is_empty());
let result = engine
.select_grouped(
"sales",
Condition::True,
&["category".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Ne(AggregateRef::CountAll, Value::Int(5))),
)
.unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_error_display_schema_corrupted() {
let err = RelationalError::SchemaCorrupted {
table: "users".to_string(),
reason: "missing column metadata".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("Schema corrupted"));
assert!(msg.contains("users"));
assert!(msg.contains("missing column metadata"));
}
#[test]
fn test_error_display_foreign_key_restrict() {
let err = RelationalError::ForeignKeyRestrict {
constraint_name: "fk_order_user".to_string(),
table: "users".to_string(),
referencing_table: "orders".to_string(),
row_count: 5,
};
let msg = format!("{err}");
assert!(msg.contains("Foreign key constraint"));
assert!(msg.contains("fk_order_user"));
assert!(msg.contains("5 row(s)"));
}
#[test]
fn test_error_display_constraint_not_found() {
let err = RelationalError::ConstraintNotFound {
table: "products".to_string(),
constraint_name: "uq_sku".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("Constraint"));
assert!(msg.contains("uq_sku"));
assert!(msg.contains("not found"));
}
#[test]
fn test_error_display_constraint_already_exists() {
let err = RelationalError::ConstraintAlreadyExists {
table: "products".to_string(),
constraint_name: "pk_id".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("already exists"));
assert!(msg.contains("pk_id"));
}
#[test]
fn test_error_display_column_has_constraint() {
let err = RelationalError::ColumnHasConstraint {
column: "user_id".to_string(),
constraint_name: "fk_user".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("Column"));
assert!(msg.contains("user_id"));
assert!(msg.contains("fk_user"));
}
#[test]
fn test_ordered_key_negative_float_from_sortable() {
let parsed = OrderedKey::from_sortable_key("f3c5c28f5c28f5c2"); assert!(parsed.is_some());
if let Some(OrderedKey::Float(_)) = parsed {
} else {
panic!("expected Float");
}
}
#[test]
fn test_ordered_key_bytes_from_sortable() {
let parsed = OrderedKey::from_sortable_key("ydeadbeef");
assert!(parsed.is_some());
if let Some(OrderedKey::Bytes(b)) = parsed {
assert_eq!(b, vec![0xDE, 0xAD, 0xBE, 0xEF]);
} else {
panic!("expected Bytes");
}
}
#[test]
fn test_ordered_key_json_from_sortable() {
let parsed = OrderedKey::from_sortable_key(r#"j{"key": "value"}"#);
assert!(parsed.is_some());
if let Some(OrderedKey::Json(j)) = parsed {
assert_eq!(j, r#"{"key": "value"}"#);
} else {
panic!("expected Json");
}
}
#[test]
fn test_ordered_key_empty_bytes() {
let parsed = OrderedKey::from_sortable_key("y");
assert!(parsed.is_some());
if let Some(OrderedKey::Bytes(b)) = parsed {
assert!(b.is_empty());
} else {
panic!("expected empty Bytes");
}
}
#[test]
fn test_ordered_key_empty_json() {
let parsed = OrderedKey::from_sortable_key("j");
assert!(parsed.is_some());
if let Some(OrderedKey::Json(j)) = parsed {
assert!(j.is_empty());
} else {
panic!("expected empty Json");
}
}
#[test]
fn test_ordered_key_invalid_prefix() {
let parsed = OrderedKey::from_sortable_key("xinvalid");
assert!(parsed.is_none());
}
#[test]
fn test_ordered_key_invalid_bool() {
let parsed = OrderedKey::from_sortable_key("b2");
assert!(parsed.is_none());
}
#[test]
fn test_ordered_key_short_string() {
let parsed = OrderedKey::from_sortable_key("x");
assert!(parsed.is_none());
}
#[test]
fn test_load_column_data_with_nulls_float() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float).nullable()]);
engine.create_table("floats", schema).unwrap();
engine
.insert(
"floats",
HashMap::from([("val".to_string(), Value::Float(1.5))]),
)
.unwrap();
engine
.insert("floats", HashMap::from([("val".to_string(), Value::Null)]))
.unwrap();
engine
.insert(
"floats",
HashMap::from([("val".to_string(), Value::Float(2.5))]),
)
.unwrap();
let col_data = engine.load_column_data("floats", "val").unwrap();
assert_eq!(col_data.row_ids.len(), 3);
}
#[test]
fn test_load_column_data_with_nulls_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("ints", schema).unwrap();
engine
.insert("ints", HashMap::from([("val".to_string(), Value::Int(10))]))
.unwrap();
engine
.insert("ints", HashMap::from([("val".to_string(), Value::Null)]))
.unwrap();
let col_data = engine.load_column_data("ints", "val").unwrap();
assert_eq!(col_data.row_ids.len(), 2);
}
#[test]
fn test_materialize_columns_nonexistent_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let result = engine.materialize_columns("test", &["nonexistent"]);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnNotFound(_)
));
}
#[test]
fn test_drop_column_with_primary_key_constraint() {
let engine = RelationalEngine::new();
let mut schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
schema.add_constraint(Constraint::PrimaryKey {
columns: vec!["id".to_string()],
name: "pk_id".to_string(),
});
engine.create_table("test", schema).unwrap();
let result = engine.drop_column("test", "id");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnHasConstraint { .. }
));
}
#[test]
fn test_drop_column_with_unique_constraint() {
let engine = RelationalEngine::new();
let mut schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("email", ColumnType::String),
]);
schema.add_constraint(Constraint::Unique {
columns: vec!["email".to_string()],
name: "uq_email".to_string(),
});
engine.create_table("users", schema).unwrap();
let result = engine.drop_column("users", "email");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnHasConstraint { .. }
));
}
#[test]
fn test_drop_column_with_not_null_constraint() {
let engine = RelationalEngine::new();
let mut schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
schema.add_constraint(Constraint::NotNull {
column: "name".to_string(),
name: "nn_name".to_string(),
});
engine.create_table("test", schema).unwrap();
let result = engine.drop_column("test", "name");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnHasConstraint { .. }
));
}
#[test]
fn test_transaction_manager_cleanup_expired() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let _tx_id = engine.begin_transaction();
let removed = engine.tx_manager.cleanup_expired();
assert_eq!(removed, 0);
}
#[test]
fn test_transaction_manager_active_lock_count() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
let tx_id = engine.begin_transaction();
let initial_locks = engine.tx_manager.active_lock_count();
let _ = engine.tx_update(
tx_id,
"test",
Condition::Eq("id".to_string(), Value::Int(1)),
HashMap::from([("id".to_string(), Value::Int(2))]),
);
engine.commit(tx_id).unwrap();
let final_locks = engine.tx_manager.active_lock_count();
assert!(final_locks <= initial_locks);
}
#[test]
fn test_transaction_manager_locks_held_by() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let tx_id = engine.begin_transaction();
let locks = engine.tx_manager.locks_held_by(tx_id);
assert_eq!(locks, 0);
engine.rollback(tx_id).unwrap();
}
#[test]
fn test_transaction_manager_is_row_locked() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
let is_locked = engine.tx_manager.is_row_locked("test", 1);
assert!(!is_locked);
}
#[test]
fn test_transaction_manager_row_lock_holder() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
let holder = engine.tx_manager.row_lock_holder("test", 1);
assert!(holder.is_none());
}
#[test]
fn test_select_columnar_ne_filter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("nums", schema).unwrap();
for i in 0..10 {
engine
.insert("nums", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
engine.materialize_columns("nums", &["val"]).unwrap();
let result = engine
.select_columnar(
"nums",
Condition::Ne("val".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(result.len(), 9);
}
#[test]
fn test_select_columnar_le_filter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("nums", schema).unwrap();
for i in 0..10 {
engine
.insert("nums", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
engine.materialize_columns("nums", &["val"]).unwrap();
let result = engine
.select_columnar(
"nums",
Condition::Le("val".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(result.len(), 6); }
#[test]
fn test_select_columnar_lt_filter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("nums", schema).unwrap();
for i in 0..10 {
engine
.insert("nums", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
engine.materialize_columns("nums", &["val"]).unwrap();
let result = engine
.select_columnar(
"nums",
Condition::Lt("val".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(result.len(), 5); }
#[test]
fn test_referential_action_variants() {
assert_eq!(ReferentialAction::default(), ReferentialAction::Restrict);
let actions = [
ReferentialAction::Restrict,
ReferentialAction::Cascade,
ReferentialAction::SetNull,
ReferentialAction::SetDefault,
ReferentialAction::NoAction,
];
for action in actions {
let cloned = action;
assert_eq!(action, cloned);
}
}
#[test]
fn test_null_bitmap_dense_high_null_ratio() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("nulls", schema).unwrap();
for i in 0..100 {
let value = if i % 2 == 0 {
Value::Null
} else {
Value::Int(i)
};
engine
.insert("nulls", HashMap::from([("val".to_string(), value)]))
.unwrap();
}
let col_data = engine.load_column_data("nulls", "val").unwrap();
assert_eq!(col_data.row_ids.len(), 100);
}
#[test]
fn test_query_timeout_display() {
let err = RelationalError::QueryTimeout {
operation: "select".to_string(),
timeout_ms: 5000,
};
let msg = format!("{err}");
assert!(msg.contains("Query timeout"));
assert!(msg.contains("select"));
assert!(msg.contains("5000"));
}
#[test]
fn test_select_columnar_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("empty", schema).unwrap();
let result = engine
.select_columnar(
"empty",
Condition::True,
ColumnarScanOptions {
projection: Some(vec!["id".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert!(result.is_empty());
}
#[test]
fn test_select_columnar_with_projection_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("users", schema).unwrap();
engine
.insert(
"users",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("Alice".into())),
("age".to_string(), Value::Int(30)),
]),
)
.unwrap();
engine.materialize_columns("users", &["id", "age"]).unwrap();
let result = engine
.select_columnar(
"users",
Condition::True,
ColumnarScanOptions {
projection: Some(vec!["id".to_string(), "name".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_load_column_data_string_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("strings", schema).unwrap();
engine
.insert(
"strings",
HashMap::from([("name".to_string(), Value::String("Alice".into()))]),
)
.unwrap();
engine
.insert(
"strings",
HashMap::from([("name".to_string(), Value::String("Bob".into()))]),
)
.unwrap();
engine
.insert(
"strings",
HashMap::from([("name".to_string(), Value::String("Alice".into()))]),
)
.unwrap();
let col_data = engine.load_column_data("strings", "name").unwrap();
assert_eq!(col_data.row_ids.len(), 3);
}
#[test]
fn test_load_column_data_string_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String).nullable()]);
engine.create_table("strings", schema).unwrap();
engine
.insert(
"strings",
HashMap::from([("name".to_string(), Value::String("Alice".into()))]),
)
.unwrap();
engine
.insert(
"strings",
HashMap::from([("name".to_string(), Value::Null)]),
)
.unwrap();
let col_data = engine.load_column_data("strings", "name").unwrap();
assert_eq!(col_data.row_ids.len(), 2);
}
#[test]
fn test_load_column_data_bool_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("active", ColumnType::Bool)]);
engine.create_table("bools", schema).unwrap();
engine
.insert(
"bools",
HashMap::from([("active".to_string(), Value::Bool(true))]),
)
.unwrap();
engine
.insert(
"bools",
HashMap::from([("active".to_string(), Value::Bool(false))]),
)
.unwrap();
let col_data = engine.load_column_data("bools", "active").unwrap();
assert_eq!(col_data.row_ids.len(), 2);
}
#[test]
fn test_load_column_data_bool_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("active", ColumnType::Bool).nullable()]);
engine.create_table("bools", schema).unwrap();
engine
.insert(
"bools",
HashMap::from([("active".to_string(), Value::Bool(true))]),
)
.unwrap();
engine
.insert(
"bools",
HashMap::from([("active".to_string(), Value::Null)]),
)
.unwrap();
let col_data = engine.load_column_data("bools", "active").unwrap();
assert_eq!(col_data.row_ids.len(), 2);
}
#[test]
fn test_load_column_data_bytes_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("data", ColumnType::Bytes)]);
engine.create_table("bytes_table", schema).unwrap();
engine
.insert(
"bytes_table",
HashMap::from([("data".to_string(), Value::Bytes(vec![1, 2, 3]))]),
)
.unwrap();
engine
.insert(
"bytes_table",
HashMap::from([("data".to_string(), Value::Bytes(vec![4, 5, 6]))]),
)
.unwrap();
let col_data = engine.load_column_data("bytes_table", "data").unwrap();
assert_eq!(col_data.row_ids.len(), 2);
}
#[test]
fn test_load_column_data_bytes_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("data", ColumnType::Bytes).nullable()]);
engine.create_table("bytes_table", schema).unwrap();
engine
.insert(
"bytes_table",
HashMap::from([("data".to_string(), Value::Bytes(vec![1, 2, 3]))]),
)
.unwrap();
engine
.insert(
"bytes_table",
HashMap::from([("data".to_string(), Value::Null)]),
)
.unwrap();
let col_data = engine.load_column_data("bytes_table", "data").unwrap();
assert_eq!(col_data.row_ids.len(), 2);
}
#[test]
fn test_load_column_data_json_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("doc", ColumnType::Json)]);
engine.create_table("jsons", schema).unwrap();
engine
.insert(
"jsons",
HashMap::from([(
"doc".to_string(),
Value::Json(serde_json::json!({"key": "value"})),
)]),
)
.unwrap();
engine
.insert(
"jsons",
HashMap::from([(
"doc".to_string(),
Value::Json(serde_json::json!({"key": "other"})),
)]),
)
.unwrap();
let col_data = engine.load_column_data("jsons", "doc").unwrap();
assert_eq!(col_data.row_ids.len(), 2);
}
#[test]
fn test_load_column_data_json_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("doc", ColumnType::Json).nullable()]);
engine.create_table("jsons", schema).unwrap();
engine
.insert(
"jsons",
HashMap::from([(
"doc".to_string(),
Value::Json(serde_json::json!({"key": "value"})),
)]),
)
.unwrap();
engine
.insert("jsons", HashMap::from([("doc".to_string(), Value::Null)]))
.unwrap();
let col_data = engine.load_column_data("jsons", "doc").unwrap();
assert_eq!(col_data.row_ids.len(), 2);
}
#[test]
fn test_select_columnar_gt_filter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("nums", schema).unwrap();
for i in 0..10 {
engine
.insert("nums", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
engine.materialize_columns("nums", &["val"]).unwrap();
let result = engine
.select_columnar(
"nums",
Condition::Gt("val".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(result.len(), 4); }
#[test]
fn test_select_columnar_ge_filter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("nums", schema).unwrap();
for i in 0..10 {
engine
.insert("nums", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
engine.materialize_columns("nums", &["val"]).unwrap();
let result = engine
.select_columnar(
"nums",
Condition::Ge("val".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(result.len(), 5); }
#[test]
fn test_select_columnar_and_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("nums", schema).unwrap();
for i in 0..10 {
engine
.insert("nums", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
engine.materialize_columns("nums", &["val"]).unwrap();
let result = engine
.select_columnar(
"nums",
Condition::And(
Box::new(Condition::Gt("val".to_string(), Value::Int(3))),
Box::new(Condition::Lt("val".to_string(), Value::Int(7))),
),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(result.len(), 3); }
#[test]
fn test_select_columnar_or_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("nums", schema).unwrap();
for i in 0..10 {
engine
.insert("nums", HashMap::from([("val".to_string(), Value::Int(i))]))
.unwrap();
}
engine.materialize_columns("nums", &["val"]).unwrap();
let result = engine
.select_columnar(
"nums",
Condition::Or(
Box::new(Condition::Lt("val".to_string(), Value::Int(3))),
Box::new(Condition::Gt("val".to_string(), Value::Int(7))),
),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(result.len(), 5); }
#[test]
fn test_drop_column_with_fk_constraint() {
let engine = RelationalEngine::new();
let parent_schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("parent", parent_schema).unwrap();
let mut child_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int),
]);
child_schema.add_constraint(Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_parent".to_string(),
columns: vec!["parent_id".to_string()],
referenced_table: "parent".to_string(),
referenced_columns: vec!["id".to_string()],
on_delete: ReferentialAction::Restrict,
on_update: ReferentialAction::Restrict,
}));
engine.create_table("child", child_schema).unwrap();
let result = engine.drop_column("child", "parent_id");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnHasConstraint { .. }
));
}
#[test]
fn test_error_display_too_many_tables_details() {
let err = RelationalError::TooManyTables {
current: 100,
max: 50,
};
let msg = format!("{err}");
assert!(msg.contains("Too many tables"));
assert!(msg.contains("100"));
assert!(msg.contains("50"));
}
#[test]
fn test_error_display_too_many_indexes_details() {
let err = RelationalError::TooManyIndexes {
table: "test".to_string(),
current: 20,
max: 10,
};
let msg = format!("{err}");
assert!(msg.contains("Too many indexes"));
assert!(msg.contains("test"));
assert!(msg.contains("20"));
}
#[test]
fn test_error_display_foreign_key_violation() {
let err = RelationalError::ForeignKeyViolation {
constraint_name: "fk_user".to_string(),
table: "orders".to_string(),
referenced_table: "users".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("Foreign key"));
assert!(msg.contains("fk_user"));
}
#[test]
fn test_error_display_cannot_add_column() {
let err = RelationalError::CannotAddColumn {
column: "status".to_string(),
reason: "table has existing rows".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("Cannot add column"));
assert!(msg.contains("status"));
}
#[test]
fn test_error_display_column_already_exists() {
let err = RelationalError::ColumnAlreadyExists {
table: "users".to_string(),
column: "email".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("already exists"));
assert!(msg.contains("email"));
}
#[test]
fn test_slab_vectorized_filter_true_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(i))]))
.unwrap();
}
let _ = engine.select("test", Condition::True);
let rows = engine
.select_columnar(
"test",
Condition::True,
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_slab_vectorized_filter_ne_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine
.select_columnar(
"test",
Condition::Ne("id".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 9);
}
#[test]
fn test_slab_vectorized_filter_lt_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine
.select_columnar(
"test",
Condition::Lt("id".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_slab_vectorized_filter_gt_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine
.select_columnar(
"test",
Condition::Gt("id".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 4);
}
#[test]
fn test_slab_vectorized_filter_float_conditions() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
#[allow(clippy::cast_precision_loss)]
let f = f64::from(i);
engine
.insert(
"test",
HashMap::from([("val".to_string(), Value::Float(f))]),
)
.unwrap();
}
let rows_lt = engine
.select_columnar(
"test",
Condition::Lt("val".to_string(), Value::Float(5.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows_lt.len(), 5);
let rows_gt = engine
.select_columnar(
"test",
Condition::Gt("val".to_string(), Value::Float(5.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows_gt.len(), 4);
let rows_eq = engine
.select_columnar(
"test",
Condition::Eq("val".to_string(), Value::Float(5.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows_eq.len(), 1);
}
#[test]
fn test_slab_vectorized_filter_and_or() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..20 {
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(i))]))
.unwrap();
}
let rows_and = engine
.select_columnar(
"test",
Condition::And(
Box::new(Condition::Gt("id".to_string(), Value::Int(5))),
Box::new(Condition::Lt("id".to_string(), Value::Int(15))),
),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows_and.len(), 9);
let rows_or = engine
.select_columnar(
"test",
Condition::Or(
Box::new(Condition::Lt("id".to_string(), Value::Int(5))),
Box::new(Condition::Gt("id".to_string(), Value::Int(15))),
),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows_or.len(), 9);
}
#[test]
fn test_select_columnar_with_projection() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("val", ColumnType::Float),
]);
engine.create_table("test", schema).unwrap();
for i in 0..5 {
#[allow(clippy::cast_precision_loss)]
let f = i as f64;
engine
.insert(
"test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("name".to_string(), Value::String(format!("name{i}"))),
("val".to_string(), Value::Float(f)),
]),
)
.unwrap();
}
let rows = engine
.select_columnar(
"test",
Condition::True,
ColumnarScanOptions {
projection: Some(vec!["id".to_string(), "name".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 5);
for row in &rows {
let has_val = row.values.iter().any(|(k, _)| k == "val");
assert!(!has_val);
}
}
#[test]
fn test_tx_manager_set_phase_nonexistent_tx() {
let mgr = TransactionManager::new();
let result = mgr.set_phase(999, TxPhase::Active);
assert!(!result);
}
#[test]
fn test_tx_manager_cleanup_expired() {
let mgr = TransactionManager::with_timeout(std::time::Duration::from_millis(1));
let tx_id = mgr.begin();
assert!(mgr.is_active(tx_id));
std::thread::sleep(std::time::Duration::from_millis(20));
let removed = mgr.cleanup_expired();
assert!(removed >= 1);
}
#[test]
fn test_lock_manager_cleanup_expired_locks() {
let mgr = TransactionManager::with_timeouts(
std::time::Duration::from_millis(100),
std::time::Duration::from_millis(1),
);
let tx_id = mgr.begin();
let rows = vec![("test".to_string(), 1u64)];
let _ = mgr.lock_manager().try_lock(tx_id, &rows);
std::thread::sleep(std::time::Duration::from_millis(20));
let removed = mgr.cleanup_expired_locks();
assert!(removed >= 1);
}
#[test]
fn test_slab_vectorized_filter_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let rows = engine
.select_columnar(
"test",
Condition::Eq("id".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_select_with_deleted_rows() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
for i in 0..10 {
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(i))]))
.unwrap();
}
engine
.delete_rows("test", Condition::Eq("id".to_string(), Value::Int(5)))
.unwrap();
engine
.delete_rows("test", Condition::Eq("id".to_string(), Value::Int(7)))
.unwrap();
let rows = engine
.select_columnar(
"test",
Condition::True,
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 8);
}
#[test]
fn test_select_columnar_fallback_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("test", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("name".to_string(), Value::String(format!("test{i}"))),
]),
)
.unwrap();
}
let rows = engine
.select_columnar(
"test",
Condition::Eq("name".to_string(), Value::String("test2".to_string())),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_try_slab_select_with_projection() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Float),
]);
engine.create_table("test", schema).unwrap();
for i in 0..5 {
#[allow(clippy::cast_precision_loss)]
let f = i as f64;
engine
.insert(
"test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("val".to_string(), Value::Float(f)),
]),
)
.unwrap();
}
let rows = engine
.select_columnar(
"test",
Condition::Gt("id".to_string(), Value::Int(2)),
ColumnarScanOptions {
projection: Some(vec!["id".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn test_select_with_index_path() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine.create_index("test", "id").unwrap();
for i in 0..100 {
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine.select("test", Condition::Eq("id".to_string(), Value::Int(50)));
assert!(rows.is_ok());
assert_eq!(rows.unwrap().len(), 1);
}
#[test]
fn test_materialize_with_projection() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
Column::new("c", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
engine
.insert(
"test",
HashMap::from([
("a".to_string(), Value::Int(1)),
("b".to_string(), Value::Int(2)),
("c".to_string(), Value::Int(3)),
]),
)
.unwrap();
let rows = engine
.select_columnar(
"test",
Condition::True,
ColumnarScanOptions {
projection: Some(vec!["_id".to_string(), "a".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 1);
let has_id_col = rows[0].values.iter().any(|(k, _)| k == "_id");
assert!(!has_id_col);
}
#[test]
fn test_select_result_too_large() {
let config = RelationalConfig::new().with_max_query_result_rows(5);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_rtl", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_rtl",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine.create_index("test_rtl", "id").unwrap();
let result = engine.select_with_options(
"test_rtl",
Condition::True,
QueryOptions::new().with_timeout_ms(60000),
);
assert!(matches!(
result,
Err(RelationalError::ResultTooLarge { .. })
));
}
#[test]
fn test_rename_column_with_primary_key() {
let engine = RelationalEngine::new();
let schema = Schema::with_constraints(
vec![Column::new("id", ColumnType::Int)],
vec![Constraint::PrimaryKey {
name: "pk_id".to_string(),
columns: vec!["id".to_string()],
}],
);
engine.create_table("test_pk", schema).unwrap();
engine.rename_column("test_pk", "id", "user_id").unwrap();
let schema = engine.get_schema("test_pk").unwrap();
assert!(schema.columns.iter().any(|c| c.name == "user_id"));
if let Some(Constraint::PrimaryKey { columns, .. }) = schema.constraints.first() {
assert!(columns.contains(&"user_id".to_string()));
}
}
#[test]
fn test_rename_column_with_unique() {
let engine = RelationalEngine::new();
let schema = Schema::with_constraints(
vec![Column::new("email", ColumnType::String)],
vec![Constraint::Unique {
name: "uk_email".to_string(),
columns: vec!["email".to_string()],
}],
);
engine.create_table("test_uk", schema).unwrap();
engine
.rename_column("test_uk", "email", "user_email")
.unwrap();
let schema = engine.get_schema("test_uk").unwrap();
if let Some(Constraint::Unique { columns, .. }) = schema.constraints.first() {
assert!(columns.contains(&"user_email".to_string()));
}
}
#[test]
fn test_rename_column_with_foreign_key() {
let engine = RelationalEngine::new();
let ref_schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("users_fk", ref_schema).unwrap();
let schema = Schema::with_constraints(
vec![
Column::new("id", ColumnType::Int),
Column::new("user_id", ColumnType::Int),
],
vec![Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_user".to_string(),
columns: vec!["user_id".to_string()],
referenced_table: "users_fk".to_string(),
referenced_columns: vec!["id".to_string()],
on_delete: ReferentialAction::Cascade,
on_update: ReferentialAction::Cascade,
})],
);
engine.create_table("orders_fk", schema).unwrap();
engine
.rename_column("orders_fk", "user_id", "customer_id")
.unwrap();
let schema = engine.get_schema("orders_fk").unwrap();
for constraint in &schema.constraints {
if let Constraint::ForeignKey(fk) = constraint {
assert!(fk.columns.contains(&"customer_id".to_string()));
}
}
}
#[test]
fn test_rename_column_with_not_null() {
let engine = RelationalEngine::new();
let schema = Schema::with_constraints(
vec![Column::new("name", ColumnType::String)],
vec![Constraint::NotNull {
name: "nn_name".to_string(),
column: "name".to_string(),
}],
);
engine.create_table("test_nn", schema).unwrap();
engine
.rename_column("test_nn", "name", "full_name")
.unwrap();
let schema = engine.get_schema("test_nn").unwrap();
for constraint in &schema.constraints {
if let Constraint::NotNull { column, .. } = constraint {
assert_eq!(column, "full_name");
}
}
}
#[test]
fn test_drop_btree_index_with_data() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_dbi", schema).unwrap();
engine.create_btree_index("test_dbi", "id").unwrap();
for i in 0..10 {
engine
.insert(
"test_dbi",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine.drop_btree_index("test_dbi", "id").unwrap();
let rows = engine.select("test_dbi", Condition::True).unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_drop_hash_index_with_data() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_dhi", schema).unwrap();
engine.create_index("test_dhi", "id").unwrap();
for i in 0..10 {
engine
.insert(
"test_dhi",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine.drop_index("test_dhi", "id").unwrap();
let rows = engine.select("test_dhi", Condition::True).unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_slow_query_warning_indexed_select() {
let config = RelationalConfig::new().with_slow_query_threshold_ms(0);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
engine.create_index("test", "id").unwrap();
for i in 0..100 {
engine
.insert("test", HashMap::from([("id".to_string(), Value::Int(i))]))
.unwrap();
}
let rows = engine
.select("test", Condition::Eq("id".to_string(), Value::Int(50)))
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_columnar_filter_type_mismatch() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("test", schema).unwrap();
engine
.insert(
"test",
HashMap::from([
("name".to_string(), Value::String("Alice".into())),
("age".to_string(), Value::Int(30)),
]),
)
.unwrap();
let rows = engine
.select_columnar(
"test",
Condition::Eq("name".to_string(), Value::Int(30)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_tx_manager_active_count() {
let mgr = TransactionManager::new();
assert_eq!(mgr.active_count(), 0);
let tx1 = mgr.begin();
assert_eq!(mgr.active_count(), 1);
let tx2 = mgr.begin();
assert_eq!(mgr.active_count(), 2);
mgr.set_phase(tx1, TxPhase::Committed);
assert_eq!(mgr.active_count(), 1);
mgr.remove(tx2);
assert_eq!(mgr.active_count(), 0);
}
#[test]
fn test_tx_manager_locks_held() {
let mgr = TransactionManager::new();
let tx_id = mgr.begin();
let rows = vec![
("test".to_string(), 1u64),
("test".to_string(), 2u64),
("test".to_string(), 3u64),
];
mgr.lock_manager().try_lock(tx_id, &rows).unwrap();
assert_eq!(mgr.locks_held_by(tx_id), 3);
mgr.release_locks(tx_id);
assert_eq!(mgr.locks_held_by(tx_id), 0);
}
#[test]
fn test_tx_manager_active_lock_count() {
let mgr = TransactionManager::new();
assert_eq!(mgr.active_lock_count(), 0);
let tx_id = mgr.begin();
let rows = vec![("test".to_string(), 1u64)];
mgr.lock_manager().try_lock(tx_id, &rows).unwrap();
assert_eq!(mgr.active_lock_count(), 1);
}
#[test]
fn test_tx_manager_row_lock_queries() {
let mgr = TransactionManager::new();
let tx_id = mgr.begin();
let rows = vec![("test".to_string(), 1u64)];
mgr.lock_manager().try_lock(tx_id, &rows).unwrap();
assert!(mgr.is_row_locked("test", 1));
assert!(!mgr.is_row_locked("test", 2));
assert_eq!(mgr.row_lock_holder("test", 1), Some(tx_id));
assert_eq!(mgr.row_lock_holder("test", 2), None);
}
#[test]
fn test_row_lock_conflict() {
let mgr = TransactionManager::new();
let tx1 = mgr.begin();
let tx2 = mgr.begin();
let rows = vec![("test".to_string(), 1u64)];
mgr.lock_manager().try_lock(tx1, &rows).unwrap();
let result = mgr.lock_manager().try_lock(tx2, &rows);
assert!(result.is_err());
}
#[test]
fn test_lock_manager_query_methods() {
let mgr = TransactionManager::new();
let tx_id = mgr.begin();
let rows = vec![("test".to_string(), 1u64)];
mgr.lock_manager().try_lock(tx_id, &rows).unwrap();
assert!(mgr.lock_manager().is_locked("test", 1));
assert!(!mgr.lock_manager().is_locked("test", 2));
assert_eq!(mgr.lock_manager().lock_holder("test", 1), Some(tx_id));
assert_eq!(mgr.lock_manager().lock_holder("test", 2), None);
}
#[test]
fn test_select_with_large_dataset() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::String),
]);
engine.create_table("test", schema).unwrap();
for i in 0..200 {
engine
.insert(
"test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("data".to_string(), Value::String(format!("item{i}"))),
]),
)
.unwrap();
}
let rows = engine
.select_columnar(
"test",
Condition::Gt("id".to_string(), Value::Int(150)),
ColumnarScanOptions {
projection: Some(vec!["id".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 49);
}
#[test]
fn test_tx_manager_get_phase() {
let mgr = TransactionManager::new();
assert!(mgr.get(999).is_none());
let tx_id = mgr.begin();
assert_eq!(mgr.get(tx_id), Some(TxPhase::Active));
mgr.set_phase(tx_id, TxPhase::Committing);
assert_eq!(mgr.get(tx_id), Some(TxPhase::Committing));
}
#[test]
fn test_tx_manager_remove() {
let mgr = TransactionManager::new();
let tx_id = mgr.begin();
assert!(mgr.is_active(tx_id));
mgr.remove(tx_id);
assert!(!mgr.is_active(tx_id));
assert!(mgr.get(tx_id).is_none());
}
#[test]
fn test_select_columnar_non_materialized() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes),
]);
engine.create_table("test_col", schema).unwrap();
engine
.insert(
"test_col",
HashMap::from([
("id".to_string(), Value::Int(1)),
("data".to_string(), Value::Bytes(vec![1, 2, 3])),
]),
)
.unwrap();
let rows = engine
.select_columnar(
"test_col",
Condition::True,
ColumnarScanOptions {
projection: Some(vec!["id".to_string(), "data".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_bytes_column_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("blob", ColumnType::Bytes),
]);
engine.create_table("test_bytes", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test_bytes",
HashMap::from([
("id".to_string(), Value::Int(i)),
("blob".to_string(), Value::Bytes(vec![i as u8; 10])),
]),
)
.unwrap();
}
let rows = engine.select("test_bytes", Condition::True).unwrap();
assert_eq!(rows.len(), 5);
for row in &rows {
let blob_val = row.values.iter().find(|(k, _)| k == "blob");
assert!(blob_val.is_some());
}
}
#[test]
fn test_json_column_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("meta", ColumnType::Json),
]);
engine.create_table("test_json", schema).unwrap();
for i in 0..5 {
let json = serde_json::json!({"key": i});
engine
.insert(
"test_json",
HashMap::from([
("id".to_string(), Value::Int(i)),
("meta".to_string(), Value::Json(json)),
]),
)
.unwrap();
}
let rows = engine.select("test_json", Condition::True).unwrap();
assert_eq!(rows.len(), 5);
for row in &rows {
let meta_val = row.values.iter().find(|(k, _)| k == "meta");
assert!(meta_val.is_some());
}
}
#[test]
fn test_select_index_miss_debug() {
let config = RelationalConfig::new();
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_imiss", schema).unwrap();
for i in 0..20 {
engine
.insert(
"test_imiss",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine.select("test_imiss", Condition::True).unwrap();
assert_eq!(rows.len(), 20);
}
#[test]
fn test_select_columnar_empty_result() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_cempty", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_cempty",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows = engine
.select_columnar(
"test_cempty",
Condition::Eq("id".to_string(), Value::Int(999)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_slab_vectorized_filter_le_ge() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_lege", schema).unwrap();
for i in 0..20 {
engine
.insert(
"test_lege",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let rows_le = engine
.select_columnar(
"test_lege",
Condition::Le("id".to_string(), Value::Int(10)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows_le.len(), 11);
let rows_ge = engine
.select_columnar(
"test_lege",
Condition::Ge("id".to_string(), Value::Int(10)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows_ge.len(), 10);
}
#[test]
fn test_slab_vectorized_filter_float_le_ge() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("test_fleg", schema).unwrap();
for i in 0..20 {
#[allow(clippy::cast_precision_loss)]
let f = f64::from(i);
engine
.insert(
"test_fleg",
HashMap::from([("val".to_string(), Value::Float(f))]),
)
.unwrap();
}
let rows_le = engine
.select_columnar(
"test_fleg",
Condition::Le("val".to_string(), Value::Float(10.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows_le.len(), 11);
let rows_ge = engine
.select_columnar(
"test_fleg",
Condition::Ge("val".to_string(), Value::Float(10.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows_ge.len(), 10);
}
#[test]
fn test_slab_vectorized_filter_float_ne() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("test_fne", schema).unwrap();
for i in 0..10 {
#[allow(clippy::cast_precision_loss)]
let f = f64::from(i);
engine
.insert(
"test_fne",
HashMap::from([("val".to_string(), Value::Float(f))]),
)
.unwrap();
}
let rows = engine
.select_columnar(
"test_fne",
Condition::Ne("val".to_string(), Value::Float(5.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 9);
}
#[test]
fn test_select_non_indexed_large() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("test_large", schema).unwrap();
for i in 0..500 {
engine
.insert(
"test_large",
HashMap::from([
("id".to_string(), Value::Int(i)),
("name".to_string(), Value::String(format!("name{i}"))),
]),
)
.unwrap();
}
let rows = engine.select("test_large", Condition::True).unwrap();
assert_eq!(rows.len(), 500);
}
#[test]
fn test_update_no_match() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("test_unm", schema).unwrap();
engine
.insert(
"test_unm",
HashMap::from([
("id".to_string(), Value::Int(1)),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
let count = engine
.update(
"test_unm",
Condition::Eq("id".to_string(), Value::Int(999)),
HashMap::from([("val".to_string(), Value::Int(20))]),
)
.unwrap();
assert_eq!(count, 0);
}
#[test]
fn test_delete_no_match() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_dnm", schema).unwrap();
engine
.insert(
"test_dnm",
HashMap::from([("id".to_string(), Value::Int(1))]),
)
.unwrap();
let count = engine
.delete_rows("test_dnm", Condition::Eq("id".to_string(), Value::Int(999)))
.unwrap();
assert_eq!(count, 0);
let rows = engine.select("test_dnm", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_columnar_all_true_selection() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
]);
engine.create_table("test_ats", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_ats",
HashMap::from([
("a".to_string(), Value::Int(i)),
("b".to_string(), Value::Int(i * 2)),
]),
)
.unwrap();
}
let rows = engine
.select_columnar(
"test_ats",
Condition::True,
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_load_column_data_bytes() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes),
]);
engine.create_table("test_lcdb", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test_lcdb",
HashMap::from([
("id".to_string(), Value::Int(i)),
("data".to_string(), Value::Bytes(vec![i as u8; 10])),
]),
)
.unwrap();
}
let col_data = engine.load_column_data("test_lcdb", "data").unwrap();
assert_eq!(col_data.row_ids.len(), 5);
for i in 0..5 {
let val = col_data.get_value(i);
assert!(val.is_some());
if let Some(Value::Bytes(b)) = val {
assert_eq!(b.len(), 10);
}
}
}
#[test]
fn test_load_column_data_json() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("meta", ColumnType::Json),
]);
engine.create_table("test_lcdj", schema).unwrap();
for i in 0..5 {
let json = serde_json::json!({"key": i, "name": format!("item{i}")});
engine
.insert(
"test_lcdj",
HashMap::from([
("id".to_string(), Value::Int(i)),
("meta".to_string(), Value::Json(json)),
]),
)
.unwrap();
}
let col_data = engine.load_column_data("test_lcdj", "meta").unwrap();
assert_eq!(col_data.row_ids.len(), 5);
for i in 0..5 {
let val = col_data.get_value(i);
assert!(val.is_some());
assert!(matches!(val, Some(Value::Json(_))));
}
}
#[test]
fn test_load_column_data_bytes_nulls_extended() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes).nullable(),
]);
engine.create_table("test_lcdbn", schema).unwrap();
engine
.insert(
"test_lcdbn",
HashMap::from([
("id".to_string(), Value::Int(1)),
("data".to_string(), Value::Bytes(vec![1, 2, 3])),
]),
)
.unwrap();
engine
.insert(
"test_lcdbn",
HashMap::from([
("id".to_string(), Value::Int(2)),
("data".to_string(), Value::Null),
]),
)
.unwrap();
engine
.insert(
"test_lcdbn",
HashMap::from([
("id".to_string(), Value::Int(3)),
("data".to_string(), Value::Bytes(vec![4, 5, 6])),
]),
)
.unwrap();
let col_data = engine.load_column_data("test_lcdbn", "data").unwrap();
assert_eq!(col_data.row_ids.len(), 3);
assert!(col_data.null_count() >= 1);
}
#[test]
fn test_load_column_data_json_nulls_extended() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("meta", ColumnType::Json).nullable(),
]);
engine.create_table("test_lcdjn2", schema).unwrap();
engine
.insert(
"test_lcdjn2",
HashMap::from([
("id".to_string(), Value::Int(1)),
("meta".to_string(), Value::Json(serde_json::json!({"a": 1}))),
]),
)
.unwrap();
engine
.insert(
"test_lcdjn2",
HashMap::from([
("id".to_string(), Value::Int(2)),
("meta".to_string(), Value::Null),
]),
)
.unwrap();
let col_data = engine.load_column_data("test_lcdjn2", "meta").unwrap();
assert_eq!(col_data.row_ids.len(), 2);
assert!(col_data.null_count() >= 1);
let null_val = col_data.get_value(1);
assert_eq!(null_val, Some(Value::Null));
}
#[test]
fn test_materialize_columns_bytes() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("blob", ColumnType::Bytes),
]);
engine.create_table("test_mcb", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test_mcb",
HashMap::from([
("id".to_string(), Value::Int(i)),
("blob".to_string(), Value::Bytes(vec![i as u8; 5])),
]),
)
.unwrap();
}
engine.materialize_columns("test_mcb", &["blob"]).unwrap();
let rows = engine.select("test_mcb", Condition::True).unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_materialize_columns_json() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("meta", ColumnType::Json),
]);
engine.create_table("test_mcj", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test_mcj",
HashMap::from([
("id".to_string(), Value::Int(i)),
("meta".to_string(), Value::Json(serde_json::json!({"i": i}))),
]),
)
.unwrap();
}
engine.materialize_columns("test_mcj", &["meta"]).unwrap();
let rows = engine.select("test_mcj", Condition::True).unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_select_columnar_bytes_materialized() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("blob", ColumnType::Bytes),
]);
engine.create_table("test_scbm", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_scbm",
HashMap::from([
("id".to_string(), Value::Int(i)),
("blob".to_string(), Value::Bytes(vec![i as u8; 5])),
]),
)
.unwrap();
}
engine
.materialize_columns("test_scbm", &["id", "blob"])
.unwrap();
let rows = engine
.select_columnar(
"test_scbm",
Condition::Gt("id".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: Some(vec!["id".to_string(), "blob".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 4);
for row in &rows {
let has_blob = row.values.iter().any(|(k, _)| k == "blob");
assert!(has_blob);
}
}
#[test]
fn test_select_columnar_json_materialized() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("meta", ColumnType::Json),
]);
engine.create_table("test_scjm", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_scjm",
HashMap::from([
("id".to_string(), Value::Int(i)),
(
"meta".to_string(),
Value::Json(serde_json::json!({"val": i})),
),
]),
)
.unwrap();
}
engine
.materialize_columns("test_scjm", &["id", "meta"])
.unwrap();
let rows = engine
.select_columnar(
"test_scjm",
Condition::Lt("id".to_string(), Value::Int(5)),
ColumnarScanOptions {
projection: Some(vec!["id".to_string(), "meta".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 5);
for row in &rows {
let has_meta = row.values.iter().any(|(k, _)| k == "meta");
assert!(has_meta);
}
}
#[test]
fn test_column_values_len() {
let engine = RelationalEngine::new();
let schema_int = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("test_cvl_int", schema_int).unwrap();
for i in 0..10 {
engine
.insert(
"test_cvl_int",
HashMap::from([("val".to_string(), Value::Int(i))]),
)
.unwrap();
}
let col_int = engine.load_column_data("test_cvl_int", "val").unwrap();
assert_eq!(col_int.row_ids.len(), 10);
let schema_float = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("test_cvl_float", schema_float).unwrap();
for i in 0..10 {
#[allow(clippy::cast_precision_loss)]
let f = f64::from(i);
engine
.insert(
"test_cvl_float",
HashMap::from([("val".to_string(), Value::Float(f))]),
)
.unwrap();
}
let col_float = engine.load_column_data("test_cvl_float", "val").unwrap();
assert_eq!(col_float.row_ids.len(), 10);
let schema_bool = Schema::new(vec![Column::new("val", ColumnType::Bool)]);
engine.create_table("test_cvl_bool", schema_bool).unwrap();
for i in 0..10 {
engine
.insert(
"test_cvl_bool",
HashMap::from([("val".to_string(), Value::Bool(i % 2 == 0))]),
)
.unwrap();
}
let col_bool = engine.load_column_data("test_cvl_bool", "val").unwrap();
assert_eq!(col_bool.row_ids.len(), 10);
}
#[test]
fn test_vectorized_filter_column_not_found() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_vfcnf", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test_vfcnf",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine.materialize_columns("test_vfcnf", &["id"]).unwrap();
let result = engine.select_columnar(
"test_vfcnf",
Condition::Eq("nonexistent".to_string(), Value::Int(1)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
);
if let Ok(rows) = result {
assert!(rows.is_empty() || !rows.is_empty());
}
}
#[test]
fn test_select_with_minimal_timeout() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_smt", schema).unwrap();
for i in 0..100 {
engine
.insert(
"test_smt",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let result = engine.select_with_options(
"test_smt",
Condition::True,
QueryOptions::new().with_timeout_ms(0),
);
match result {
Ok(rows) => assert_eq!(rows.len(), 100),
Err(RelationalError::QueryTimeout { .. }) => (),
Err(e) => panic!("Unexpected error: {e}"),
}
}
#[test]
fn test_select_columnar_unsupported_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("test_scuc", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_scuc",
HashMap::from([
("id".to_string(), Value::Int(i)),
("name".to_string(), Value::String(format!("name{i}"))),
]),
)
.unwrap();
}
let rows = engine
.select_columnar(
"test_scuc",
Condition::And(
Box::new(Condition::Ge(
"name".to_string(),
Value::String("name3".to_string()),
)),
Box::new(Condition::Le(
"name".to_string(),
Value::String("name7".to_string()),
)),
),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert!(!rows.is_empty());
}
#[test]
fn test_delete_rows_with_options() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_drwo", schema).unwrap();
for i in 0..20 {
engine
.insert(
"test_drwo",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let count = engine
.delete_rows_with_options(
"test_drwo",
Condition::Lt("id".to_string(), Value::Int(10)),
QueryOptions::new().with_timeout_ms(60000),
)
.unwrap();
assert_eq!(count, 10);
let remaining = engine.select("test_drwo", Condition::True).unwrap();
assert_eq!(remaining.len(), 10);
}
#[test]
fn test_update_with_options() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("test_uwo", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_uwo",
HashMap::from([
("id".to_string(), Value::Int(i)),
("val".to_string(), Value::Int(0)),
]),
)
.unwrap();
}
let count = engine
.update_with_options(
"test_uwo",
Condition::Lt("id".to_string(), Value::Int(5)),
HashMap::from([("val".to_string(), Value::Int(100))]),
QueryOptions::new().with_timeout_ms(60000),
)
.unwrap();
assert_eq!(count, 5);
}
#[test]
fn test_join_with_options() {
let engine = RelationalEngine::new();
let schema1 = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("test_jwo_a", schema1).unwrap();
let schema2 = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("test_jwo_b", schema2).unwrap();
for i in 0..5 {
engine
.insert(
"test_jwo_a",
HashMap::from([
("id".to_string(), Value::Int(i)),
("name".to_string(), Value::String(format!("item{i}"))),
]),
)
.unwrap();
engine
.insert(
"test_jwo_b",
HashMap::from([
("id".to_string(), Value::Int(i)),
("value".to_string(), Value::Int(i * 10)),
]),
)
.unwrap();
}
let rows = engine
.join_with_options(
"test_jwo_a",
"test_jwo_b",
"id",
"id",
QueryOptions::new().with_timeout_ms(60000),
)
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_load_column_data_bool_with_null() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("flag", ColumnType::Bool).nullable(),
]);
engine.create_table("test_lcdbn2", schema).unwrap();
engine
.insert(
"test_lcdbn2",
HashMap::from([
("id".to_string(), Value::Int(1)),
("flag".to_string(), Value::Bool(true)),
]),
)
.unwrap();
engine
.insert(
"test_lcdbn2",
HashMap::from([
("id".to_string(), Value::Int(2)),
("flag".to_string(), Value::Null),
]),
)
.unwrap();
engine
.insert(
"test_lcdbn2",
HashMap::from([
("id".to_string(), Value::Int(3)),
("flag".to_string(), Value::Bool(false)),
]),
)
.unwrap();
let col_data = engine.load_column_data("test_lcdbn2", "flag").unwrap();
assert_eq!(col_data.row_ids.len(), 3);
assert!(col_data.null_count() >= 1);
}
#[test]
fn test_column_data_get_value_string_multi() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("test_cdgvs", schema).unwrap();
engine
.insert(
"test_cdgvs",
HashMap::from([("name".to_string(), Value::String("hello".to_string()))]),
)
.unwrap();
engine
.insert(
"test_cdgvs",
HashMap::from([("name".to_string(), Value::String("world".to_string()))]),
)
.unwrap();
let col_data = engine.load_column_data("test_cdgvs", "name").unwrap();
let val0 = col_data.get_value(0);
assert!(matches!(val0, Some(Value::String(_))));
let val1 = col_data.get_value(1);
assert!(matches!(val1, Some(Value::String(_))));
}
#[test]
fn test_column_data_get_value_bool_multi() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("flag", ColumnType::Bool)]);
engine.create_table("test_cdgvbm", schema).unwrap();
engine
.insert(
"test_cdgvbm",
HashMap::from([("flag".to_string(), Value::Bool(true))]),
)
.unwrap();
engine
.insert(
"test_cdgvbm",
HashMap::from([("flag".to_string(), Value::Bool(false))]),
)
.unwrap();
let col_data = engine.load_column_data("test_cdgvbm", "flag").unwrap();
let val0 = col_data.get_value(0);
assert_eq!(val0, Some(Value::Bool(true)));
let val1 = col_data.get_value(1);
assert_eq!(val1, Some(Value::Bool(false)));
}
#[test]
fn test_null_bitmap_sparse_path() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("test_nbsp", schema).unwrap();
for i in 0..100 {
let value = if i % 20 == 0 {
Value::Null
} else {
Value::Int(i)
};
engine
.insert("test_nbsp", HashMap::from([("val".to_string(), value)]))
.unwrap();
}
let col_data = engine.load_column_data("test_nbsp", "val").unwrap();
assert_eq!(col_data.null_count(), 5);
assert_eq!(col_data.get_value(0), Some(Value::Null));
assert_eq!(col_data.get_value(20), Some(Value::Null));
assert!(matches!(col_data.get_value(1), Some(Value::Int(_))));
}
#[test]
fn test_slab_filter_rows_true() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_sfrt", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test_sfrt",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine.materialize_columns("test_sfrt", &["id"]).unwrap();
let rows = engine.select("test_sfrt", Condition::True).unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_slab_filter_rows_ne_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_sfrni", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test_sfrni",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine.materialize_columns("test_sfrni", &["id"]).unwrap();
let rows = engine
.select("test_sfrni", Condition::Ne("id".to_string(), Value::Int(2)))
.unwrap();
assert_eq!(rows.len(), 4);
}
#[test]
fn test_slab_filter_rows_lt_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("test_sfrli", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfrli",
HashMap::from([("value".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine
.materialize_columns("test_sfrli", &["value"])
.unwrap();
let rows = engine
.select(
"test_sfrli",
Condition::Lt("value".to_string(), Value::Int(5)),
)
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_slab_filter_rows_le_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("test_sfrle", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfrle",
HashMap::from([("value".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine
.materialize_columns("test_sfrle", &["value"])
.unwrap();
let rows = engine
.select(
"test_sfrle",
Condition::Le("value".to_string(), Value::Int(5)),
)
.unwrap();
assert_eq!(rows.len(), 6);
}
#[test]
fn test_slab_filter_rows_gt_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("test_sfrgi", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfrgi",
HashMap::from([("value".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine
.materialize_columns("test_sfrgi", &["value"])
.unwrap();
let rows = engine
.select(
"test_sfrgi",
Condition::Gt("value".to_string(), Value::Int(5)),
)
.unwrap();
assert_eq!(rows.len(), 4);
}
#[test]
fn test_slab_filter_rows_ge_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("test_sfrgei", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfrgei",
HashMap::from([("value".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine
.materialize_columns("test_sfrgei", &["value"])
.unwrap();
let rows = engine
.select(
"test_sfrgei",
Condition::Ge("value".to_string(), Value::Int(5)),
)
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_slab_filter_rows_lt_float() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Float)]);
engine.create_table("test_sfrlf", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfrlf",
HashMap::from([("value".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
engine
.materialize_columns("test_sfrlf", &["value"])
.unwrap();
let rows = engine
.select(
"test_sfrlf",
Condition::Lt("value".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_slab_filter_rows_gt_float() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Float)]);
engine.create_table("test_sfrgf", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfrgf",
HashMap::from([("value".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
engine
.materialize_columns("test_sfrgf", &["value"])
.unwrap();
let rows = engine
.select(
"test_sfrgf",
Condition::Gt("value".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 4);
}
#[test]
fn test_slab_filter_rows_eq_float() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Float)]);
engine.create_table("test_sfref", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfref",
HashMap::from([("value".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
engine
.materialize_columns("test_sfref", &["value"])
.unwrap();
let rows = engine
.select(
"test_sfref",
Condition::Eq("value".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_drop_column_with_hash_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("test_dchi", schema).unwrap();
engine.create_index("test_dchi", "name").unwrap();
engine
.insert(
"test_dchi",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("Alice".to_string())),
]),
)
.unwrap();
engine.drop_column("test_dchi", "name").unwrap();
let schema = engine.get_schema("test_dchi").unwrap();
assert!(schema.get_column("name").is_none());
}
#[test]
fn test_drop_column_with_btree_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("test_dcbi", schema).unwrap();
engine.create_btree_index("test_dcbi", "value").unwrap();
for i in 0..10 {
engine
.insert(
"test_dcbi",
HashMap::from([
("id".to_string(), Value::Int(i)),
("value".to_string(), Value::Int(i * 10)),
]),
)
.unwrap();
}
engine.drop_column("test_dcbi", "value").unwrap();
let schema = engine.get_schema("test_dcbi").unwrap();
assert!(schema.get_column("value").is_none());
}
#[test]
fn test_column_values_bytes_len() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("data", ColumnType::Bytes)]);
engine.create_table("test_cvbl", schema).unwrap();
engine
.insert(
"test_cvbl",
HashMap::from([("data".to_string(), Value::Bytes(vec![1, 2, 3]))]),
)
.unwrap();
engine
.insert(
"test_cvbl",
HashMap::from([("data".to_string(), Value::Bytes(vec![4, 5, 6, 7]))]),
)
.unwrap();
let col_data = engine.load_column_data("test_cvbl", "data").unwrap();
assert_eq!(col_data.values.len(), 2);
}
#[test]
fn test_column_values_json_len() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("meta", ColumnType::Json)]);
engine.create_table("test_cvjl", schema).unwrap();
engine
.insert(
"test_cvjl",
HashMap::from([("meta".to_string(), Value::Json(serde_json::json!({"a": 1})))]),
)
.unwrap();
engine
.insert(
"test_cvjl",
HashMap::from([("meta".to_string(), Value::Json(serde_json::json!({"b": 2})))]),
)
.unwrap();
let col_data = engine.load_column_data("test_cvjl", "meta").unwrap();
assert_eq!(col_data.values.len(), 2);
}
#[test]
fn test_drop_fk_constraint_cleanup() {
let engine = RelationalEngine::new();
let parent_schema = Schema::with_constraints(
vec![Column::new("id", ColumnType::Int)],
vec![Constraint::PrimaryKey {
name: "pk_parent".to_string(),
columns: vec!["id".to_string()],
}],
);
engine.create_table("parent_dc", parent_schema).unwrap();
let child_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int),
]);
engine.create_table("child_dc", child_schema).unwrap();
engine
.add_constraint(
"child_dc",
Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_child_parent".to_string(),
columns: vec!["parent_id".to_string()],
referenced_table: "parent_dc".to_string(),
referenced_columns: vec!["id".to_string()],
on_delete: ReferentialAction::Cascade,
on_update: ReferentialAction::Cascade,
}),
)
.unwrap();
engine
.drop_constraint("child_dc", "fk_child_parent")
.unwrap();
let constraints = engine.get_constraints("child_dc").unwrap();
assert!(constraints.is_empty());
}
#[test]
fn test_batch_insert_id_hash_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("test_biihi", schema).unwrap();
engine.create_index("test_biihi", "_id").unwrap();
let rows: Vec<HashMap<String, Value>> = vec![
HashMap::from([("name".to_string(), Value::String("Alice".to_string()))]),
HashMap::from([("name".to_string(), Value::String("Bob".to_string()))]),
];
let row_ids = engine.batch_insert("test_biihi", rows).unwrap();
assert_eq!(row_ids.len(), 2);
}
#[test]
fn test_batch_insert_id_btree_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("test_biibi", schema).unwrap();
engine.create_btree_index("test_biibi", "_id").unwrap();
let rows: Vec<HashMap<String, Value>> = vec![
HashMap::from([("name".to_string(), Value::String("Alice".to_string()))]),
HashMap::from([("name".to_string(), Value::String("Bob".to_string()))]),
];
let row_ids = engine.batch_insert("test_biibi", rows).unwrap();
assert_eq!(row_ids.len(), 2);
}
#[test]
fn test_update_error_rollback() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("test_uer", schema).unwrap();
engine
.insert(
"test_uer",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("Alice".to_string())),
]),
)
.unwrap();
let result = engine.update(
"test_uer",
Condition::True,
HashMap::from([("id".to_string(), Value::String("not_an_int".to_string()))]),
);
assert!(result.is_err());
let rows = engine.select("test_uer", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("id"), Some(&Value::Int(1)));
}
#[test]
fn test_slab_filter_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_sfet", schema).unwrap();
engine.materialize_columns("test_sfet", &["id"]).unwrap();
let rows = engine
.select("test_sfet", Condition::Eq("id".to_string(), Value::Int(1)))
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_select_columnar_empty_prefer_columnar() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_scet", schema).unwrap();
let rows = engine
.select_columnar(
"test_scet",
Condition::True,
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_with_store() {
let store = tensor_store::TensorStore::new();
let engine = RelationalEngine::with_store(store);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_ws", schema).unwrap();
assert!(engine.table_exists("test_ws"));
}
#[test]
fn test_with_store_and_config_max_tables() {
let store = tensor_store::TensorStore::new();
let config = RelationalConfig::new().with_max_tables(50);
let engine = RelationalEngine::with_store_and_config(store, config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_wsac", schema).unwrap();
assert!(engine.table_exists("test_wsac"));
assert_eq!(engine.config().max_tables, Some(50));
}
#[test]
fn test_slab_filter_rows_le_float() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Float)]);
engine.create_table("test_sfrlef", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfrlef",
HashMap::from([("value".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
engine
.materialize_columns("test_sfrlef", &["value"])
.unwrap();
let rows = engine
.select(
"test_sfrlef",
Condition::Le("value".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 6);
}
#[test]
fn test_slab_filter_rows_ge_float() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Float)]);
engine.create_table("test_sfrgef", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfrgef",
HashMap::from([("value".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
engine
.materialize_columns("test_sfrgef", &["value"])
.unwrap();
let rows = engine
.select(
"test_sfrgef",
Condition::Ge("value".to_string(), Value::Float(5.0)),
)
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_slab_filter_rows_ne_float() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Float)]);
engine.create_table("test_sfrnef", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test_sfrnef",
HashMap::from([("value".to_string(), Value::Float(f64::from(i)))]),
)
.unwrap();
}
engine
.materialize_columns("test_sfrnef", &["value"])
.unwrap();
let rows = engine
.select(
"test_sfrnef",
Condition::Ne("value".to_string(), Value::Float(2.0)),
)
.unwrap();
assert_eq!(rows.len(), 4);
}
#[test]
fn test_slab_filter_and_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
]);
engine.create_table("test_sfac", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfac",
HashMap::from([
("a".to_string(), Value::Int(i)),
("b".to_string(), Value::Int(i % 3)),
]),
)
.unwrap();
}
engine
.materialize_columns("test_sfac", &["a", "b"])
.unwrap();
let rows = engine
.select(
"test_sfac",
Condition::And(
Box::new(Condition::Gt("a".to_string(), Value::Int(3))),
Box::new(Condition::Eq("b".to_string(), Value::Int(0))),
),
)
.unwrap();
assert!(!rows.is_empty());
}
#[test]
fn test_slab_filter_or_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_sfoc", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfoc",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine.materialize_columns("test_sfoc", &["id"]).unwrap();
let rows = engine
.select(
"test_sfoc",
Condition::Or(
Box::new(Condition::Eq("id".to_string(), Value::Int(1))),
Box::new(Condition::Eq("id".to_string(), Value::Int(8))),
),
)
.unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn test_select_columnar_no_filter_with_projection() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("test_scnf", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test_scnf",
HashMap::from([
("id".to_string(), Value::Int(i)),
("name".to_string(), Value::String(format!("name{i}"))),
]),
)
.unwrap();
}
engine
.materialize_columns("test_scnf", &["id", "name"])
.unwrap();
let rows = engine
.select_columnar(
"test_scnf",
Condition::True,
ColumnarScanOptions {
projection: Some(vec!["name".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_column_values_is_empty() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_cvie", schema).unwrap();
let col_data = engine.load_column_data("test_cvie", "id").unwrap();
assert!(col_data.values.is_empty());
engine
.insert(
"test_cvie",
HashMap::from([("id".to_string(), Value::Int(1))]),
)
.unwrap();
let col_data = engine.load_column_data("test_cvie", "id").unwrap();
assert!(!col_data.values.is_empty());
}
#[test]
fn test_select_result_limit_exceeded() {
let config = RelationalConfig::new().with_max_query_result_rows(5);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_srtl", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_srtl",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let result = engine.select("test_srtl", Condition::True);
assert!(result.is_err());
match result.unwrap_err() {
RelationalError::ResultTooLarge {
operation,
actual,
max,
} => {
assert_eq!(operation, "select");
assert_eq!(actual, 10);
assert_eq!(max, 5);
},
e => panic!("unexpected error: {e:?}"),
}
}
#[test]
fn test_column_data_bytes_get_value() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("blob", ColumnType::Bytes)]);
engine.create_table("test_cdbgv", schema).unwrap();
engine
.insert(
"test_cdbgv",
HashMap::from([("blob".to_string(), Value::Bytes(vec![1, 2, 3]))]),
)
.unwrap();
let col_data = engine.load_column_data("test_cdbgv", "blob").unwrap();
let val = col_data.get_value(0);
assert!(matches!(val, Some(Value::Bytes(_))));
}
#[test]
fn test_column_data_json_get_value() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("meta", ColumnType::Json)]);
engine.create_table("test_cdjgv", schema).unwrap();
engine
.insert(
"test_cdjgv",
HashMap::from([(
"meta".to_string(),
Value::Json(serde_json::json!({"key": "value"})),
)]),
)
.unwrap();
let col_data = engine.load_column_data("test_cdjgv", "meta").unwrap();
let val = col_data.get_value(0);
assert!(matches!(val, Some(Value::Json(_))));
}
#[test]
fn test_slab_filter_with_deleted_rows() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_sfwdr", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_sfwdr",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine
.delete_rows("test_sfwdr", Condition::Lt("id".to_string(), Value::Int(5)))
.unwrap();
engine.materialize_columns("test_sfwdr", &["id"]).unwrap();
let rows = engine.select("test_sfwdr", Condition::True).unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_indexed_select_with_slab() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_isws", schema).unwrap();
engine.create_index("test_isws", "id").unwrap();
for i in 0..10 {
engine
.insert(
"test_isws",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
engine.materialize_columns("test_isws", &["id"]).unwrap();
let rows = engine
.select("test_isws", Condition::Eq("id".to_string(), Value::Int(5)))
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("id"), Some(&Value::Int(5)));
}
#[test]
fn test_join_with_timeout_option() {
let engine = RelationalEngine::new();
let schema1 = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("left_jwo", schema1).unwrap();
let schema2 = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("right_jwo", schema2).unwrap();
engine
.insert(
"left_jwo",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("Alice".to_string())),
]),
)
.unwrap();
engine
.insert(
"right_jwo",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(100)),
]),
)
.unwrap();
let result = engine.join_with_options(
"left_jwo",
"right_jwo",
"id",
"id",
QueryOptions::new().with_timeout_ms(60000),
);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_update_with_timeout_option() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("test_uwo", schema).unwrap();
engine
.insert(
"test_uwo",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(10)),
]),
)
.unwrap();
let count = engine
.update_with_options(
"test_uwo",
Condition::True,
HashMap::from([("value".to_string(), Value::Int(20))]),
QueryOptions::new().with_timeout_ms(60000),
)
.unwrap();
assert_eq!(count, 1);
}
#[test]
fn test_delete_with_timeout_option() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_dwo", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_dwo",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let count = engine
.delete_rows_with_options(
"test_dwo",
Condition::Lt("id".to_string(), Value::Int(5)),
QueryOptions::new().with_timeout_ms(60000),
)
.unwrap();
assert_eq!(count, 5);
}
#[test]
fn test_vectorized_filter_type_mismatch() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("test_vftm", schema).unwrap();
engine
.insert(
"test_vftm",
HashMap::from([("name".to_string(), Value::String("Alice".to_string()))]),
)
.unwrap();
engine.materialize_columns("test_vftm", &["name"]).unwrap();
let rows = engine
.select(
"test_vftm",
Condition::Eq("name".to_string(), Value::Int(1)),
)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_columnar_bool_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("active", ColumnType::Bool),
]);
engine.create_table("test_cbc", schema).unwrap();
for i in 0..10 {
engine
.insert(
"test_cbc",
HashMap::from([
("id".to_string(), Value::Int(i)),
("active".to_string(), Value::Bool(i % 2 == 0)),
]),
)
.unwrap();
}
engine
.materialize_columns("test_cbc", &["id", "active"])
.unwrap();
let rows = engine
.select(
"test_cbc",
Condition::Eq("active".to_string(), Value::Bool(true)),
)
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_select_columnar_with_projection_filter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("value", ColumnType::Int),
]);
engine.create_table("test_scwp", schema).unwrap();
for i in 0..5 {
engine
.insert(
"test_scwp",
HashMap::from([
("id".to_string(), Value::Int(i)),
("name".to_string(), Value::String(format!("name{i}"))),
("value".to_string(), Value::Int(i * 10)),
]),
)
.unwrap();
}
engine
.materialize_columns("test_scwp", &["id", "name", "value"])
.unwrap();
let rows = engine
.select_columnar(
"test_scwp",
Condition::Gt("id".to_string(), Value::Int(2)),
ColumnarScanOptions {
projection: Some(vec!["name".to_string()]),
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn test_config_preset_high_throughput() {
let config = RelationalConfig::high_throughput();
assert!(config.max_tables.is_none());
assert!(config.max_indexes_per_table.is_none());
assert_eq!(config.max_btree_entries, 20_000_000);
assert_eq!(config.default_query_timeout_ms, Some(30_000));
assert_eq!(config.max_query_timeout_ms, Some(600_000));
assert_eq!(config.slow_query_threshold_ms, 50);
assert!(config.max_query_result_rows.is_none());
assert_eq!(config.transaction_timeout_secs, 120);
assert_eq!(config.lock_timeout_secs, 60);
let engine = RelationalEngine::with_config(config);
assert!(engine.config().max_tables.is_none());
}
#[test]
fn test_config_preset_low_memory() {
let config = RelationalConfig::low_memory();
assert_eq!(config.max_tables, Some(100));
assert_eq!(config.max_indexes_per_table, Some(5));
assert_eq!(config.max_btree_entries, 1_000_000);
assert_eq!(config.default_query_timeout_ms, Some(10_000));
assert_eq!(config.max_query_timeout_ms, Some(60_000));
assert_eq!(config.slow_query_threshold_ms, 100);
assert_eq!(config.max_query_result_rows, Some(10_000));
assert_eq!(config.transaction_timeout_secs, 30);
assert_eq!(config.lock_timeout_secs, 15);
let engine = RelationalEngine::with_config(config);
assert_eq!(engine.config().max_tables, Some(100));
}
#[test]
fn test_selection_vector_boundary_64_bit() {
let sv = SelectionVector::all(64);
assert_eq!(sv.count(), 64);
assert!(sv.is_selected(63));
assert!(!sv.is_selected(64));
}
#[test]
fn test_selection_vector_bitmap_accessors_coverage() {
let mut sv = SelectionVector::from_bitmap(vec![0b1111_0000u64], 8);
assert_eq!(sv.bitmap().len(), 1);
sv.bitmap_mut()[0] = 0b0000_1111;
assert!(sv.is_selected(0));
assert!(!sv.is_selected(4));
}
#[test]
fn test_column_data_null_tracking() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int).nullable(),
]);
engine.create_table("null_test", schema).unwrap();
engine
.insert(
"null_test",
HashMap::from([("id".to_string(), Value::Int(1))]),
)
.unwrap();
engine
.insert(
"null_test",
HashMap::from([
("id".to_string(), Value::Int(2)),
("value".to_string(), Value::Int(42)),
]),
)
.unwrap();
engine
.insert(
"null_test",
HashMap::from([("id".to_string(), Value::Int(3))]),
)
.unwrap();
engine
.materialize_columns("null_test", &["id", "value"])
.unwrap();
let col_data = engine.load_column_data("null_test", "value").unwrap();
assert_eq!(col_data.null_count(), 2); }
#[test]
fn test_column_data_get_value_all_types() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("int_col", ColumnType::Int),
Column::new("float_col", ColumnType::Float),
Column::new("str_col", ColumnType::String),
Column::new("bool_col", ColumnType::Bool),
Column::new("bytes_col", ColumnType::Bytes),
Column::new("json_col", ColumnType::Json),
]);
engine.create_table("all_types", schema).unwrap();
engine
.insert(
"all_types",
HashMap::from([
("int_col".to_string(), Value::Int(42)),
("float_col".to_string(), Value::Float(3.15)),
("str_col".to_string(), Value::String("hello".to_string())),
("bool_col".to_string(), Value::Bool(true)),
(
"bytes_col".to_string(),
Value::Bytes(vec![0x01, 0x02, 0x03]),
),
(
"json_col".to_string(),
Value::Json(serde_json::json!({"key": "value"})),
),
]),
)
.unwrap();
engine
.materialize_columns(
"all_types",
&[
"int_col",
"float_col",
"str_col",
"bool_col",
"bytes_col",
"json_col",
],
)
.unwrap();
let int_data = engine.load_column_data("all_types", "int_col").unwrap();
assert_eq!(int_data.get_value(0), Some(Value::Int(42)));
let float_data = engine.load_column_data("all_types", "float_col").unwrap();
assert_eq!(float_data.get_value(0), Some(Value::Float(3.15)));
let str_data = engine.load_column_data("all_types", "str_col").unwrap();
assert_eq!(
str_data.get_value(0),
Some(Value::String("hello".to_string()))
);
let bool_data = engine.load_column_data("all_types", "bool_col").unwrap();
assert_eq!(bool_data.get_value(0), Some(Value::Bool(true)));
let bytes_data = engine.load_column_data("all_types", "bytes_col").unwrap();
assert_eq!(
bytes_data.get_value(0),
Some(Value::Bytes(vec![0x01, 0x02, 0x03]))
);
let json_data = engine.load_column_data("all_types", "json_col").unwrap();
assert_eq!(
json_data.get_value(0),
Some(Value::Json(serde_json::json!({"key": "value"})))
);
}
#[test]
fn test_column_values_empty_and_len() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("empty_col", schema).unwrap();
engine.materialize_columns("empty_col", &["id"]).unwrap();
let col_data = engine.load_column_data("empty_col", "id").unwrap();
assert!(col_data.row_ids.is_empty());
}
#[test]
fn test_grouped_row_get_key_and_aggregate_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("category", ColumnType::String),
Column::new("amount", ColumnType::Int),
]);
engine.create_table("grouped_key_agg", schema).unwrap();
for i in 0..10 {
engine
.insert(
"grouped_key_agg",
HashMap::from([
(
"category".to_string(),
Value::String(if i % 2 == 0 { "A" } else { "B" }.to_string()),
),
("amount".to_string(), Value::Int(i * 10)),
]),
)
.unwrap();
}
let grouped = engine
.select_grouped(
"grouped_key_agg",
Condition::True,
&["category".to_string()],
&[
AggregateExpr::Sum("amount".to_string()),
AggregateExpr::Count("amount".to_string()),
],
None,
)
.unwrap();
for row in &grouped {
let key = row.get_key("category");
assert!(key.is_some());
let sum = row.get_aggregate("sum_amount");
assert!(sum.is_some());
let count = row.get_aggregate("count_amount");
assert!(count.is_some());
let nonexistent = row.get_aggregate("nonexistent");
assert!(nonexistent.is_none());
}
}
#[test]
fn test_condition_and_or_chaining() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..20 {
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String(format!("User{i}"))),
("age".to_string(), Value::Int(20 + i)),
]),
)
.unwrap();
}
let condition = Condition::Gt("age".to_string(), Value::Int(30))
.and(Condition::Lt("age".to_string(), Value::Int(35)))
.or(Condition::Eq("age".to_string(), Value::Int(25)));
let rows = engine.select("users", condition).unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_schema_constraints_accessor_coverage() {
let fk = ForeignKeyConstraint::new(
"fk_user_cov".to_string(),
vec!["user_id".to_string()],
"users".to_string(),
vec!["id".to_string()],
);
let schema = Schema::with_constraints(
vec![
Column::new("id", ColumnType::Int),
Column::new("user_id", ColumnType::Int),
],
vec![
Constraint::primary_key("pk_posts_cov", vec!["id".to_string()]),
Constraint::foreign_key(fk),
],
);
assert_eq!(schema.constraints().len(), 2);
}
#[test]
fn test_constraint_name_accessor() {
let pk = Constraint::primary_key("my_pk", vec!["id".to_string()]);
assert_eq!(pk.name(), "my_pk");
let unique = Constraint::unique("my_unique", vec!["email".to_string()]);
assert_eq!(unique.name(), "my_unique");
let not_null = Constraint::not_null("my_notnull", "name");
assert_eq!(not_null.name(), "my_notnull");
}
#[test]
fn test_foreign_key_referential_actions() {
let fk = ForeignKeyConstraint::new(
"fk_test".to_string(),
vec!["ref_id".to_string()],
"parent".to_string(),
vec!["id".to_string()],
)
.on_delete(ReferentialAction::Cascade)
.on_update(ReferentialAction::SetNull);
assert_eq!(fk.on_delete, ReferentialAction::Cascade);
assert_eq!(fk.on_update, ReferentialAction::SetNull);
}
#[test]
fn test_value_is_truthy_all_types() {
assert!(!Value::Null.is_truthy());
assert!(!Value::Bool(false).is_truthy());
assert!(Value::Bool(true).is_truthy());
assert!(!Value::Int(0).is_truthy());
assert!(Value::Int(1).is_truthy());
assert!(Value::Int(-1).is_truthy());
assert!(!Value::Float(0.0).is_truthy());
assert!(Value::Float(1.0).is_truthy());
assert!(!Value::String(String::new()).is_truthy());
assert!(Value::String("hello".to_string()).is_truthy());
assert!(!Value::Bytes(vec![]).is_truthy());
assert!(Value::Bytes(vec![1]).is_truthy());
assert!(Value::Json(serde_json::json!({})).is_truthy());
}
#[test]
fn test_row_contains_column() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String("Alice".to_string())),
("age".to_string(), Value::Int(30)),
]),
)
.unwrap();
let rows = engine.select("users", Condition::True).unwrap();
let row = &rows[0];
assert!(row.contains("name"));
assert!(row.contains("age"));
assert!(!row.contains("nonexistent"));
}
#[test]
fn test_query_options_default() {
let options = QueryOptions::default();
assert!(options.timeout_ms.is_none());
let options2 = QueryOptions::new().with_timeout_ms(5000);
assert_eq!(options2.timeout_ms, Some(5000));
}
#[test]
fn test_columnar_scan_options_defaults() {
let options = ColumnarScanOptions::default();
assert!(options.projection.is_none());
assert!(!options.prefer_columnar);
}
#[test]
fn test_config_with_all_options() {
let config = RelationalConfig::new()
.with_max_tables(50)
.with_max_indexes_per_table(10)
.with_default_timeout_ms(5000)
.with_max_timeout_ms(30000)
.with_max_btree_entries(500_000)
.with_slow_query_threshold_ms(200)
.with_max_query_result_rows(5000)
.with_transaction_timeout_secs(60)
.with_lock_timeout_secs(30);
assert_eq!(config.max_tables, Some(50));
assert_eq!(config.max_indexes_per_table, Some(10));
assert_eq!(config.default_query_timeout_ms, Some(5000));
assert_eq!(config.max_query_timeout_ms, Some(30000));
assert_eq!(config.max_btree_entries, 500_000);
assert_eq!(config.slow_query_threshold_ms, 200);
assert_eq!(config.max_query_result_rows, Some(5000));
assert_eq!(config.transaction_timeout_secs, 60);
assert_eq!(config.lock_timeout_secs, 30);
assert!(config.validate().is_ok());
}
#[test]
fn test_create_table_already_exists_error() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema.clone()).unwrap();
let result = engine.create_table("test", schema);
assert!(matches!(
result,
Err(RelationalError::TableAlreadyExists(_))
));
}
#[test]
fn test_insert_table_not_found_error() {
let engine = RelationalEngine::new();
let result = engine.insert(
"nonexistent",
HashMap::from([("id".to_string(), Value::Int(1))]),
);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_select_table_not_found_error() {
let engine = RelationalEngine::new();
let result = engine.select("nonexistent", Condition::True);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_btree_index_error_on_nonexistent_table() {
let engine = RelationalEngine::new();
let result = engine.create_btree_index("nonexistent", "id");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_has_btree_index_returns_false_for_missing() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
assert!(!engine.has_btree_index("test", "id"));
assert!(!engine.has_btree_index("nonexistent", "id"));
}
#[test]
fn test_drop_btree_index_not_found_error() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("btree_drop_test", schema).unwrap();
let result = engine.drop_btree_index("btree_drop_test", "id");
assert!(matches!(result, Err(RelationalError::IndexNotFound { .. })));
}
#[test]
fn test_has_columnar_data_nonexistent_table() {
let engine = RelationalEngine::new();
assert!(!engine.has_columnar_data("nonexistent", "id"));
}
#[test]
fn test_load_column_data_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.load_column_data("nonexistent", "id");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_add_column_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.add_column("nonexistent", Column::new("new_col", ColumnType::Int));
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_drop_column_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.drop_column("nonexistent", "col");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_rename_column_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.rename_column("nonexistent", "old", "new");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_get_constraints_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.get_constraints("nonexistent");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_add_constraint_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.add_constraint("nonexistent", Constraint::not_null("nn", "id".to_string()));
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_drop_constraint_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.drop_constraint("nonexistent", "constraint_name");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_tx_not_found_errors() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test", schema).unwrap();
let invalid_tx_id = 9999;
let result = engine.tx_insert(
invalid_tx_id,
"test",
HashMap::from([("id".to_string(), Value::Int(1))]),
);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
let result = engine.tx_update(
invalid_tx_id,
"test",
Condition::True,
HashMap::from([("id".to_string(), Value::Int(1))]),
);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
let result = engine.tx_delete(invalid_tx_id, "test", Condition::True);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
let result = engine.tx_select(invalid_tx_id, "test", Condition::True);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_commit_not_found_transaction() {
let engine = RelationalEngine::new();
let result = engine.commit(9999);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_rollback_not_found_transaction() {
let engine = RelationalEngine::new();
let result = engine.rollback(9999);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_column_type_bytes_in_slab() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes),
]);
engine.create_table("bytes_test", schema).unwrap();
let data = vec![0x00, 0x01, 0x02, 0xff];
engine
.insert(
"bytes_test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("data".to_string(), Value::Bytes(data.clone())),
]),
)
.unwrap();
let rows = engine.select("bytes_test", Condition::True).unwrap();
assert_eq!(rows[0].get("data"), Some(&Value::Bytes(data)));
}
#[test]
fn test_schema_add_constraint_coverage() {
let mut schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
assert!(schema.constraints.is_empty());
schema.add_constraint(Constraint::primary_key(
"pk_add_cov",
vec!["id".to_string()],
));
assert_eq!(schema.constraints.len(), 1);
}
#[test]
fn test_get_schema_not_found() {
let engine = RelationalEngine::new();
let result = engine.get_schema("nonexistent");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_row_count_not_found() {
let engine = RelationalEngine::new();
let result = engine.row_count("nonexistent");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_aggregate_count_column_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int).nullable(),
]);
engine.create_table("count_null_test", schema).unwrap();
engine
.insert(
"count_null_test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(10)),
]),
)
.unwrap();
engine
.insert(
"count_null_test",
HashMap::from([("id".to_string(), Value::Int(2))]),
)
.unwrap();
engine
.insert(
"count_null_test",
HashMap::from([
("id".to_string(), Value::Int(3)),
("value".to_string(), Value::Int(20)),
]),
)
.unwrap();
let count = engine
.count_column("count_null_test", "value", Condition::True)
.unwrap();
assert_eq!(count, 2); }
#[test]
fn test_sum_with_float_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("amount", ColumnType::Float)]);
engine.create_table("float_sum", schema).unwrap();
engine
.insert(
"float_sum",
HashMap::from([("amount".to_string(), Value::Float(1.5))]),
)
.unwrap();
engine
.insert(
"float_sum",
HashMap::from([("amount".to_string(), Value::Float(2.5))]),
)
.unwrap();
engine
.insert(
"float_sum",
HashMap::from([("amount".to_string(), Value::Float(3.0))]),
)
.unwrap();
let sum = engine.sum("float_sum", "amount", Condition::True).unwrap();
assert!((sum - 7.0).abs() < f64::EPSILON);
}
#[test]
fn test_avg_empty_table_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("empty_avg_cov", schema).unwrap();
let avg = engine
.avg("empty_avg_cov", "value", Condition::True)
.unwrap();
assert!(avg.is_none());
}
#[test]
fn test_min_max_with_strings() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("name", ColumnType::String)]);
engine.create_table("str_minmax", schema).unwrap();
engine
.insert(
"str_minmax",
HashMap::from([("name".to_string(), Value::String("Charlie".to_string()))]),
)
.unwrap();
engine
.insert(
"str_minmax",
HashMap::from([("name".to_string(), Value::String("Alice".to_string()))]),
)
.unwrap();
engine
.insert(
"str_minmax",
HashMap::from([("name".to_string(), Value::String("Bob".to_string()))]),
)
.unwrap();
let min = engine.min("str_minmax", "name", Condition::True).unwrap();
assert_eq!(min, Some(Value::String("Alice".to_string())));
let max = engine.max("str_minmax", "name", Condition::True).unwrap();
assert_eq!(max, Some(Value::String("Charlie".to_string())));
}
#[test]
fn test_min_max_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("value", ColumnType::Int)]);
engine.create_table("empty_minmax", schema).unwrap();
let min = engine
.min("empty_minmax", "value", Condition::True)
.unwrap();
assert!(min.is_none());
let max = engine
.max("empty_minmax", "value", Condition::True)
.unwrap();
assert!(max.is_none());
}
#[test]
fn test_select_with_projection() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("proj_test", schema).unwrap();
engine
.insert(
"proj_test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("Alice".to_string())),
("age".to_string(), Value::Int(30)),
]),
)
.unwrap();
let rows = engine
.select_with_projection("proj_test", Condition::True, Some(vec!["name".to_string()]))
.unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].contains("name"));
}
#[test]
fn test_left_right_full_join_empty_tables() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::String),
]);
let schema_b = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("ref_id", ColumnType::Int),
]);
engine.create_table("join_empty_a", schema_a).unwrap();
engine.create_table("join_empty_b", schema_b).unwrap();
let left = engine
.left_join("join_empty_a", "join_empty_b", "id", "id")
.unwrap();
assert!(left.is_empty());
let right = engine
.right_join("join_empty_a", "join_empty_b", "id", "id")
.unwrap();
assert!(right.is_empty());
let full = engine
.full_join("join_empty_a", "join_empty_b", "id", "id")
.unwrap();
assert!(full.is_empty());
}
#[test]
fn test_cross_join_result_size() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![Column::new("a", ColumnType::Int)]);
let schema_b = Schema::new(vec![Column::new("b", ColumnType::Int)]);
engine.create_table("cross_a", schema_a).unwrap();
engine.create_table("cross_b", schema_b).unwrap();
for i in 0..3 {
engine
.insert("cross_a", HashMap::from([("a".to_string(), Value::Int(i))]))
.unwrap();
}
for i in 0..4 {
engine
.insert("cross_b", HashMap::from([("b".to_string(), Value::Int(i))]))
.unwrap();
}
let result = engine.cross_join("cross_a", "cross_b").unwrap();
assert_eq!(result.len(), 12); }
#[test]
fn test_natural_join_matching_columns() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
let schema_b = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("salary", ColumnType::Int),
]);
engine.create_table("nat_a", schema_a).unwrap();
engine.create_table("nat_b", schema_b).unwrap();
engine
.insert(
"nat_a",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("Alice".to_string())),
]),
)
.unwrap();
engine
.insert(
"nat_a",
HashMap::from([
("id".to_string(), Value::Int(2)),
("name".to_string(), Value::String("Bob".to_string())),
]),
)
.unwrap();
engine
.insert(
"nat_b",
HashMap::from([
("id".to_string(), Value::Int(1)),
("salary".to_string(), Value::Int(50000)),
]),
)
.unwrap();
engine
.insert(
"nat_b",
HashMap::from([
("id".to_string(), Value::Int(3)),
("salary".to_string(), Value::Int(60000)),
]),
)
.unwrap();
let result = engine.natural_join("nat_a", "nat_b").unwrap();
assert_eq!(result.len(), 1); }
#[test]
fn test_with_store_and_config_coverage() {
let store = TensorStore::new();
let config = RelationalConfig::new().with_max_tables(15);
let engine = RelationalEngine::with_store_and_config(store, config);
assert_eq!(engine.config().max_tables, Some(15));
}
#[test]
fn test_condition_ne_evaluation() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("status", ColumnType::String),
]);
engine.create_table("ne_test", schema).unwrap();
engine
.insert(
"ne_test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("status".to_string(), Value::String("active".to_string())),
]),
)
.unwrap();
engine
.insert(
"ne_test",
HashMap::from([
("id".to_string(), Value::Int(2)),
("status".to_string(), Value::String("inactive".to_string())),
]),
)
.unwrap();
engine
.insert(
"ne_test",
HashMap::from([
("id".to_string(), Value::Int(3)),
("status".to_string(), Value::String("active".to_string())),
]),
)
.unwrap();
let rows = engine
.select(
"ne_test",
Condition::Ne("status".to_string(), Value::String("active".to_string())),
)
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("id"), Some(&Value::Int(2)));
}
#[test]
fn test_update_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.update(
"nonexistent",
Condition::True,
HashMap::from([("id".to_string(), Value::Int(1))]),
);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_delete_rows_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.delete_rows("nonexistent", Condition::True);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_join_table_not_found() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("join_existing", schema).unwrap();
let result = engine.join("join_existing", "nonexistent", "id", "id");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
let result = engine.join("nonexistent", "join_existing", "id", "id");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_aggregates_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.count("nonexistent", Condition::True);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
let result = engine.sum("nonexistent", "col", Condition::True);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
let result = engine.avg("nonexistent", "col", Condition::True);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
let result = engine.min("nonexistent", "col", Condition::True);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
let result = engine.max("nonexistent", "col", Condition::True);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_materialize_columns_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.materialize_columns("nonexistent", &["id"]);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_select_columnar_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.select_columnar(
"nonexistent",
Condition::True,
ColumnarScanOptions::default(),
);
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_config_with_result_row_limit() {
let config = RelationalConfig::new().with_max_query_result_rows(100);
let engine = RelationalEngine::with_config(config);
create_users_table(&engine);
for i in 0..50 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 50);
}
#[test]
fn test_select_with_zero_timeout_triggers_error() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let options = QueryOptions::new().with_timeout_ms(0);
let result = engine.select_with_options("users", Condition::True, options);
assert!(matches!(result, Err(RelationalError::QueryTimeout { .. })));
}
#[test]
fn test_select_with_zero_timeout_many_rows() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..100 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
let options = QueryOptions::new().with_timeout_ms(0);
let result = engine.select_with_options("users", Condition::True, options);
assert!(matches!(result, Err(RelationalError::QueryTimeout { .. })));
}
#[test]
fn test_result_too_large_with_limit() {
let config = RelationalConfig::new().with_max_query_result_rows(5);
let engine = RelationalEngine::with_config(config);
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
let result = engine.select("users", Condition::True);
assert!(matches!(
result,
Err(RelationalError::ResultTooLarge { .. })
));
}
#[test]
fn test_result_at_exactly_limit() {
let config = RelationalConfig::new().with_max_query_result_rows(5);
let engine = RelationalEngine::with_config(config);
create_users_table(&engine);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
let result = engine.select("users", Condition::True);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 5);
}
#[test]
fn test_tx_insert_with_nonexistent_tx() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
let result = engine.tx_insert(99999, "users", values);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(99999))
));
}
#[test]
fn test_tx_update_with_nonexistent_tx() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(40));
let result = engine.tx_update(99999, "users", Condition::True, updates);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(99999))
));
}
#[test]
fn test_tx_delete_with_nonexistent_tx() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let result = engine.tx_delete(99999, "users", Condition::True);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(99999))
));
}
#[test]
fn test_tx_select_with_nonexistent_tx() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let result = engine.tx_select(99999, "users", Condition::True);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(99999))
));
}
#[test]
fn test_columnar_eq_int_filter() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i * 10));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions::default();
let result = engine.select_columnar(
"users",
Condition::Eq("age".to_string(), Value::Int(50)),
options,
);
assert!(result.is_ok());
let rows = result.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_columnar_ne_int_filter() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i * 10));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions::default();
let result = engine.select_columnar(
"users",
Condition::Ne("age".to_string(), Value::Int(20)),
options,
);
assert!(result.is_ok());
let rows = result.unwrap();
assert_eq!(rows.len(), 4); }
#[test]
fn test_columnar_lt_int_filter() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i * 10));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions::default();
let result = engine.select_columnar(
"users",
Condition::Lt("age".to_string(), Value::Int(25)),
options,
);
assert!(result.is_ok());
let rows = result.unwrap();
assert_eq!(rows.len(), 3); }
#[test]
fn test_columnar_gt_int_filter() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i * 10));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions::default();
let result = engine.select_columnar(
"users",
Condition::Gt("age".to_string(), Value::Int(25)),
options,
);
assert!(result.is_ok());
let rows = result.unwrap();
assert_eq!(rows.len(), 2); }
#[test]
fn test_columnar_le_int_filter() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i * 10));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions::default();
let result = engine.select_columnar(
"users",
Condition::Le("age".to_string(), Value::Int(20)),
options,
);
assert!(result.is_ok());
let rows = result.unwrap();
assert_eq!(rows.len(), 3); }
#[test]
fn test_columnar_ge_int_filter() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i * 10));
engine.insert("users", values).unwrap();
}
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions::default();
let result = engine.select_columnar(
"users",
Condition::Ge("age".to_string(), Value::Int(20)),
options,
);
assert!(result.is_ok());
let rows = result.unwrap();
assert_eq!(rows.len(), 3); }
#[test]
fn test_columnar_float_filters() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("score", ColumnType::Float),
]);
engine.create_table("scores", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("score".to_string(), Value::Float(f64::from(i) * 1.5));
engine.insert("scores", values).unwrap();
}
engine.materialize_columns("scores", &["score"]).unwrap();
let options = ColumnarScanOptions::default();
let result = engine.select_columnar(
"scores",
Condition::Lt("score".to_string(), Value::Float(4.0)),
options.clone(),
);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 3);
let result = engine.select_columnar(
"scores",
Condition::Gt("score".to_string(), Value::Float(3.0)),
options.clone(),
);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 2);
let result = engine.select_columnar(
"scores",
Condition::Eq("score".to_string(), Value::Float(3.0)),
options,
);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_columnar_type_mismatch_fallback() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
engine.materialize_columns("users", &["age"]).unwrap();
let options = ColumnarScanOptions::default();
let result = engine.select_columnar(
"users",
Condition::Eq("age".to_string(), Value::Float(30.0)),
options,
);
assert!(result.is_ok() || matches!(result, Err(RelationalError::TypeMismatch { .. })));
}
#[test]
fn test_condition_and_both_true() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let result = engine.select(
"users",
Condition::And(Box::new(Condition::True), Box::new(Condition::True)),
);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_condition_or_both_matching() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let result = engine.select(
"users",
Condition::Or(
Box::new(Condition::True),
Box::new(Condition::Eq("age".to_string(), Value::Int(30))),
),
);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_condition_ne_filter() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..5 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i * 10));
engine.insert("users", values).unwrap();
}
let result = engine.select("users", Condition::Ne("age".to_string(), Value::Int(20)));
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 4);
}
#[test]
fn test_tx_count_increments_decrements() {
let engine = RelationalEngine::new();
assert_eq!(engine.active_transaction_count(), 0);
let tx1 = engine.begin_transaction();
assert_eq!(engine.active_transaction_count(), 1);
let tx2 = engine.begin_transaction();
assert_eq!(engine.active_transaction_count(), 2);
engine.commit(tx1).unwrap();
assert_eq!(engine.active_transaction_count(), 1);
engine.rollback(tx2).unwrap();
assert_eq!(engine.active_transaction_count(), 0);
}
#[test]
fn test_hash_index_with_timeout_option() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
engine.create_index("users", "age").unwrap();
let options = QueryOptions::new().with_timeout_ms(5000);
let result = engine.select_with_options(
"users",
Condition::Eq("age".to_string(), Value::Int(5)),
options,
);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_btree_index_with_timeout_option() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..10 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users", values).unwrap();
}
engine.create_btree_index("users", "age").unwrap();
let options = QueryOptions::new().with_timeout_ms(5000);
let result = engine.select_with_options(
"users",
Condition::Lt("age".to_string(), Value::Int(5)),
options,
);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 5);
}
#[test]
fn test_tx_insert_maintains_hash_index() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_index("users", "name").unwrap();
let tx_id = engine.begin_transaction();
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.tx_insert(tx_id, "users", values).unwrap();
engine.commit(tx_id).unwrap();
let result = engine.select(
"users",
Condition::Eq("name".to_string(), Value::String("Alice".to_string())),
);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_tx_insert_maintains_btree_index() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine.create_btree_index("users", "age").unwrap();
let tx_id = engine.begin_transaction();
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.tx_insert(tx_id, "users", values).unwrap();
engine.commit(tx_id).unwrap();
let result = engine.select("users", Condition::Ge("age".to_string(), Value::Int(25)));
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_tx_update_maintains_indexes() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
engine.create_index("users", "age").unwrap();
engine.create_btree_index("users", "age").unwrap();
let tx_id = engine.begin_transaction();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(40));
engine
.tx_update(
tx_id,
"users",
Condition::Eq("name".to_string(), Value::String("Alice".to_string())),
updates,
)
.unwrap();
engine.commit(tx_id).unwrap();
let result = engine.select("users", Condition::Eq("age".to_string(), Value::Int(30)));
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
let result = engine.select("users", Condition::Eq("age".to_string(), Value::Int(40)));
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_tx_delete_removes_index_entries() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
engine.create_index("users", "age").unwrap();
engine.create_btree_index("users", "age").unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_delete(
tx_id,
"users",
Condition::Eq("name".to_string(), Value::String("Alice".to_string())),
)
.unwrap();
engine.commit(tx_id).unwrap();
let result = engine.select("users", Condition::Eq("age".to_string(), Value::Int(30)));
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_tx_rollback_restores_hash_index_entry() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
engine.create_index("users", "age").unwrap();
let tx_id = engine.begin_transaction();
engine.tx_delete(tx_id, "users", Condition::True).unwrap();
let result = engine.select("users", Condition::Eq("age".to_string(), Value::Int(30)));
assert!(result.unwrap().is_empty());
engine.rollback(tx_id).unwrap();
let result = engine.select("users", Condition::Eq("age".to_string(), Value::Int(30)));
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_tx_rollback_restores_btree_index_entry() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
engine.create_btree_index("users", "age").unwrap();
let tx_id = engine.begin_transaction();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(50));
engine
.tx_update(tx_id, "users", Condition::True, updates)
.unwrap();
engine.rollback(tx_id).unwrap();
let result = engine.select("users", Condition::Le("age".to_string(), Value::Int(35)));
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_schema_get_column_by_name() {
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String).nullable(),
]);
assert!(schema.get_column("id").is_some());
assert!(schema.get_column("name").is_some());
assert!(schema.get_column("nonexistent").is_none());
}
#[test]
fn test_schema_with_primary_key_constraint() {
let schema = Schema::with_constraints(
vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
],
vec![Constraint::PrimaryKey {
name: "pk_id".to_string(),
columns: vec!["id".to_string()],
}],
);
assert_eq!(schema.constraints().len(), 1);
}
#[test]
fn test_bytes_column_roundtrip() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes),
]);
engine.create_table("binary", schema).unwrap();
let data = vec![0u8, 1, 2, 3, 255, 254];
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("data".to_string(), Value::Bytes(data.clone()));
engine.insert("binary", values).unwrap();
let rows = engine.select("binary", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("data"), Some(&Value::Bytes(data)));
}
#[test]
fn test_json_column_roundtrip() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("metadata", ColumnType::Json),
]);
engine.create_table("jsondata", schema).unwrap();
let json_value: serde_json::Value = serde_json::json!({"key": "value", "count": 42});
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("metadata".to_string(), Value::Json(json_value.clone()));
engine.insert("jsondata", values).unwrap();
let rows = engine.select("jsondata", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("metadata"), Some(&Value::Json(json_value)));
}
#[test]
fn test_parallel_join_large_tables() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::String),
]);
engine.create_table("large_a", schema_a).unwrap();
let schema_b = Schema::new(vec![
Column::new("key", ColumnType::Int),
Column::new("data", ColumnType::String),
]);
engine.create_table("large_b", schema_b).unwrap();
for i in 0..1001 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::String(format!("a{i}")));
engine.insert("large_a", values).unwrap();
}
for i in 0..100 {
let mut values = HashMap::new();
values.insert("key".to_string(), Value::Int(i * 10));
values.insert("data".to_string(), Value::String(format!("b{i}")));
engine.insert("large_b", values).unwrap();
}
let result = engine.join("large_a", "large_b", "id", "key").unwrap();
assert_eq!(result.len(), 100);
}
#[test]
fn test_parallel_sum_large_dataset() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("amount", ColumnType::Float),
]);
engine.create_table("amounts", schema).unwrap();
for i in 0..1001 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("amount".to_string(), Value::Float(i as f64));
engine.insert("amounts", values).unwrap();
}
let sum = engine.sum("amounts", "amount", Condition::True).unwrap();
assert!((sum - 500_500.0).abs() < 0.01);
}
#[test]
fn test_parallel_avg_large_dataset() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Float),
]);
engine.create_table("avg_data", schema).unwrap();
for i in 0..1001 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Float(i as f64));
engine.insert("avg_data", values).unwrap();
}
let avg = engine
.avg("avg_data", "value", Condition::True)
.unwrap()
.unwrap();
assert!((avg - 500.0).abs() < 0.01);
}
#[test]
fn test_parallel_min_large_dataset() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]);
engine.create_table("scores_min", schema).unwrap();
for i in 0..1001 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("score".to_string(), Value::Int(i + 100));
engine.insert("scores_min", values).unwrap();
}
let min = engine.min("scores_min", "score", Condition::True).unwrap();
assert_eq!(min, Some(Value::Int(100)));
}
#[test]
fn test_parallel_max_large_dataset() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]);
engine.create_table("scores_max", schema).unwrap();
for i in 0..1001 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("score".to_string(), Value::Int(i));
engine.insert("scores_max", values).unwrap();
}
let max = engine.max("scores_max", "score", Condition::True).unwrap();
assert_eq!(max, Some(Value::Int(1000)));
}
#[test]
fn test_delete_with_zero_timeout() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let result = engine.delete_rows_with_options(
"users",
Condition::True,
QueryOptions::new().with_timeout_ms(0),
);
assert!(result.is_err());
if let Err(RelationalError::QueryTimeout { operation, .. }) = result {
assert!(operation.contains("delete"));
}
}
#[test]
fn test_update_with_zero_timeout() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(31));
let result = engine.update_with_options(
"users",
Condition::True,
updates,
QueryOptions::new().with_timeout_ms(0),
);
assert!(result.is_err());
if let Err(RelationalError::QueryTimeout { operation, .. }) = result {
assert!(operation.contains("update"));
}
}
#[test]
fn test_materialize_bool_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("active", ColumnType::Bool),
]);
engine.create_table("bool_data", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("active".to_string(), Value::Bool(i % 2 == 0));
engine.insert("bool_data", values).unwrap();
}
engine
.materialize_columns("bool_data", &["active"])
.unwrap();
let rows = engine
.select_columnar("bool_data", Condition::True, ColumnarScanOptions::default())
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_materialize_bytes_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes),
]);
engine.create_table("bytes_mat", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("data".to_string(), Value::Bytes(vec![i as u8; 4]));
engine.insert("bytes_mat", values).unwrap();
}
engine.materialize_columns("bytes_mat", &["data"]).unwrap();
let rows = engine
.select_columnar("bytes_mat", Condition::True, ColumnarScanOptions::default())
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_materialize_json_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("meta", ColumnType::Json),
]);
engine.create_table("json_mat", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert(
"meta".to_string(),
Value::Json(serde_json::json!({"idx": i})),
);
engine.insert("json_mat", values).unwrap();
}
engine.materialize_columns("json_mat", &["meta"]).unwrap();
let rows = engine
.select_columnar("json_mat", Condition::True, ColumnarScanOptions::default())
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_materialize_string_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("label", ColumnType::String),
]);
engine.create_table("str_mat", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("label".to_string(), Value::String(format!("item_{i}")));
engine.insert("str_mat", values).unwrap();
}
engine.materialize_columns("str_mat", &["label"]).unwrap();
let rows = engine
.select_columnar("str_mat", Condition::True, ColumnarScanOptions::default())
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_materialize_column_with_nulls_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int).nullable(),
]);
engine.create_table("nulls_int", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 3 == 0 {
values.insert("value".to_string(), Value::Null);
} else {
values.insert("value".to_string(), Value::Int(i * 10));
}
engine.insert("nulls_int", values).unwrap();
}
engine.materialize_columns("nulls_int", &["value"]).unwrap();
let rows = engine
.select_columnar("nulls_int", Condition::True, ColumnarScanOptions::default())
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_materialize_column_with_nulls_float() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("amount", ColumnType::Float).nullable(),
]);
engine.create_table("nulls_float", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 2 == 0 {
values.insert("amount".to_string(), Value::Null);
} else {
values.insert("amount".to_string(), Value::Float(i as f64));
}
engine.insert("nulls_float", values).unwrap();
}
engine
.materialize_columns("nulls_float", &["amount"])
.unwrap();
let rows = engine
.select_columnar(
"nulls_float",
Condition::True,
ColumnarScanOptions::default(),
)
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_materialize_column_with_nulls_string() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String).nullable(),
]);
engine.create_table("nulls_str", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 4 == 0 {
values.insert("name".to_string(), Value::Null);
} else {
values.insert("name".to_string(), Value::String(format!("n{i}")));
}
engine.insert("nulls_str", values).unwrap();
}
engine.materialize_columns("nulls_str", &["name"]).unwrap();
let rows = engine
.select_columnar("nulls_str", Condition::True, ColumnarScanOptions::default())
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_materialize_column_with_nulls_bool() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("flag", ColumnType::Bool).nullable(),
]);
engine.create_table("nulls_bool", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 5 == 0 {
values.insert("flag".to_string(), Value::Null);
} else {
values.insert("flag".to_string(), Value::Bool(i % 2 == 1));
}
engine.insert("nulls_bool", values).unwrap();
}
engine.materialize_columns("nulls_bool", &["flag"]).unwrap();
let rows = engine
.select_columnar(
"nulls_bool",
Condition::True,
ColumnarScanOptions::default(),
)
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_materialize_column_with_nulls_bytes() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("blob", ColumnType::Bytes).nullable(),
]);
engine.create_table("nulls_bytes", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 3 == 0 {
values.insert("blob".to_string(), Value::Null);
} else {
values.insert("blob".to_string(), Value::Bytes(vec![i as u8]));
}
engine.insert("nulls_bytes", values).unwrap();
}
engine
.materialize_columns("nulls_bytes", &["blob"])
.unwrap();
let rows = engine
.select_columnar(
"nulls_bytes",
Condition::True,
ColumnarScanOptions::default(),
)
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_materialize_column_with_nulls_json() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("doc", ColumnType::Json).nullable(),
]);
engine.create_table("nulls_json", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 2 == 0 {
values.insert("doc".to_string(), Value::Null);
} else {
values.insert("doc".to_string(), Value::Json(serde_json::json!({"i": i})));
}
engine.insert("nulls_json", values).unwrap();
}
engine.materialize_columns("nulls_json", &["doc"]).unwrap();
let rows = engine
.select_columnar(
"nulls_json",
Condition::True,
ColumnarScanOptions::default(),
)
.unwrap();
assert_eq!(rows.len(), 10);
}
#[test]
fn test_drop_columnar_data_idempotent() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("drop_col", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Int(i * 2));
engine.insert("drop_col", values).unwrap();
}
engine.materialize_columns("drop_col", &["value"]).unwrap();
engine.drop_columnar_data("drop_col", "value").unwrap();
engine.drop_columnar_data("drop_col", "value").unwrap();
}
#[test]
fn test_drop_columnar_data_nonexistent() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("drop_none", schema).unwrap();
assert!(engine
.drop_columnar_data("drop_none", "nonexistent")
.is_ok());
}
#[test]
fn test_natural_join_empty_result() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("nat_a", schema_a).unwrap();
let schema_b = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("nat_b", schema_b).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("name".to_string(), Value::String("a".to_string()));
engine.insert("nat_a", values).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(2));
values.insert("value".to_string(), Value::Int(100));
engine.insert("nat_b", values).unwrap();
let result = engine.natural_join("nat_a", "nat_b").unwrap();
assert!(result.is_empty());
}
#[test]
fn test_btree_index_range_query_boundary() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]);
engine.create_table("btree_range", schema).unwrap();
for i in 0..20 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("score".to_string(), Value::Int(i));
engine.insert("btree_range", values).unwrap();
}
engine.create_btree_index("btree_range", "score").unwrap();
let rows = engine
.select(
"btree_range",
Condition::Ge("score".to_string(), Value::Int(19)),
)
.unwrap();
assert_eq!(rows.len(), 1);
let rows = engine
.select(
"btree_range",
Condition::Le("score".to_string(), Value::Int(0)),
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_result_too_large_error() {
let config = RelationalConfig::new().with_max_query_result_rows(5);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("limited", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("limited", values).unwrap();
}
let result = engine.select("limited", Condition::True);
assert!(matches!(
result,
Err(RelationalError::ResultTooLarge { max: 5, .. })
));
}
#[test]
fn test_cross_join_success() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![Column::new("a", ColumnType::Int)]);
engine.create_table("cross_a", schema_a).unwrap();
let schema_b = Schema::new(vec![Column::new("b", ColumnType::Int)]);
engine.create_table("cross_b", schema_b).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("a".to_string(), Value::Int(i));
engine.insert("cross_a", values).unwrap();
}
for i in 0..4 {
let mut values = HashMap::new();
values.insert("b".to_string(), Value::Int(i));
engine.insert("cross_b", values).unwrap();
}
let result = engine.cross_join("cross_a", "cross_b").unwrap();
assert_eq!(result.len(), 20);
}
#[test]
fn test_vectorized_filter_type_mismatch_int() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("type_mismatch", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("name".to_string(), Value::String("test".to_string()));
engine.insert("type_mismatch", values).unwrap();
engine
.materialize_columns("type_mismatch", &["name"])
.unwrap();
let options = ColumnarScanOptions {
prefer_columnar: true,
projection: None,
};
let result = engine.select_columnar(
"type_mismatch",
Condition::Eq("name".to_string(), Value::Int(1)),
options,
);
let _ = result;
}
#[test]
fn test_rollback_multiple_updates_same_row() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
engine.insert("users", values).unwrap();
let tx = engine.begin_transaction();
let mut update1 = HashMap::new();
update1.insert("age".to_string(), Value::Int(31));
engine
.tx_update(tx, "users", Condition::True, update1)
.unwrap();
let mut update2 = HashMap::new();
update2.insert("age".to_string(), Value::Int(32));
engine
.tx_update(tx, "users", Condition::True, update2)
.unwrap();
engine.rollback(tx).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows[0].get("age"), Some(&Value::Int(30)));
}
#[test]
fn test_rollback_insert_update_delete_sequence() {
let engine = RelationalEngine::new();
create_users_table(&engine);
let tx = engine.begin_transaction();
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Bob".to_string()));
values.insert("age".to_string(), Value::Int(25));
engine.tx_insert(tx, "users", values).unwrap();
let mut updates = HashMap::new();
updates.insert("age".to_string(), Value::Int(26));
engine
.tx_update(
tx,
"users",
Condition::Eq("name".to_string(), Value::String("Bob".to_string())),
updates,
)
.unwrap();
engine
.tx_delete(
tx,
"users",
Condition::Eq("name".to_string(), Value::String("Bob".to_string())),
)
.unwrap();
engine.rollback(tx).unwrap();
let rows = engine.select("users", Condition::True).unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_select_with_projection_subset() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
Column::new("email", ColumnType::String),
]);
engine.create_table("proj_test", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("age".to_string(), Value::Int(30));
values.insert(
"email".to_string(),
Value::String("alice@example.com".to_string()),
);
engine.insert("proj_test", values).unwrap();
let rows = engine
.select_with_projection(
"proj_test",
Condition::True,
Some(vec!["name".to_string(), "age".to_string()]),
)
.unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].get("name").is_some());
assert!(rows[0].get("age").is_some());
assert!(rows[0].get("email").is_none());
assert!(rows[0].get("id").is_none());
}
#[test]
fn test_count_with_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("count_cond", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Int(i));
engine.insert("count_cond", values).unwrap();
}
let count = engine
.count(
"count_cond",
Condition::Gt("value".to_string(), Value::Int(5)),
)
.unwrap();
assert_eq!(count, 4); }
#[test]
fn test_count_column_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int).nullable(),
]);
engine.create_table("count_null", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 2 == 0 {
values.insert("value".to_string(), Value::Int(i));
} else {
values.insert("value".to_string(), Value::Null);
}
engine.insert("count_null", values).unwrap();
}
let count = engine
.count_column("count_null", "value", Condition::True)
.unwrap();
assert_eq!(count, 5); }
#[test]
fn test_slab_filter_eq_int_empty_result() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("slab_empty", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Int(i));
engine.insert("slab_empty", values).unwrap();
}
let options = ColumnarScanOptions::default();
let rows = engine
.select_columnar(
"slab_empty",
Condition::Eq("value".to_string(), Value::Int(999)),
options,
)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_slab_filter_float_le() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("amount", ColumnType::Float),
]);
engine.create_table("float_le", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("amount".to_string(), Value::Float(i as f64));
engine.insert("float_le", values).unwrap();
}
let options = ColumnarScanOptions::default();
let rows = engine
.select_columnar(
"float_le",
Condition::Le("amount".to_string(), Value::Float(3.0)),
options,
)
.unwrap();
assert_eq!(rows.len(), 4); }
#[test]
fn test_slab_filter_float_ge() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("amount", ColumnType::Float),
]);
engine.create_table("float_ge", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("amount".to_string(), Value::Float(i as f64));
engine.insert("float_ge", values).unwrap();
}
let options = ColumnarScanOptions::default();
let rows = engine
.select_columnar(
"float_ge",
Condition::Ge("amount".to_string(), Value::Float(7.0)),
options,
)
.unwrap();
assert_eq!(rows.len(), 3); }
#[test]
fn test_slab_filter_float_ne() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("amount", ColumnType::Float),
]);
engine.create_table("float_ne", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("amount".to_string(), Value::Float(i as f64));
engine.insert("float_ne", values).unwrap();
}
let options = ColumnarScanOptions::default();
let rows = engine
.select_columnar(
"float_ne",
Condition::Ne("amount".to_string(), Value::Float(2.0)),
options,
)
.unwrap();
assert_eq!(rows.len(), 4); }
#[test]
fn test_slab_filter_and_condition_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("and_filter_cov", schema).unwrap();
for i in 0..20 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Int(i));
engine.insert("and_filter_cov", values).unwrap();
}
let condition = Condition::And(
Box::new(Condition::Gt("value".to_string(), Value::Int(5))),
Box::new(Condition::Lt("value".to_string(), Value::Int(15))),
);
let rows = engine
.select_columnar("and_filter_cov", condition, ColumnarScanOptions::default())
.unwrap();
assert_eq!(rows.len(), 9); }
#[test]
fn test_slab_filter_or_condition_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("or_filter_cov", schema).unwrap();
for i in 0..20 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Int(i));
engine.insert("or_filter_cov", values).unwrap();
}
let condition = Condition::Or(
Box::new(Condition::Lt("value".to_string(), Value::Int(3))),
Box::new(Condition::Gt("value".to_string(), Value::Int(17))),
);
let rows = engine
.select_columnar("or_filter_cov", condition, ColumnarScanOptions::default())
.unwrap();
assert_eq!(rows.len(), 5); }
#[test]
fn test_select_with_projection_and_filter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("proj_filter", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String(format!("user{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("proj_filter", values).unwrap();
}
let rows = engine
.select_with_projection(
"proj_filter",
Condition::Gt("age".to_string(), Value::Int(25)),
Some(vec!["name".to_string()]),
)
.unwrap();
assert_eq!(rows.len(), 4); for row in &rows {
assert!(row.get("name").is_some());
assert!(row.get("age").is_none()); assert!(row.get("id").is_none()); }
}
#[test]
fn test_batch_insert_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int).nullable(),
]);
engine.create_table("batch_nulls", schema).unwrap();
let rows: Vec<HashMap<String, Value>> = (0..5)
.map(|i| {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 2 == 0 {
values.insert("value".to_string(), Value::Null);
} else {
values.insert("value".to_string(), Value::Int(i * 10));
}
values
})
.collect();
engine.batch_insert("batch_nulls", rows).unwrap();
let result = engine.select("batch_nulls", Condition::True).unwrap();
assert_eq!(result.len(), 5);
}
#[test]
fn test_group_by_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("category", ColumnType::String).nullable(),
Column::new("value", ColumnType::Int),
]);
engine.create_table("group_nulls", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 3 == 0 {
values.insert("category".to_string(), Value::Null);
} else {
values.insert(
"category".to_string(),
Value::String(format!("cat{}", i % 2)),
);
}
values.insert("value".to_string(), Value::Int(i));
engine.insert("group_nulls", values).unwrap();
}
let groups = engine
.select_grouped(
"group_nulls",
Condition::True,
&["category".to_string()],
&[],
None,
)
.unwrap();
assert!(groups.len() >= 2);
}
#[test]
fn test_distinct_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int).nullable(),
]);
engine.create_table("distinct_nulls", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 3 == 0 {
values.insert("value".to_string(), Value::Null);
} else {
values.insert("value".to_string(), Value::Int(i % 2));
}
engine.insert("distinct_nulls", values).unwrap();
}
let distinct = engine
.select_distinct(
"distinct_nulls",
Condition::True,
Some(&["value".to_string()]),
)
.unwrap();
assert!(distinct.len() >= 2);
}
#[test]
fn test_min_max_empty_result() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("minmax_empty", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Int(i));
engine.insert("minmax_empty", values).unwrap();
}
let min = engine
.min(
"minmax_empty",
"value",
Condition::Gt("value".to_string(), Value::Int(100)),
)
.unwrap();
assert!(min.is_none());
let max = engine
.max(
"minmax_empty",
"value",
Condition::Gt("value".to_string(), Value::Int(100)),
)
.unwrap();
assert!(max.is_none());
}
#[test]
fn test_avg_empty_result() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Float),
]);
engine.create_table("avg_empty", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Float(i as f64));
engine.insert("avg_empty", values).unwrap();
}
let avg = engine
.avg(
"avg_empty",
"value",
Condition::Gt("value".to_string(), Value::Float(100.0)),
)
.unwrap();
assert!(avg.is_none());
}
#[test]
fn test_select_iter_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::String),
]);
engine.create_table("iter_cov", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::String(format!("val{i}")));
engine.insert("iter_cov", values).unwrap();
}
let iter = engine
.select_iter("iter_cov", Condition::True, CursorOptions::default())
.unwrap();
let collected: Vec<_> = iter.collect();
assert_eq!(collected.len(), 5);
}
#[test]
fn test_multiple_indexes_same_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("multi_idx", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Int(i * 10));
engine.insert("multi_idx", values).unwrap();
}
engine.create_index("multi_idx", "value").unwrap();
engine.create_btree_index("multi_idx", "value").unwrap();
let rows = engine
.select(
"multi_idx",
Condition::Eq("value".to_string(), Value::Int(50)),
)
.unwrap();
assert_eq!(rows.len(), 1);
let rows = engine
.select(
"multi_idx",
Condition::Ge("value".to_string(), Value::Int(70)),
)
.unwrap();
assert_eq!(rows.len(), 3);
}
#[test]
fn test_update_with_index_changes() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("status", ColumnType::String),
]);
engine.create_table("idx_update", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("status".to_string(), Value::String("active".to_string()));
engine.insert("idx_update", values).unwrap();
engine.create_index("idx_update", "status").unwrap();
let mut updates = HashMap::new();
updates.insert("status".to_string(), Value::String("inactive".to_string()));
engine
.update("idx_update", Condition::True, updates)
.unwrap();
let rows = engine
.select(
"idx_update",
Condition::Eq("status".to_string(), Value::String("active".to_string())),
)
.unwrap();
assert!(rows.is_empty());
let rows = engine
.select(
"idx_update",
Condition::Eq("status".to_string(), Value::String("inactive".to_string())),
)
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_delete_with_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("key", ColumnType::String),
]);
engine.create_table("idx_delete", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("key".to_string(), Value::String(format!("key{i}")));
engine.insert("idx_delete", values).unwrap();
}
engine.create_index("idx_delete", "key").unwrap();
engine
.delete_rows(
"idx_delete",
Condition::Eq("key".to_string(), Value::String("key2".to_string())),
)
.unwrap();
let rows = engine
.select(
"idx_delete",
Condition::Eq("key".to_string(), Value::String("key2".to_string())),
)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_join_on_non_matching_column() {
let engine = RelationalEngine::new();
let schema_a = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::String),
]);
engine.create_table("join_nm_a", schema_a).unwrap();
let schema_b = Schema::new(vec![
Column::new("key", ColumnType::Int),
Column::new("data", ColumnType::String),
]);
engine.create_table("join_nm_b", schema_b).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("val".to_string(), Value::String(format!("a{i}")));
engine.insert("join_nm_a", values).unwrap();
}
for i in 10..15 {
let mut values = HashMap::new();
values.insert("key".to_string(), Value::Int(i));
values.insert("data".to_string(), Value::String(format!("b{i}")));
engine.insert("join_nm_b", values).unwrap();
}
let result = engine.join("join_nm_a", "join_nm_b", "id", "key").unwrap();
assert!(result.is_empty());
}
#[test]
fn test_drop_table_basic() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("drop_me", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
engine.insert("drop_me", values).unwrap();
engine.create_index("drop_me", "id").unwrap();
engine.drop_table("drop_me").unwrap();
assert!(engine.get_schema("drop_me").is_err());
assert!(engine.select("drop_me", Condition::True).is_err());
}
#[test]
fn test_drop_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.drop_table("nonexistent");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_drop_table_with_btree_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("drop_btree", schema).unwrap();
engine.create_btree_index("drop_btree", "id").unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("drop_btree", values).unwrap();
}
engine.drop_table("drop_btree").unwrap();
assert!(engine.get_schema("drop_btree").is_err());
}
#[test]
fn test_add_column_success() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("add_col", schema).unwrap();
engine
.add_column(
"add_col",
Column::new("name", ColumnType::String).nullable(),
)
.unwrap();
let schema = engine.get_schema("add_col").unwrap();
assert_eq!(schema.columns.len(), 2);
assert!(schema.get_column("name").is_some());
}
#[test]
fn test_add_column_already_exists_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("add_dup_cov", schema).unwrap();
let result = engine.add_column("add_dup_cov", Column::new("id", ColumnType::Int));
assert!(matches!(
result,
Err(RelationalError::ColumnAlreadyExists { .. })
));
}
#[test]
fn test_add_non_nullable_column_with_rows() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("add_nn", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
engine.insert("add_nn", values).unwrap();
let result = engine.add_column("add_nn", Column::new("required", ColumnType::String));
assert!(matches!(
result,
Err(RelationalError::CannotAddColumn { .. })
));
}
#[test]
fn test_add_non_nullable_column_to_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("add_nn_empty", schema).unwrap();
engine
.add_column("add_nn_empty", Column::new("required", ColumnType::String))
.unwrap();
}
#[test]
fn test_drop_column_success() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("drop_col", schema).unwrap();
engine.drop_column("drop_col", "name").unwrap();
let schema = engine.get_schema("drop_col").unwrap();
assert_eq!(schema.columns.len(), 1);
assert!(schema.get_column("name").is_none());
}
#[test]
fn test_drop_column_not_found_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("drop_col_nf_cov", schema).unwrap();
let result = engine.drop_column("drop_col_nf_cov", "nonexistent");
assert!(matches!(result, Err(RelationalError::ColumnNotFound(_))));
}
#[test]
fn test_drop_column_with_pk_constraint_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::with_constraints(
vec![Column::new("id", ColumnType::Int)],
vec![Constraint::PrimaryKey {
name: "pk_id".to_string(),
columns: vec!["id".to_string()],
}],
);
engine.create_table("drop_col_pk_cov", schema).unwrap();
let result = engine.drop_column("drop_col_pk_cov", "id");
assert!(matches!(
result,
Err(RelationalError::ColumnHasConstraint { .. })
));
}
#[test]
fn test_drop_column_with_unique_constraint_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::with_constraints(
vec![Column::new("email", ColumnType::String)],
vec![Constraint::Unique {
name: "unique_email".to_string(),
columns: vec!["email".to_string()],
}],
);
engine.create_table("drop_col_uq_cov", schema).unwrap();
let result = engine.drop_column("drop_col_uq_cov", "email");
assert!(matches!(
result,
Err(RelationalError::ColumnHasConstraint { .. })
));
}
#[test]
fn test_drop_column_with_not_null_constraint_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::with_constraints(
vec![Column::new("name", ColumnType::String)],
vec![Constraint::NotNull {
name: "nn_name".to_string(),
column: "name".to_string(),
}],
);
engine.create_table("drop_col_nn_cov", schema).unwrap();
let result = engine.drop_column("drop_col_nn_cov", "name");
assert!(matches!(
result,
Err(RelationalError::ColumnHasConstraint { .. })
));
}
#[test]
fn test_drop_column_with_hash_index_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("drop_col_idx_cov", schema).unwrap();
engine.create_index("drop_col_idx_cov", "value").unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Int(i * 10));
engine.insert("drop_col_idx_cov", values).unwrap();
}
engine.drop_column("drop_col_idx_cov", "value").unwrap();
let schema = engine.get_schema("drop_col_idx_cov").unwrap();
assert!(schema.get_column("value").is_none());
}
#[test]
fn test_drop_column_with_btree_index_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]);
engine.create_table("drop_col_btree_cov", schema).unwrap();
engine
.create_btree_index("drop_col_btree_cov", "score")
.unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("score".to_string(), Value::Int(i * 10));
engine.insert("drop_col_btree_cov", values).unwrap();
}
engine.drop_column("drop_col_btree_cov", "score").unwrap();
let schema = engine.get_schema("drop_col_btree_cov").unwrap();
assert!(schema.get_column("score").is_none());
}
#[test]
fn test_rename_column_success() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("old_name", ColumnType::String)]);
engine.create_table("rename_col", schema).unwrap();
engine
.rename_column("rename_col", "old_name", "new_name")
.unwrap();
let schema = engine.get_schema("rename_col").unwrap();
assert!(schema.get_column("old_name").is_none());
assert!(schema.get_column("new_name").is_some());
}
#[test]
fn test_rename_column_not_found_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("rename_col_nf_cov", schema).unwrap();
let result = engine.rename_column("rename_col_nf_cov", "missing", "new");
assert!(matches!(result, Err(RelationalError::ColumnNotFound(_))));
}
#[test]
fn test_rename_column_target_exists_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("col_a", ColumnType::Int),
Column::new("col_b", ColumnType::Int),
]);
engine.create_table("rename_col_dup_cov", schema).unwrap();
let result = engine.rename_column("rename_col_dup_cov", "col_a", "col_b");
assert!(matches!(
result,
Err(RelationalError::ColumnAlreadyExists { .. })
));
}
#[test]
fn test_rename_column_updates_pk_constraint() {
let engine = RelationalEngine::new();
let schema = Schema::with_constraints(
vec![Column::new("old_id", ColumnType::Int)],
vec![Constraint::PrimaryKey {
name: "pk".to_string(),
columns: vec!["old_id".to_string()],
}],
);
engine.create_table("rename_pk", schema).unwrap();
engine
.rename_column("rename_pk", "old_id", "new_id")
.unwrap();
let schema = engine.get_schema("rename_pk").unwrap();
if let Some(Constraint::PrimaryKey { columns, .. }) = schema.constraints.first() {
assert_eq!(columns[0], "new_id");
} else {
panic!("Expected PrimaryKey constraint");
}
}
#[test]
fn test_rename_column_updates_unique_constraint() {
let engine = RelationalEngine::new();
let schema = Schema::with_constraints(
vec![Column::new("old_email", ColumnType::String)],
vec![Constraint::Unique {
name: "uq".to_string(),
columns: vec!["old_email".to_string()],
}],
);
engine.create_table("rename_uq", schema).unwrap();
engine
.rename_column("rename_uq", "old_email", "new_email")
.unwrap();
let schema = engine.get_schema("rename_uq").unwrap();
if let Some(Constraint::Unique { columns, .. }) = schema.constraints.first() {
assert_eq!(columns[0], "new_email");
} else {
panic!("Expected Unique constraint");
}
}
#[test]
fn test_rename_column_updates_not_null_constraint() {
let engine = RelationalEngine::new();
let schema = Schema::with_constraints(
vec![Column::new("old_name", ColumnType::String)],
vec![Constraint::NotNull {
name: "nn".to_string(),
column: "old_name".to_string(),
}],
);
engine.create_table("rename_nn", schema).unwrap();
engine
.rename_column("rename_nn", "old_name", "new_name")
.unwrap();
let schema = engine.get_schema("rename_nn").unwrap();
if let Some(Constraint::NotNull { column, .. }) = schema.constraints.first() {
assert_eq!(column, "new_name");
} else {
panic!("Expected NotNull constraint");
}
}
#[test]
fn test_rename_column_updates_fk_constraint() {
let engine = RelationalEngine::new();
let ref_schema = Schema::with_constraints(
vec![Column::new("id", ColumnType::Int)],
vec![Constraint::PrimaryKey {
name: "ref_pk".to_string(),
columns: vec!["id".to_string()],
}],
);
engine.create_table("ref_table", ref_schema).unwrap();
let fk = ForeignKeyConstraint::new(
"fk_ref",
vec!["old_ref_id".to_string()],
"ref_table",
vec!["id".to_string()],
);
let schema = Schema::with_constraints(
vec![Column::new("old_ref_id", ColumnType::Int).nullable()],
vec![Constraint::ForeignKey(fk)],
);
engine.create_table("fk_table", schema).unwrap();
engine
.rename_column("fk_table", "old_ref_id", "new_ref_id")
.unwrap();
let schema = engine.get_schema("fk_table").unwrap();
if let Some(Constraint::ForeignKey(fk)) = schema.constraints.first() {
assert_eq!(fk.columns[0], "new_ref_id");
} else {
panic!("Expected ForeignKey constraint");
}
}
#[test]
fn test_error_display_table_not_found_cov() {
let err = RelationalError::TableNotFound("users".to_string());
assert!(err.to_string().contains("Table not found"));
assert!(err.to_string().contains("users"));
}
#[test]
fn test_error_display_table_already_exists_cov() {
let err = RelationalError::TableAlreadyExists("users".to_string());
assert!(err.to_string().contains("Table already exists"));
}
#[test]
fn test_error_display_column_not_found_cov() {
let err = RelationalError::ColumnNotFound("email".to_string());
assert!(err.to_string().contains("Column not found"));
}
#[test]
fn test_error_display_type_mismatch_cov() {
let err = RelationalError::TypeMismatch {
column: "age".to_string(),
expected: ColumnType::Int,
};
assert!(err.to_string().contains("Type mismatch"));
assert!(err.to_string().contains("age"));
}
#[test]
fn test_error_display_null_not_allowed_cov() {
let err = RelationalError::NullNotAllowed("name".to_string());
assert!(err.to_string().contains("Null not allowed"));
}
#[test]
fn test_error_display_index_already_exists_cov() {
let err = RelationalError::IndexAlreadyExists {
table: "users".to_string(),
column: "email".to_string(),
};
assert!(err.to_string().contains("Index already exists"));
assert!(err.to_string().contains("users.email"));
}
#[test]
fn test_error_display_index_not_found_cov() {
let err = RelationalError::IndexNotFound {
table: "users".to_string(),
column: "email".to_string(),
};
assert!(err.to_string().contains("Index not found"));
}
#[test]
fn test_error_display_storage_error_cov() {
let err = RelationalError::StorageError("disk full".to_string());
assert!(err.to_string().contains("Storage error"));
}
#[test]
fn test_error_display_invalid_name_cov() {
let err = RelationalError::InvalidName("contains spaces".to_string());
assert!(err.to_string().contains("Invalid name"));
}
#[test]
fn test_error_display_transaction_not_found_cov() {
let err = RelationalError::TransactionNotFound(123);
assert!(err.to_string().contains("Transaction not found"));
assert!(err.to_string().contains("123"));
}
#[test]
fn test_error_display_transaction_inactive_cov() {
let err = RelationalError::TransactionInactive(456);
assert!(err.to_string().contains("Transaction not active"));
}
#[test]
fn test_error_display_lock_conflict_cov() {
let err = RelationalError::LockConflict {
tx_id: 1,
blocking_tx: 2,
table: "users".to_string(),
row_id: 42,
};
let msg = err.to_string();
assert!(msg.contains("Lock conflict"));
assert!(msg.contains("tx 1"));
assert!(msg.contains("tx 2"));
}
#[test]
fn test_error_display_lock_timeout_cov() {
let err = RelationalError::LockTimeout {
tx_id: 1,
table: "users".to_string(),
row_ids: vec![1, 2, 3],
};
let msg = err.to_string();
assert!(msg.contains("Lock timeout"));
assert!(msg.contains("3 rows"));
}
#[test]
fn test_error_display_rollback_failed_cov() {
let err = RelationalError::RollbackFailed {
tx_id: 1,
reason: "storage failure".to_string(),
};
assert!(err.to_string().contains("Rollback failed"));
}
#[test]
fn test_error_display_result_too_large_cov() {
let err = RelationalError::ResultTooLarge {
operation: "SELECT".to_string(),
actual: 50000,
max: 10000,
};
let msg = err.to_string();
assert!(msg.contains("too large"));
assert!(msg.contains("50000"));
assert!(msg.contains("10000"));
}
#[test]
fn test_error_display_index_corrupted_cov() {
let err = RelationalError::IndexCorrupted {
reason: "invalid pointer".to_string(),
};
assert!(err.to_string().contains("Index data corrupted"));
}
#[test]
fn test_error_display_schema_corrupted_cov() {
let err = RelationalError::SchemaCorrupted {
table: "users".to_string(),
reason: "missing columns".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("Schema corrupted"));
assert!(msg.contains("users"));
}
#[test]
fn test_error_display_too_many_tables_cov() {
let err = RelationalError::TooManyTables {
current: 101,
max: 100,
};
let msg = err.to_string();
assert!(msg.contains("Too many tables"));
assert!(msg.contains("101"));
}
#[test]
fn test_error_display_too_many_indexes_cov() {
let err = RelationalError::TooManyIndexes {
table: "users".to_string(),
current: 11,
max: 10,
};
let msg = err.to_string();
assert!(msg.contains("Too many indexes"));
assert!(msg.contains("11"));
}
#[test]
fn test_error_display_query_timeout_cov() {
let err = RelationalError::QueryTimeout {
operation: "SELECT".to_string(),
timeout_ms: 5000,
};
let msg = err.to_string();
assert!(msg.contains("Query timeout"));
assert!(msg.contains("5000ms"));
}
#[test]
fn test_error_display_pk_violation_cov() {
let err = RelationalError::PrimaryKeyViolation {
table: "users".to_string(),
columns: vec!["id".to_string()],
value: "42".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("Primary key violation"));
assert!(msg.contains("users"));
}
#[test]
fn test_error_display_unique_violation_cov() {
let err = RelationalError::UniqueViolation {
constraint_name: "unique_email".to_string(),
columns: vec!["email".to_string()],
value: "test@test.com".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("Unique constraint"));
assert!(msg.contains("unique_email"));
}
#[test]
fn test_error_display_fk_violation_cov() {
let err = RelationalError::ForeignKeyViolation {
constraint_name: "fk_user".to_string(),
table: "orders".to_string(),
referenced_table: "users".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("Foreign key constraint"));
}
#[test]
fn test_error_display_fk_restrict_cov() {
let err = RelationalError::ForeignKeyRestrict {
constraint_name: "fk_user".to_string(),
table: "users".to_string(),
referencing_table: "orders".to_string(),
row_count: 5,
};
let msg = err.to_string();
assert!(msg.contains("restricts operation"));
assert!(msg.contains("5 row(s)"));
}
#[test]
fn test_error_display_constraint_not_found_cov() {
let err = RelationalError::ConstraintNotFound {
table: "users".to_string(),
constraint_name: "pk_users".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("Constraint"));
assert!(msg.contains("not found"));
}
#[test]
fn test_error_display_constraint_already_exists_cov() {
let err = RelationalError::ConstraintAlreadyExists {
table: "users".to_string(),
constraint_name: "pk_users".to_string(),
};
assert!(err.to_string().contains("already exists"));
}
#[test]
fn test_error_display_column_has_constraint_cov() {
let err = RelationalError::ColumnHasConstraint {
column: "id".to_string(),
constraint_name: "pk_id".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("is part of constraint"));
}
#[test]
fn test_error_display_cannot_add_column_cov() {
let err = RelationalError::CannotAddColumn {
column: "required".to_string(),
reason: "non-nullable".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("Cannot add column"));
}
#[test]
fn test_error_display_column_already_exists_cov() {
let err = RelationalError::ColumnAlreadyExists {
table: "users".to_string(),
column: "id".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("already exists"));
}
#[test]
fn test_config_builder_methods() {
let config = RelationalConfig::new()
.with_max_tables(50)
.with_max_indexes_per_table(5)
.with_default_timeout_ms(5000)
.with_max_timeout_ms(30000)
.with_max_btree_entries(500_000)
.with_slow_query_threshold_ms(50)
.with_max_query_result_rows(1000)
.with_transaction_timeout_secs(120)
.with_lock_timeout_secs(60);
assert_eq!(config.max_tables, Some(50));
assert_eq!(config.max_indexes_per_table, Some(5));
assert_eq!(config.default_query_timeout_ms, Some(5000));
assert_eq!(config.max_query_timeout_ms, Some(30000));
assert_eq!(config.max_btree_entries, 500_000);
assert_eq!(config.slow_query_threshold_ms, 50);
assert_eq!(config.max_query_result_rows, Some(1000));
assert_eq!(config.transaction_timeout_secs, 120);
assert_eq!(config.lock_timeout_secs, 60);
}
#[test]
fn test_config_high_throughput_preset() {
let config = RelationalConfig::high_throughput();
assert_eq!(config.max_tables, None);
assert_eq!(config.max_indexes_per_table, None);
assert_eq!(config.max_btree_entries, 20_000_000);
assert_eq!(config.default_query_timeout_ms, Some(30_000));
assert_eq!(config.max_query_timeout_ms, Some(600_000));
assert_eq!(config.slow_query_threshold_ms, 50);
assert_eq!(config.transaction_timeout_secs, 120);
assert_eq!(config.lock_timeout_secs, 60);
}
#[test]
fn test_config_low_memory_preset() {
let config = RelationalConfig::low_memory();
assert_eq!(config.max_tables, Some(100));
assert_eq!(config.max_indexes_per_table, Some(5));
assert_eq!(config.max_btree_entries, 1_000_000);
assert_eq!(config.default_query_timeout_ms, Some(10_000));
assert_eq!(config.max_query_timeout_ms, Some(60_000));
assert_eq!(config.max_query_result_rows, Some(10_000));
assert_eq!(config.transaction_timeout_secs, 30);
assert_eq!(config.lock_timeout_secs, 15);
}
#[test]
fn test_config_validate_success() {
let config = RelationalConfig::new()
.with_default_timeout_ms(5000)
.with_max_timeout_ms(10000);
assert!(config.validate().is_ok());
}
#[test]
fn test_config_validate_failure() {
let config = RelationalConfig::new()
.with_default_timeout_ms(20000)
.with_max_timeout_ms(10000);
let result = config.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("exceeds"));
}
#[test]
fn test_engine_with_config() {
let config = RelationalConfig::new()
.with_max_tables(2)
.with_max_query_result_rows(5);
let engine = RelationalEngine::with_config(config);
engine
.create_table("t1", Schema::new(vec![Column::new("id", ColumnType::Int)]))
.unwrap();
engine
.create_table("t2", Schema::new(vec![Column::new("id", ColumnType::Int)]))
.unwrap();
let result = engine.create_table("t3", Schema::new(vec![Column::new("id", ColumnType::Int)]));
assert!(matches!(result, Err(RelationalError::TooManyTables { .. })));
}
#[test]
fn test_engine_max_query_result_rows() {
let config = RelationalConfig::new().with_max_query_result_rows(3);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("limited", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("limited", values).unwrap();
}
let result = engine.select("limited", Condition::True);
assert!(matches!(
result,
Err(RelationalError::ResultTooLarge { .. })
));
}
#[test]
fn test_aggregate_count_all_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("empty_agg", schema).unwrap();
let result = engine
.select_grouped(
"empty_agg",
Condition::True,
&[],
&[AggregateExpr::CountAll],
None,
)
.unwrap();
assert!(result.is_empty());
}
#[test]
fn test_aggregate_sum_empty_group() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group_key", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("sum_empty", schema).unwrap();
let result = engine
.select_grouped(
"sum_empty",
Condition::True,
&["group_key".to_string()],
&[AggregateExpr::Sum("val".to_string())],
None,
)
.unwrap();
assert!(result.is_empty()); }
#[test]
fn test_aggregate_min_max_single_row() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("single_agg", schema).unwrap();
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Int(42));
engine.insert("single_agg", values).unwrap();
let min = engine.min("single_agg", "val", Condition::True).unwrap();
let max = engine.max("single_agg", "val", Condition::True).unwrap();
assert_eq!(min, Some(Value::Int(42)));
assert_eq!(max, Some(Value::Int(42)));
}
#[test]
fn test_aggregate_avg_with_nulls() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("avg_nulls", schema).unwrap();
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Int(10));
engine.insert("avg_nulls", values).unwrap();
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Null);
engine.insert("avg_nulls", values).unwrap();
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Int(20));
engine.insert("avg_nulls", values).unwrap();
let avg = engine.avg("avg_nulls", "val", Condition::True).unwrap();
assert_eq!(avg, Some(15.0));
}
#[test]
fn test_having_condition_eq() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("dept", ColumnType::String),
Column::new("salary", ColumnType::Int),
]);
engine.create_table("having_eq", schema).unwrap();
for (dept, sal) in [("A", 100), ("A", 200), ("B", 150)] {
let mut values = HashMap::new();
values.insert("dept".to_string(), Value::String(dept.to_string()));
values.insert("salary".to_string(), Value::Int(sal));
engine.insert("having_eq", values).unwrap();
}
let result = engine
.select_grouped(
"having_eq",
Condition::True,
&["dept".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Eq(AggregateRef::CountAll, Value::Int(2))),
)
.unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_having_condition_ne_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("dept", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("having_ne_cov", schema).unwrap();
for (dept, val) in [("A", 1), ("A", 2), ("B", 1)] {
let mut values = HashMap::new();
values.insert("dept".to_string(), Value::String(dept.to_string()));
values.insert("val".to_string(), Value::Int(val));
engine.insert("having_ne_cov", values).unwrap();
}
let result = engine
.select_grouped(
"having_ne_cov",
Condition::True,
&["dept".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Ne(AggregateRef::CountAll, Value::Int(1))),
)
.unwrap();
assert_eq!(result.len(), 1); }
#[test]
fn test_having_condition_le_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("dept", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("having_le_cov", schema).unwrap();
for (dept, val) in [("A", 1), ("A", 2), ("A", 3), ("B", 1)] {
let mut values = HashMap::new();
values.insert("dept".to_string(), Value::String(dept.to_string()));
values.insert("val".to_string(), Value::Int(val));
engine.insert("having_le_cov", values).unwrap();
}
let result = engine
.select_grouped(
"having_le_cov",
Condition::True,
&["dept".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Le(AggregateRef::CountAll, Value::Int(2))),
)
.unwrap();
assert_eq!(result.len(), 1); }
#[test]
fn test_having_condition_lt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("dept", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("having_lt", schema).unwrap();
for (dept, val) in [("A", 1), ("A", 2), ("B", 1)] {
let mut values = HashMap::new();
values.insert("dept".to_string(), Value::String(dept.to_string()));
values.insert("val".to_string(), Value::Int(val));
engine.insert("having_lt", values).unwrap();
}
let result = engine
.select_grouped(
"having_lt",
Condition::True,
&["dept".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Lt(AggregateRef::CountAll, Value::Int(2))),
)
.unwrap();
assert_eq!(result.len(), 1); }
#[test]
fn test_having_condition_and() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("dept", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("having_and", schema).unwrap();
for (dept, val) in [
("A", 10),
("A", 20),
("B", 5),
("C", 100),
("C", 200),
("C", 300),
] {
let mut values = HashMap::new();
values.insert("dept".to_string(), Value::String(dept.to_string()));
values.insert("val".to_string(), Value::Int(val));
engine.insert("having_and", values).unwrap();
}
let result = engine
.select_grouped(
"having_and",
Condition::True,
&["dept".to_string()],
&[
AggregateExpr::CountAll,
AggregateExpr::Sum("val".to_string()),
],
None, )
.unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn test_having_condition_or() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("dept", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("having_or", schema).unwrap();
for (dept, val) in [("A", 10), ("B", 5), ("B", 5), ("C", 100)] {
let mut values = HashMap::new();
values.insert("dept".to_string(), Value::String(dept.to_string()));
values.insert("val".to_string(), Value::Int(val));
engine.insert("having_or", values).unwrap();
}
let result = engine
.select_grouped(
"having_or",
Condition::True,
&["dept".to_string()],
&[
AggregateExpr::CountAll,
AggregateExpr::Sum("val".to_string()),
],
Some(HavingCondition::Or(
Box::new(HavingCondition::Eq(AggregateRef::CountAll, Value::Int(1))),
Box::new(HavingCondition::Ge(
AggregateRef::Sum("val".to_string()),
Value::Int(100),
)),
)),
)
.unwrap();
assert_eq!(result.len(), 2); }
#[test]
fn test_schema_get_column() {
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
assert!(schema.get_column("id").is_some());
assert!(schema.get_column("missing").is_none());
}
#[test]
fn test_schema_add_constraint_coverage_v2() {
let mut schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
schema.add_constraint(Constraint::PrimaryKey {
name: "pk".to_string(),
columns: vec!["id".to_string()],
});
assert_eq!(schema.constraints().len(), 1);
}
#[test]
fn test_column_nullable_builder() {
let col = Column::new("optional", ColumnType::String).nullable();
assert!(col.nullable);
}
#[test]
fn test_foreign_key_constraint_builder() {
let fk = ForeignKeyConstraint::new(
"fk_test",
vec!["user_id".to_string()],
"users",
vec!["id".to_string()],
)
.on_delete(ReferentialAction::Cascade)
.on_update(ReferentialAction::SetNull);
assert_eq!(fk.name, "fk_test");
assert_eq!(fk.on_delete, ReferentialAction::Cascade);
assert_eq!(fk.on_update, ReferentialAction::SetNull);
}
#[test]
fn test_referential_action_default_coverage() {
let action: ReferentialAction = Default::default();
assert_eq!(action, ReferentialAction::Restrict);
}
#[test]
fn test_row_cursor_debug_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("cursor_dbg_cov", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
engine.insert("cursor_dbg_cov", values).unwrap();
}
let mut cursor = engine
.select_iter("cursor_dbg_cov", Condition::True, CursorOptions::default())
.unwrap();
cursor.next();
let debug_str = format!("{cursor:?}");
assert!(debug_str.contains("RowCursor"));
assert!(debug_str.contains("rows_processed"));
}
#[test]
fn test_materialize_all_column_types() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("int_col", ColumnType::Int),
Column::new("float_col", ColumnType::Float),
Column::new("str_col", ColumnType::String),
Column::new("bool_col", ColumnType::Bool),
Column::new("bytes_col", ColumnType::Bytes),
Column::new("json_col", ColumnType::Json),
]);
engine.create_table("all_types_mat", schema).unwrap();
let mut values = HashMap::new();
values.insert("int_col".to_string(), Value::Int(42));
values.insert("float_col".to_string(), Value::Float(3.15));
values.insert("str_col".to_string(), Value::String("hello".to_string()));
values.insert("bool_col".to_string(), Value::Bool(true));
values.insert("bytes_col".to_string(), Value::Bytes(vec![1, 2, 3]));
values.insert(
"json_col".to_string(),
Value::Json(serde_json::json!({"key": "val"})),
);
engine.insert("all_types_mat", values).unwrap();
engine
.materialize_columns("all_types_mat", &["int_col"])
.unwrap();
engine
.materialize_columns("all_types_mat", &["float_col"])
.unwrap();
engine
.materialize_columns("all_types_mat", &["str_col"])
.unwrap();
engine
.materialize_columns("all_types_mat", &["bool_col"])
.unwrap();
engine
.materialize_columns("all_types_mat", &["bytes_col"])
.unwrap();
engine
.materialize_columns("all_types_mat", &["json_col"])
.unwrap();
}
#[test]
fn test_too_many_indexes_per_table() {
let config = RelationalConfig::new().with_max_indexes_per_table(2);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
Column::new("c", ColumnType::Int),
]);
engine.create_table("idx_limit", schema).unwrap();
engine.create_index("idx_limit", "a").unwrap();
engine.create_index("idx_limit", "b").unwrap();
let result = engine.create_index("idx_limit", "c");
assert!(matches!(
result,
Err(RelationalError::TooManyIndexes { .. })
));
}
#[test]
fn test_drop_columnar_data() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::String),
]);
engine.create_table("drop_columnar", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("data".to_string(), Value::String(format!("row{i}")));
engine.insert("drop_columnar", values).unwrap();
}
engine.drop_columnar_data("drop_columnar", "data").unwrap();
}
#[test]
fn test_drop_columnar_data_missing_is_ok() {
let engine = RelationalEngine::new();
let result = engine.drop_columnar_data("missing", "col");
assert!(result.is_ok()); }
#[test]
fn test_transaction_double_commit() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("tx_double", schema).unwrap();
let tx = engine.begin_transaction();
engine.commit(tx).unwrap();
let result = engine.commit(tx);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_transaction_rollback_after_commit() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("tx_rb_commit", schema).unwrap();
let tx = engine.begin_transaction();
engine.commit(tx).unwrap();
let result = engine.rollback(tx);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_condition_between_using_and() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("range_inc_cov", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Int(i));
engine.insert("range_inc_cov", values).unwrap();
}
let cond = Condition::Ge("val".to_string(), Value::Int(3))
.and(Condition::Le("val".to_string(), Value::Int(7)));
let results = engine.select("range_inc_cov", cond).unwrap();
assert_eq!(results.len(), 5); }
#[test]
fn test_condition_or_combination() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("or_cond_cov", schema).unwrap();
for i in 1..=5 {
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Int(i));
engine.insert("or_cond_cov", values).unwrap();
}
let cond = Condition::Eq("val".to_string(), Value::Int(1))
.or(Condition::Eq("val".to_string(), Value::Int(5)));
let results = engine.select("or_cond_cov", cond).unwrap();
assert_eq!(results.len(), 2); }
#[test]
fn test_condition_ne_filter_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("ne_cond_cov2", schema).unwrap();
for i in 1..=5 {
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Int(i));
engine.insert("ne_cond_cov2", values).unwrap();
}
let cond = Condition::Ne("val".to_string(), Value::Int(3));
let results = engine.select("ne_cond_cov2", cond).unwrap();
assert_eq!(results.len(), 4); }
#[test]
fn test_condition_lt_filter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("lt_cond_cov", schema).unwrap();
for i in 1..=5 {
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Int(i));
engine.insert("lt_cond_cov", values).unwrap();
}
let cond = Condition::Lt("val".to_string(), Value::Int(3));
let results = engine.select("lt_cond_cov", cond).unwrap();
assert_eq!(results.len(), 2); }
#[test]
fn test_condition_gt_filter() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("gt_cond_cov", schema).unwrap();
for i in 1..=5 {
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Int(i));
engine.insert("gt_cond_cov", values).unwrap();
}
let cond = Condition::Gt("val".to_string(), Value::Int(3));
let results = engine.select("gt_cond_cov", cond).unwrap();
assert_eq!(results.len(), 2); }
#[test]
fn test_condition_null_equality() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int).nullable()]);
engine.create_table("null_eq_cov", schema).unwrap();
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Null);
engine.insert("null_eq_cov", values).unwrap();
let mut values = HashMap::new();
values.insert("val".to_string(), Value::Int(1));
engine.insert("null_eq_cov", values).unwrap();
let cond = Condition::Eq("val".to_string(), Value::Null);
let results = engine.select("null_eq_cov", cond).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_value_equality() {
assert_eq!(Value::Null, Value::Null);
assert_eq!(Value::Bool(true), Value::Bool(true));
assert_ne!(Value::Bool(true), Value::Bool(false));
assert_eq!(Value::Int(42), Value::Int(42));
assert_ne!(Value::Int(1), Value::Int(2));
assert_eq!(Value::Float(3.15), Value::Float(3.15));
assert_eq!(
Value::String("test".to_string()),
Value::String("test".to_string())
);
assert_eq!(Value::Bytes(vec![1, 2, 3]), Value::Bytes(vec![1, 2, 3]));
}
#[test]
fn test_value_debug_format() {
let null = format!("{:?}", Value::Null);
assert!(null.contains("Null"));
let int = format!("{:?}", Value::Int(42));
assert!(int.contains("42"));
let float = format!("{:?}", Value::Float(3.15));
assert!(float.contains("3.15"));
let string = format!("{:?}", Value::String("hello".to_string()));
assert!(string.contains("hello"));
let bool_val = format!("{:?}", Value::Bool(true));
assert!(bool_val.contains("true"));
let bytes = format!("{:?}", Value::Bytes(vec![1, 2, 3]));
assert!(bytes.contains("Bytes"));
}
#[test]
fn test_select_columnar_int_eq() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("users_col", schema).unwrap();
for i in 0..20 {
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + (i % 5)));
engine.insert("users_col", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Eq("age".to_string(), Value::Int(22));
let results = engine
.select_columnar("users_col", condition, options)
.unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_select_columnar_int_ne() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("data_ne", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Int(i % 3)); engine.insert("data_ne", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Ne("value".to_string(), Value::Int(0));
let results = engine
.select_columnar("data_ne", condition, options)
.unwrap();
assert_eq!(results.len(), 6);
}
#[test]
fn test_select_columnar_int_lt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]);
engine.create_table("scores_lt", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("score".to_string(), Value::Int(i * 10));
engine.insert("scores_lt", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Lt("score".to_string(), Value::Int(50));
let results = engine
.select_columnar("scores_lt", condition, options)
.unwrap();
assert_eq!(results.len(), 5);
}
#[test]
fn test_select_columnar_int_le() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("level", ColumnType::Int),
]);
engine.create_table("levels_le", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("level".to_string(), Value::Int(i));
engine.insert("levels_le", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Le("level".to_string(), Value::Int(5));
let results = engine
.select_columnar("levels_le", condition, options)
.unwrap();
assert_eq!(results.len(), 6);
}
#[test]
fn test_select_columnar_int_gt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("rank", ColumnType::Int),
]);
engine.create_table("ranks_gt", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("rank".to_string(), Value::Int(i));
engine.insert("ranks_gt", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Gt("rank".to_string(), Value::Int(6));
let results = engine
.select_columnar("ranks_gt", condition, options)
.unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_select_columnar_int_ge() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("priority", ColumnType::Int),
]);
engine.create_table("priorities_ge", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("priority".to_string(), Value::Int(i));
engine.insert("priorities_ge", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Ge("priority".to_string(), Value::Int(7));
let results = engine
.select_columnar("priorities_ge", condition, options)
.unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_select_columnar_float_lt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("temp", ColumnType::Float),
]);
engine.create_table("temps_lt", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("temp".to_string(), Value::Float(i as f64 * 10.5));
engine.insert("temps_lt", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Lt("temp".to_string(), Value::Float(52.5));
let results = engine
.select_columnar("temps_lt", condition, options)
.unwrap();
assert_eq!(results.len(), 5);
}
#[test]
fn test_select_columnar_float_gt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("price", ColumnType::Float),
]);
engine.create_table("prices_gt", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("price".to_string(), Value::Float(i as f64 * 5.0));
engine.insert("prices_gt", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Gt("price".to_string(), Value::Float(30.0));
let results = engine
.select_columnar("prices_gt", condition, options)
.unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_select_columnar_float_eq() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("rate", ColumnType::Float),
]);
engine.create_table("rates_eq", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("rate".to_string(), Value::Float((i % 3) as f64));
engine.insert("rates_eq", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Eq("rate".to_string(), Value::Float(1.0));
let results = engine
.select_columnar("rates_eq", condition, options)
.unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_select_columnar_and_condition_simd() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("age", ColumnType::Int),
]);
engine.create_table("users_and_simd", schema).unwrap();
for i in 0..20 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("age".to_string(), Value::Int(i));
engine.insert("users_and_simd", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let cond_ge = Condition::Ge("age".to_string(), Value::Int(5));
let cond_lt = Condition::Lt("age".to_string(), Value::Int(10));
let condition = cond_ge.and(cond_lt);
let results = engine
.select_columnar("users_and_simd", condition, options)
.unwrap();
assert_eq!(results.len(), 5);
}
#[test]
fn test_select_columnar_or_condition_simd() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("status", ColumnType::Int),
]);
engine.create_table("items_or_simd", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("status".to_string(), Value::Int(i % 4)); engine.insert("items_or_simd", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let cond_eq0 = Condition::Eq("status".to_string(), Value::Int(0));
let cond_eq3 = Condition::Eq("status".to_string(), Value::Int(3));
let condition = cond_eq0.or(cond_eq3);
let results = engine
.select_columnar("items_or_simd", condition, options)
.unwrap();
assert_eq!(results.len(), 5);
}
#[test]
fn test_select_columnar_condition_true() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("items_true", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("val".to_string(), Value::Int(i * 10));
engine.insert("items_true", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let results = engine
.select_columnar("items_true", Condition::True, options)
.unwrap();
assert_eq!(results.len(), 5);
}
#[test]
fn test_select_columnar_with_projection_simd() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("users_proj_simd", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("users_proj_simd", values).unwrap();
}
let options = ColumnarScanOptions {
projection: Some(vec!["name".to_string()]),
prefer_columnar: true,
};
let condition = Condition::Gt("age".to_string(), Value::Int(25));
let results = engine
.select_columnar("users_proj_simd", condition, options)
.unwrap();
assert_eq!(results.len(), 4);
for row in &results {
let has_name = row.values.iter().any(|(k, _)| k == "name");
assert!(has_name);
}
}
#[test]
fn test_select_columnar_empty_result_simd() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("empty_res_simd", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("val".to_string(), Value::Int(i));
engine.insert("empty_res_simd", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Gt("val".to_string(), Value::Int(100));
let results = engine
.select_columnar("empty_res_simd", condition, options)
.unwrap();
assert!(results.is_empty());
}
#[test]
fn test_select_columnar_empty_table_simd() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("empty_tbl_simd", schema).unwrap();
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Eq("val".to_string(), Value::Int(1));
let results = engine
.select_columnar("empty_tbl_simd", condition, options)
.unwrap();
assert!(results.is_empty());
}
#[test]
fn test_select_columnar_prefer_false_fallback() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("fallback_tbl", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("val".to_string(), Value::Int(i * 2));
engine.insert("fallback_tbl", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: false,
};
let condition = Condition::Lt("val".to_string(), Value::Int(6));
let results = engine
.select_columnar("fallback_tbl", condition, options)
.unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_select_columnar_string_condition_fallback() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("str_cond", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String(format!("Item{i}")));
engine.insert("str_cond", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Eq("name".to_string(), Value::String("Item2".to_string()));
let results = engine
.select_columnar("str_cond", condition, options)
.unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_select_columnar_complex_and_or() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("x", ColumnType::Int),
Column::new("y", ColumnType::Int),
]);
engine.create_table("complex_cond", schema).unwrap();
for i in 0..20 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("x".to_string(), Value::Int(i % 5));
values.insert("y".to_string(), Value::Int(i % 3));
engine.insert("complex_cond", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let cond_x0 = Condition::Eq("x".to_string(), Value::Int(0));
let cond_x4 = Condition::Eq("x".to_string(), Value::Int(4));
let cond_y0 = Condition::Eq("y".to_string(), Value::Int(0));
let condition = cond_x0.or(cond_x4).and(cond_y0);
let results = engine
.select_columnar("complex_cond", condition, options)
.unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_select_columnar_with_deleted_rows() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("del_rows", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("val".to_string(), Value::Int(i));
engine.insert("del_rows", values).unwrap();
}
engine
.delete_rows("del_rows", Condition::Eq("val".to_string(), Value::Int(3)))
.unwrap();
engine
.delete_rows("del_rows", Condition::Eq("val".to_string(), Value::Int(7)))
.unwrap();
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let condition = Condition::Gt("val".to_string(), Value::Int(2));
let results = engine
.select_columnar("del_rows", condition, options)
.unwrap();
assert_eq!(results.len(), 5);
}
#[test]
fn test_select_columnar_nested_and() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("a", ColumnType::Int),
Column::new("b", ColumnType::Int),
Column::new("c", ColumnType::Int),
]);
engine.create_table("nested_and", schema).unwrap();
for i in 0..30 {
let mut values = HashMap::new();
values.insert("a".to_string(), Value::Int(i % 3));
values.insert("b".to_string(), Value::Int(i % 5));
values.insert("c".to_string(), Value::Int(i % 7));
engine.insert("nested_and", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let cond = Condition::Eq("a".to_string(), Value::Int(0))
.and(Condition::Eq("b".to_string(), Value::Int(0)))
.and(Condition::Eq("c".to_string(), Value::Int(0)));
let results = engine.select_columnar("nested_and", cond, options).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_select_columnar_nested_or() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("x", ColumnType::Int)]);
engine.create_table("nested_or", schema).unwrap();
for i in 0..20 {
let mut values = HashMap::new();
values.insert("x".to_string(), Value::Int(i));
engine.insert("nested_or", values).unwrap();
}
let options = ColumnarScanOptions {
projection: None,
prefer_columnar: true,
};
let cond = Condition::Eq("x".to_string(), Value::Int(5))
.or(Condition::Eq("x".to_string(), Value::Int(10)))
.or(Condition::Eq("x".to_string(), Value::Int(15)));
let results = engine.select_columnar("nested_or", cond, options).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_query_timeout_zero_ms() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("timeout_test", schema).unwrap();
for i in 0..100 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("val".to_string(), Value::Int(i * 10));
engine.insert("timeout_test", values).unwrap();
}
let options = QueryOptions::new().with_timeout_ms(0);
let result = engine.select_with_options("timeout_test", Condition::True, options);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, RelationalError::QueryTimeout { .. }));
}
#[test]
fn test_result_too_large_error_cov() {
let config = RelationalConfig::default().with_max_query_result_rows(5);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("large_result_cov", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("val".to_string(), Value::Int(i));
engine.insert("large_result_cov", values).unwrap();
}
let result = engine.select("large_result_cov", Condition::True);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, RelationalError::ResultTooLarge { .. }));
}
#[test]
fn test_result_too_large_display_cov() {
let err = RelationalError::ResultTooLarge {
operation: "select".to_string(),
actual: 100,
max: 50,
};
let display = err.to_string();
assert!(display.contains("100"));
assert!(display.contains("50"));
assert!(display.contains("select"));
}
#[test]
fn test_query_timeout_display_cov() {
let err = RelationalError::QueryTimeout {
operation: "select".to_string(),
timeout_ms: 1000,
};
let display = err.to_string();
assert!(display.contains("select"));
assert!(display.contains("1000"));
}
#[test]
fn test_sum_float_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("amount", ColumnType::Float),
]);
engine.create_table("float_sum", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("amount".to_string(), Value::Float(i as f64 * 10.5));
engine.insert("float_sum", values).unwrap();
}
let result = engine.sum("float_sum", "amount", Condition::True).unwrap();
assert!((result - 105.0).abs() < 0.001);
}
#[test]
fn test_avg_float_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Float),
]);
engine.create_table("float_avg", schema).unwrap();
for i in 0..4 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert(
"score".to_string(),
Value::Float((i as f64).mul_add(5.0, 10.0)),
);
engine.insert("float_avg", values).unwrap();
}
let result = engine.avg("float_avg", "score", Condition::True).unwrap();
assert!(result.is_some());
let avg = result.unwrap();
assert!((avg - 17.5).abs() < 0.001);
}
#[test]
fn test_avg_empty_result_returns_none() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("empty_avg", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("val".to_string(), Value::Int(i));
engine.insert("empty_avg", values).unwrap();
}
let result = engine
.avg(
"empty_avg",
"val",
Condition::Gt("val".to_string(), Value::Int(100)),
)
.unwrap();
assert!(result.is_none());
}
#[test]
fn test_min_with_null_values_cov() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int).nullable(),
]);
engine.create_table("min_null_cov", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 2 == 0 {
values.insert("val".to_string(), Value::Null);
} else {
values.insert("val".to_string(), Value::Int(i * 10));
}
engine.insert("min_null_cov", values).unwrap();
}
let result = engine.min("min_null_cov", "val", Condition::True).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap(), Value::Int(10));
}
#[test]
fn test_max_with_null_values_cov() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int).nullable(),
]);
engine.create_table("max_null_cov", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
if i % 2 == 0 {
values.insert("val".to_string(), Value::Null);
} else {
values.insert("val".to_string(), Value::Int(i * 10));
}
engine.insert("max_null_cov", values).unwrap();
}
let result = engine.max("max_null_cov", "val", Condition::True).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap(), Value::Int(30));
}
#[test]
fn test_min_max_all_nulls_cov() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int).nullable(),
]);
engine.create_table("all_null_cov", schema).unwrap();
for i in 0..5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("val".to_string(), Value::Null);
engine.insert("all_null_cov", values).unwrap();
}
let min_result = engine.min("all_null_cov", "val", Condition::True).unwrap();
let max_result = engine.max("all_null_cov", "val", Condition::True).unwrap();
assert!(min_result.is_none());
assert!(max_result.is_none());
}
#[test]
fn test_select_grouped_sum_float() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("category", ColumnType::String),
Column::new("amount", ColumnType::Float),
]);
engine.create_table("grouped_float", schema).unwrap();
let data = [("A", 10.5), ("A", 20.5), ("B", 15.0), ("B", 25.0)];
for (cat, amount) in &data {
let mut values = HashMap::new();
values.insert("category".to_string(), Value::String(cat.to_string()));
values.insert("amount".to_string(), Value::Float(*amount));
engine.insert("grouped_float", values).unwrap();
}
let results = engine
.select_grouped(
"grouped_float",
Condition::True,
&["category".to_string()],
&[AggregateExpr::Sum("amount".to_string())],
None,
)
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_select_grouped_avg_float_cov() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("group_id", ColumnType::Int),
Column::new("score", ColumnType::Float),
]);
engine
.create_table("grouped_avg_float_cov", schema)
.unwrap();
let data = [(1, 10.0), (1, 20.0), (2, 30.0), (2, 40.0)];
for (grp, score) in &data {
let mut values = HashMap::new();
values.insert("group_id".to_string(), Value::Int(*grp));
values.insert("score".to_string(), Value::Float(*score));
engine.insert("grouped_avg_float_cov", values).unwrap();
}
let results = engine
.select_grouped(
"grouped_avg_float_cov",
Condition::True,
&["group_id".to_string()],
&[AggregateExpr::Avg("score".to_string())],
None,
)
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_with_store_and_config_cov() {
let store = TensorStore::new();
let config = RelationalConfig::default().with_max_tables(10);
let engine = RelationalEngine::with_store_and_config(store, config);
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_store_cov", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
engine.insert("test_store_cov", values).unwrap();
let rows = engine.select("test_store_cov", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_cursor_options_default_cov() {
let options = CursorOptions::default();
assert_eq!(options.batch_size, 1000);
assert_eq!(options.offset, 0);
assert!(options.limit.is_none());
}
#[test]
fn test_cursor_options_with_limit_cov() {
let options = CursorOptions::default().with_limit(50);
assert_eq!(options.limit, Some(50));
}
#[test]
fn test_cursor_options_with_offset_cov() {
let options = CursorOptions::default().with_offset(10);
assert_eq!(options.offset, 10);
}
#[test]
fn test_cursor_options_with_batch_size_cov() {
let options = CursorOptions::default().with_batch_size(500);
assert_eq!(options.batch_size, 500);
}
#[test]
fn test_lock_timeout_error_display_cov() {
let err = RelationalError::LockTimeout {
tx_id: 1,
table: "test_table".to_string(),
row_ids: vec![1, 2, 3],
};
let display = err.to_string();
assert!(display.contains("test_table"));
}
#[test]
fn test_rollback_failed_error_display() {
let err = RelationalError::RollbackFailed {
tx_id: 1,
reason: "storage error".to_string(),
};
let display = err.to_string();
assert!(display.contains("storage error"));
}
#[test]
fn test_index_corrupted_error_display() {
let err = RelationalError::IndexCorrupted {
reason: "missing entry".to_string(),
};
let display = err.to_string();
assert!(display.contains("missing entry"));
}
#[test]
fn test_schema_corrupted_error_display() {
let err = RelationalError::SchemaCorrupted {
table: "users".to_string(),
reason: "invalid column type".to_string(),
};
let display = err.to_string();
assert!(display.contains("users"));
assert!(display.contains("invalid column type"));
}
#[test]
fn test_too_many_tables_error_display() {
let err = RelationalError::TooManyTables {
current: 100,
max: 50,
};
let display = err.to_string();
assert!(display.contains("100"));
assert!(display.contains("50"));
}
#[test]
fn test_too_many_indexes_error_display() {
let err = RelationalError::TooManyIndexes {
table: "users".to_string(),
current: 10,
max: 5,
};
let display = err.to_string();
assert!(display.contains("users"));
assert!(display.contains("10"));
}
#[test]
fn test_primary_key_violation_display() {
let err = RelationalError::PrimaryKeyViolation {
table: "users".to_string(),
columns: vec!["id".to_string()],
value: "1".to_string(),
};
let display = err.to_string();
assert!(display.contains("users"));
}
#[test]
fn test_unique_violation_display() {
let err = RelationalError::UniqueViolation {
constraint_name: "uk_email".to_string(),
columns: vec!["email".to_string()],
value: "test@example.com".to_string(),
};
let display = err.to_string();
assert!(display.contains("uk_email"));
}
#[test]
fn test_config_validation_default_timeout_exceeds_max() {
let result = RelationalConfig::default()
.with_default_timeout_ms(10000)
.with_max_timeout_ms(5000)
.validate();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("exceeds"));
}
#[test]
fn test_config_validation_valid_cov() {
let result = RelationalConfig::default()
.with_default_timeout_ms(5000)
.with_max_timeout_ms(10000)
.validate();
assert!(result.is_ok());
}
#[test]
fn test_config_validation_no_max_timeout() {
let result = RelationalConfig::default()
.with_default_timeout_ms(10000)
.validate();
assert!(result.is_ok());
}
#[test]
fn test_deep_or_condition_evaluation() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String("Alice".into())),
("age".to_string(), Value::Int(25)),
]),
)
.unwrap();
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String("Bob".into())),
("age".to_string(), Value::Int(30)),
]),
)
.unwrap();
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String("Charlie".into())),
("age".to_string(), Value::Int(35)),
]),
)
.unwrap();
let condition = Condition::Or(
Box::new(Condition::Eq("age".to_string(), Value::Int(25))),
Box::new(Condition::Or(
Box::new(Condition::Eq("age".to_string(), Value::Int(30))),
Box::new(Condition::Eq("age".to_string(), Value::Int(99))),
)),
);
let rows = engine.select("users", condition).unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn test_condition_and_or_mixed() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String("Alice".into())),
("age".to_string(), Value::Int(25)),
]),
)
.unwrap();
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String("Bob".into())),
("age".to_string(), Value::Int(30)),
]),
)
.unwrap();
let condition = Condition::Or(
Box::new(Condition::And(
Box::new(Condition::Eq("age".to_string(), Value::Int(25))),
Box::new(Condition::Eq(
"name".to_string(),
Value::String("Alice".into()),
)),
)),
Box::new(Condition::Eq("age".to_string(), Value::Int(30))),
);
let rows = engine.select("users", condition).unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn test_value_truthy_null() {
assert!(!Value::Null.is_truthy());
}
#[test]
fn test_value_truthy_bool_false() {
assert!(!Value::Bool(false).is_truthy());
}
#[test]
fn test_value_truthy_int_zero() {
assert!(!Value::Int(0).is_truthy());
}
#[test]
fn test_value_truthy_float_zero() {
assert!(!Value::Float(0.0).is_truthy());
}
#[test]
fn test_value_truthy_string_empty() {
assert!(!Value::String(String::new()).is_truthy());
}
#[test]
fn test_value_truthy_bytes_empty() {
assert!(!Value::Bytes(vec![]).is_truthy());
}
#[test]
fn test_value_truthy_json_null() {
assert!(!Value::Json(serde_json::Value::Null).is_truthy());
}
#[test]
fn test_value_truthy_positive_cases() {
assert!(Value::Bool(true).is_truthy());
assert!(Value::Int(1).is_truthy());
assert!(Value::Float(1.0).is_truthy());
assert!(Value::String("hello".to_string()).is_truthy());
assert!(Value::Bytes(vec![1]).is_truthy());
assert!(Value::Json(serde_json::json!({"a": 1})).is_truthy());
}
#[test]
fn test_fk_constraint_on_delete_on_update() {
let fk = ForeignKeyConstraint::new(
"fk_test",
vec!["col1".to_string()],
"other_table",
vec!["ref_col".to_string()],
)
.on_delete(ReferentialAction::Cascade)
.on_update(ReferentialAction::SetNull);
assert_eq!(fk.on_delete, ReferentialAction::Cascade);
assert_eq!(fk.on_update, ReferentialAction::SetNull);
}
#[test]
fn test_aggregate_value_to_value_conversion() {
let count_agg = AggregateValue::Count(42);
let sum_agg = AggregateValue::Sum(3.15);
let avg_some_agg = AggregateValue::Avg(Some(2.5));
let avg_none_agg = AggregateValue::Avg(None);
let min_some_agg = AggregateValue::Min(Some(Value::Int(1)));
let min_none_agg = AggregateValue::Min(None);
let max_some_agg = AggregateValue::Max(Some(Value::Int(100)));
let max_none_agg = AggregateValue::Max(None);
assert_eq!(count_agg.to_value(), Value::Int(42));
assert_eq!(sum_agg.to_value(), Value::Float(3.15));
assert_eq!(avg_some_agg.to_value(), Value::Float(2.5));
assert_eq!(avg_none_agg.to_value(), Value::Null);
assert_eq!(min_some_agg.to_value(), Value::Int(1));
assert_eq!(min_none_agg.to_value(), Value::Null);
assert_eq!(max_some_agg.to_value(), Value::Int(100));
assert_eq!(max_none_agg.to_value(), Value::Null);
}
#[test]
fn test_referential_action_default_v2() {
assert_eq!(ReferentialAction::default(), ReferentialAction::Restrict);
}
#[test]
fn test_having_condition_ne_v2() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("dept", ColumnType::String),
Column::new("salary", ColumnType::Int),
]);
engine.create_table("emp_having_ne_v2", schema).unwrap();
for i in 0..5 {
engine
.insert(
"emp_having_ne_v2",
HashMap::from([
("dept".to_string(), Value::String("A".into())),
("salary".to_string(), Value::Int(1000 * (i + 1))),
]),
)
.unwrap();
}
for i in 0..3 {
engine
.insert(
"emp_having_ne_v2",
HashMap::from([
("dept".to_string(), Value::String("B".into())),
("salary".to_string(), Value::Int(2000 * (i + 1))),
]),
)
.unwrap();
}
let groups = engine
.select_grouped(
"emp_having_ne_v2",
Condition::True,
&["dept".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Ne(AggregateRef::CountAll, Value::Int(3))),
)
.unwrap();
assert_eq!(groups.len(), 1);
}
#[test]
fn test_having_condition_lt_v2() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("dept", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("emp_having_lt_v2", schema).unwrap();
for i in 0..5 {
engine
.insert(
"emp_having_lt_v2",
HashMap::from([
("dept".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(i + 1)),
]),
)
.unwrap();
}
for i in 0..2 {
engine
.insert(
"emp_having_lt_v2",
HashMap::from([
("dept".to_string(), Value::String("B".into())),
("val".to_string(), Value::Int(i + 1)),
]),
)
.unwrap();
}
let groups = engine
.select_grouped(
"emp_having_lt_v2",
Condition::True,
&["dept".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Lt(AggregateRef::CountAll, Value::Int(3))),
)
.unwrap();
assert_eq!(groups.len(), 1);
}
#[test]
fn test_having_condition_le_v2() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("dept", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("emp_having_le_v2", schema).unwrap();
for i in 0..3 {
engine
.insert(
"emp_having_le_v2",
HashMap::from([
("dept".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(i + 1)),
]),
)
.unwrap();
}
for i in 0..5 {
engine
.insert(
"emp_having_le_v2",
HashMap::from([
("dept".to_string(), Value::String("B".into())),
("val".to_string(), Value::Int(i + 1)),
]),
)
.unwrap();
}
let groups = engine
.select_grouped(
"emp_having_le_v2",
Condition::True,
&["dept".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Le(AggregateRef::CountAll, Value::Int(3))),
)
.unwrap();
assert_eq!(groups.len(), 1);
}
#[test]
fn test_grouped_row_get_aggregate_by_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("cat", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("grp_agg_idx", schema).unwrap();
engine
.insert(
"grp_agg_idx",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
let groups = engine
.select_grouped(
"grp_agg_idx",
Condition::True,
&["cat".to_string()],
&[
AggregateExpr::Sum("val".to_string()),
AggregateExpr::Count("val".to_string()),
],
None,
)
.unwrap();
assert_eq!(groups.len(), 1);
assert!(groups[0].get_aggregate("sum_val").is_some());
assert!(groups[0].get_aggregate("count_val").is_some());
}
#[test]
fn test_select_iter_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("empty_iter", schema).unwrap();
let mut iter = engine
.select_iter("empty_iter", Condition::True, CursorOptions::default())
.unwrap();
assert!(iter.next().is_none());
}
#[test]
fn test_condition_evaluate_ne_false() {
let row = Row {
id: 1,
values: vec![("val".to_string(), Value::Int(5))],
};
let cond = Condition::Ne("val".to_string(), Value::Int(5));
assert!(!cond.evaluate(&row));
}
#[test]
fn test_condition_evaluate_ge_equal() {
let row = Row {
id: 1,
values: vec![("val".to_string(), Value::Int(5))],
};
let cond = Condition::Ge("val".to_string(), Value::Int(5));
assert!(cond.evaluate(&row));
}
#[test]
fn test_condition_evaluate_le_equal() {
let row = Row {
id: 1,
values: vec![("val".to_string(), Value::Int(5))],
};
let cond = Condition::Le("val".to_string(), Value::Int(5));
assert!(cond.evaluate(&row));
}
#[test]
fn test_schema_add_constraint_v2() {
let mut schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
schema.add_constraint(Constraint::not_null("nn_name_v2", "name"));
assert_eq!(schema.constraints().len(), 1);
assert_eq!(schema.constraints()[0].name(), "nn_name_v2");
}
#[test]
fn test_deadline_remaining_ms() {
use crate::transaction::Deadline;
let deadline = Deadline::from_timeout_ms(Some(10000));
let remaining = deadline.remaining_ms();
assert!(remaining.is_some());
assert!(remaining.unwrap() > 0);
assert!(remaining.unwrap() <= 10000);
let no_deadline = Deadline::never();
assert!(no_deadline.remaining_ms().is_none());
}
#[test]
fn test_deadline_default() {
use crate::transaction::Deadline;
let deadline = Deadline::default();
assert!(!deadline.is_expired());
assert!(deadline.remaining_ms().is_none());
}
#[test]
fn test_row_lock_is_locked() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String("Test".into())),
("age".to_string(), Value::Int(25)),
]),
)
.unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_update(
tx_id,
"users",
Condition::Eq("name".to_string(), Value::String("Test".into())),
HashMap::from([("age".to_string(), Value::Int(30))]),
)
.unwrap();
assert!(engine.tx_manager.lock_manager().is_locked("users", 1));
engine.commit(tx_id).unwrap();
assert!(!engine.tx_manager.lock_manager().is_locked("users", 1));
}
#[test]
fn test_row_lock_holder() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String("Test".into())),
("age".to_string(), Value::Int(25)),
]),
)
.unwrap();
let tx_id = engine.begin_transaction();
engine
.tx_update(
tx_id,
"users",
Condition::Eq("name".to_string(), Value::String("Test".into())),
HashMap::from([("age".to_string(), Value::Int(30))]),
)
.unwrap();
assert_eq!(
engine.tx_manager.lock_manager().lock_holder("users", 1),
Some(tx_id)
);
engine.rollback(tx_id).unwrap();
assert_eq!(
engine.tx_manager.lock_manager().lock_holder("users", 1),
None
);
}
#[test]
fn test_row_lock_locks_held_by() {
let engine = RelationalEngine::new();
create_users_table(&engine);
for i in 0..3 {
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String(format!("User{i}"))),
("age".to_string(), Value::Int(20 + i)),
]),
)
.unwrap();
}
let tx_id = engine.begin_transaction();
engine
.tx_update(
tx_id,
"users",
Condition::True,
HashMap::from([("age".to_string(), Value::Int(99))]),
)
.unwrap();
assert_eq!(engine.tx_manager.lock_manager().locks_held_by(tx_id), 3);
engine.rollback(tx_id).unwrap();
assert_eq!(engine.tx_manager.lock_manager().locks_held_by(tx_id), 0);
}
#[test]
fn test_condition_evaluate_missing_column() {
let row = Row {
id: 1,
values: vec![("name".to_string(), Value::String("Test".into()))],
};
let cond = Condition::Eq("nonexistent".to_string(), Value::Int(5));
assert!(!cond.evaluate(&row));
}
#[test]
fn test_condition_evaluate_type_mismatch_string_int() {
let row = Row {
id: 1,
values: vec![("val".to_string(), Value::String("hello".into()))],
};
let cond = Condition::Eq("val".to_string(), Value::Int(5));
assert!(!cond.evaluate(&row));
}
#[test]
fn test_condition_evaluate_null_comparison() {
let row = Row {
id: 1,
values: vec![("val".to_string(), Value::Null)],
};
let cond_eq = Condition::Eq("val".to_string(), Value::Null);
assert!(cond_eq.evaluate(&row));
let cond_lt = Condition::Lt("val".to_string(), Value::Int(5));
assert!(!cond_lt.evaluate(&row));
}
#[test]
fn test_value_partial_cmp_different_types() {
let int_val = Value::Int(5);
let string_val = Value::String("5".into());
assert!(int_val.partial_cmp_value(&string_val).is_none());
let float_val = Value::Float(5.0);
let bool_val = Value::Bool(true);
assert!(float_val.partial_cmp_value(&bool_val).is_none());
}
#[test]
fn test_value_partial_cmp_same_types() {
assert_eq!(
Value::Int(5).partial_cmp_value(&Value::Int(3)),
Some(std::cmp::Ordering::Greater)
);
assert_eq!(
Value::Int(3).partial_cmp_value(&Value::Int(5)),
Some(std::cmp::Ordering::Less)
);
assert_eq!(
Value::Int(5).partial_cmp_value(&Value::Int(5)),
Some(std::cmp::Ordering::Equal)
);
assert_eq!(
Value::Float(5.5).partial_cmp_value(&Value::Float(3.3)),
Some(std::cmp::Ordering::Greater)
);
assert_eq!(
Value::String("b".into()).partial_cmp_value(&Value::String("a".into())),
Some(std::cmp::Ordering::Greater)
);
}
#[test]
fn test_having_condition_and_combined() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("cat", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("having_and", schema).unwrap();
for i in 0..10 {
engine
.insert(
"having_and",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
for i in 0..5 {
engine
.insert(
"having_and",
HashMap::from([
("cat".to_string(), Value::String("B".into())),
("val".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let groups = engine
.select_grouped(
"having_and",
Condition::True,
&["cat".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::And(
Box::new(HavingCondition::Gt(AggregateRef::CountAll, Value::Int(3))),
Box::new(HavingCondition::Lt(AggregateRef::CountAll, Value::Int(8))),
)),
)
.unwrap();
assert_eq!(groups.len(), 1);
}
#[test]
fn test_having_condition_or_combined() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("cat", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("having_or", schema).unwrap();
for i in 0..10 {
engine
.insert(
"having_or",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
for i in 0..2 {
engine
.insert(
"having_or",
HashMap::from([
("cat".to_string(), Value::String("B".into())),
("val".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let groups = engine
.select_grouped(
"having_or",
Condition::True,
&["cat".to_string()],
&[AggregateExpr::CountAll],
Some(HavingCondition::Or(
Box::new(HavingCondition::Lt(AggregateRef::CountAll, Value::Int(3))),
Box::new(HavingCondition::Gt(AggregateRef::CountAll, Value::Int(8))),
)),
)
.unwrap();
assert_eq!(groups.len(), 2);
}
#[test]
fn test_aggregate_ref_sum() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("cat", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("agg_sum", schema).unwrap();
for i in 1..=5 {
engine
.insert(
"agg_sum",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(i * 10)),
]),
)
.unwrap();
}
let groups = engine
.select_grouped(
"agg_sum",
Condition::True,
&["cat".to_string()],
&[AggregateExpr::Sum("val".to_string())],
Some(HavingCondition::Gt(
AggregateRef::Sum("val".to_string()),
Value::Float(100.0),
)),
)
.unwrap();
assert_eq!(groups.len(), 1);
}
#[test]
fn test_aggregate_ref_avg() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("cat", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("agg_avg", schema).unwrap();
for i in 1..=4 {
engine
.insert(
"agg_avg",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(i * 10)),
]),
)
.unwrap();
}
let groups = engine
.select_grouped(
"agg_avg",
Condition::True,
&["cat".to_string()],
&[AggregateExpr::Avg("val".to_string())],
Some(HavingCondition::Gt(
AggregateRef::Avg("val".to_string()),
Value::Float(20.0),
)),
)
.unwrap();
assert_eq!(groups.len(), 1);
}
#[test]
fn test_aggregate_ref_min() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("cat", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("agg_min", schema).unwrap();
for i in 5..=10 {
engine
.insert(
"agg_min",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let groups = engine
.select_grouped(
"agg_min",
Condition::True,
&["cat".to_string()],
&[AggregateExpr::Min("val".to_string())],
Some(HavingCondition::Gt(
AggregateRef::Min("val".to_string()),
Value::Int(3),
)),
)
.unwrap();
assert_eq!(groups.len(), 1);
}
#[test]
fn test_aggregate_ref_max() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("cat", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("agg_max", schema).unwrap();
for i in 1..=5 {
engine
.insert(
"agg_max",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let groups = engine
.select_grouped(
"agg_max",
Condition::True,
&["cat".to_string()],
&[AggregateExpr::Max("val".to_string())],
Some(HavingCondition::Lt(
AggregateRef::Max("val".to_string()),
Value::Int(10),
)),
)
.unwrap();
assert_eq!(groups.len(), 1);
}
#[test]
fn test_aggregate_ref_count_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("cat", ColumnType::String),
Column::new("val", ColumnType::Int).nullable(),
]);
engine.create_table("agg_count_col", schema).unwrap();
engine
.insert(
"agg_count_col",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(1)),
]),
)
.unwrap();
engine
.insert(
"agg_count_col",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("val".to_string(), Value::Null),
]),
)
.unwrap();
engine
.insert(
"agg_count_col",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("val".to_string(), Value::Int(3)),
]),
)
.unwrap();
let groups = engine
.select_grouped(
"agg_count_col",
Condition::True,
&["cat".to_string()],
&[AggregateExpr::Count("val".to_string())],
Some(HavingCondition::Eq(
AggregateRef::Count("val".to_string()),
Value::Int(2),
)),
)
.unwrap();
assert_eq!(groups.len(), 1);
}
#[test]
fn test_transaction_manager_cleanup_no_expired() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String("Test".into())),
("age".to_string(), Value::Int(25)),
]),
)
.unwrap();
let cleaned_locks = engine.tx_manager.lock_manager().cleanup_expired();
assert_eq!(cleaned_locks, 0);
let cleaned_txs = engine.tx_manager.cleanup_expired();
assert_eq!(cleaned_txs, 0);
}
#[test]
fn test_transaction_cleanup_expired_locks() {
let engine = RelationalEngine::new();
let cleaned = engine.tx_manager.cleanup_expired_locks();
assert_eq!(cleaned, 0);
}
#[test]
fn test_transaction_active_lock_count() {
let engine = RelationalEngine::new();
create_users_table(&engine);
engine
.insert(
"users",
HashMap::from([
("name".to_string(), Value::String("Test".into())),
("age".to_string(), Value::Int(25)),
]),
)
.unwrap();
assert_eq!(engine.tx_manager.active_lock_count(), 0);
let tx_id = engine.begin_transaction();
engine
.tx_update(
tx_id,
"users",
Condition::True,
HashMap::from([("age".to_string(), Value::Int(30))]),
)
.unwrap();
assert_eq!(engine.tx_manager.active_lock_count(), 1);
engine.commit(tx_id).unwrap();
assert_eq!(engine.tx_manager.active_lock_count(), 0);
}
#[test]
fn test_grouped_row_get_key() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("cat", ColumnType::String),
Column::new("sub", ColumnType::String),
Column::new("val", ColumnType::Int),
]);
engine.create_table("grp_key", schema).unwrap();
engine
.insert(
"grp_key",
HashMap::from([
("cat".to_string(), Value::String("A".into())),
("sub".to_string(), Value::String("X".into())),
("val".to_string(), Value::Int(10)),
]),
)
.unwrap();
let groups = engine
.select_grouped(
"grp_key",
Condition::True,
&["cat".to_string(), "sub".to_string()],
&[AggregateExpr::CountAll],
None,
)
.unwrap();
assert_eq!(groups.len(), 1);
assert_eq!(
groups[0].get_key("cat"),
Some(&Value::String("A".to_string()))
);
assert_eq!(
groups[0].get_key("sub"),
Some(&Value::String("X".to_string()))
);
assert_eq!(groups[0].get_key("nonexistent"), None);
}
#[test]
fn test_value_sortable_key_bytes() {
let bytes_val = Value::Bytes(vec![0x01, 0x02, 0xff]);
let key = bytes_val.sortable_key();
assert!(!key.is_empty());
}
#[test]
fn test_value_sortable_key_json() {
let json_val = Value::Json(serde_json::json!({"key": "value"}));
let key = json_val.sortable_key();
assert!(!key.is_empty());
}
#[test]
fn test_value_hash_key_all_types() {
let null_key = Value::Null.hash_key();
let bool_key = Value::Bool(true).hash_key();
let int_key = Value::Int(42).hash_key();
let float_key = Value::Float(3.15).hash_key();
let string_key = Value::String("test".into()).hash_key();
let bytes_key = Value::Bytes(vec![1, 2, 3]).hash_key();
let json_key = Value::Json(serde_json::json!(null)).hash_key();
assert!(!null_key.is_empty());
assert!(!bool_key.is_empty());
assert!(!int_key.is_empty());
assert!(!float_key.is_empty());
assert!(!string_key.is_empty());
assert!(!bytes_key.is_empty());
assert!(!json_key.is_empty());
}
#[test]
fn test_row_contains() {
let row = Row {
id: 1,
values: vec![
("name".to_string(), Value::String("Test".into())),
("age".to_string(), Value::Int(25)),
],
};
assert!(row.contains("name"));
assert!(row.contains("age"));
assert!(!row.contains("nonexistent"));
}
#[test]
fn test_constraint_primary_key_name() {
let pk = Constraint::primary_key("pk_users", vec!["id".to_string()]);
assert_eq!(pk.name(), "pk_users");
}
#[test]
fn test_constraint_unique_name() {
let uq = Constraint::unique("uq_email", vec!["email".to_string()]);
assert_eq!(uq.name(), "uq_email");
}
#[test]
fn test_constraint_foreign_key_name() {
let fk = ForeignKeyConstraint::new(
"fk_parent",
vec!["parent_id".to_string()],
"parents",
vec!["id".to_string()],
);
let constraint = Constraint::foreign_key(fk);
assert_eq!(constraint.name(), "fk_parent");
}
#[test]
fn test_create_table_empty_name() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let result = engine.create_table("", schema);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::InvalidName(_)
));
}
#[test]
fn test_create_table_name_too_long() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let long_name = "a".repeat(300); let result = engine.create_table(&long_name, schema);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::InvalidName(_)
));
}
#[test]
fn test_create_table_name_starts_with_underscore() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let result = engine.create_table("_reserved", schema);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::InvalidName(_)
));
}
#[test]
fn test_create_table_name_contains_colon() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let result = engine.create_table("bad:name", schema);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::InvalidName(_)
));
}
#[test]
fn test_create_table_name_contains_comma() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
let result = engine.create_table("bad,name", schema);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::InvalidName(_)
));
}
#[test]
fn test_too_many_tables_limit() {
let config = RelationalConfig::new().with_max_tables(2);
let engine = RelationalEngine::with_config(config);
engine
.create_table("t1", Schema::new(vec![Column::new("id", ColumnType::Int)]))
.unwrap();
engine
.create_table("t2", Schema::new(vec![Column::new("id", ColumnType::Int)]))
.unwrap();
let result = engine.create_table("t3", Schema::new(vec![Column::new("id", ColumnType::Int)]));
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TooManyTables { .. }
));
}
#[test]
fn test_too_many_indexes_limit() {
let config = RelationalConfig::new().with_max_indexes_per_table(1);
let engine = RelationalEngine::with_config(config);
engine
.create_table(
"indexed",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]),
)
.unwrap();
engine.create_index("indexed", "id").unwrap();
let result = engine.create_index("indexed", "name");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TooManyIndexes { .. }
));
}
#[test]
fn test_query_timeout_clamping() {
let config = RelationalConfig::new()
.with_default_timeout_ms(1000)
.with_max_timeout_ms(500);
let engine = RelationalEngine::with_config(config);
engine
.create_table(
"clamp",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let opts = QueryOptions::new().with_timeout_ms(2000);
let result = engine.select_with_options("clamp", Condition::True, opts);
assert!(result.is_ok());
}
#[test]
fn test_add_nullable_string_column() {
let engine = RelationalEngine::new();
engine
.create_table(
"addcol2",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.add_column(
"addcol2",
Column::new("name", ColumnType::String).nullable(),
)
.unwrap();
let schema = engine.get_schema("addcol2").unwrap();
assert!(schema.get_column("name").is_some());
assert!(schema.get_column("name").unwrap().nullable);
}
#[test]
fn test_add_non_nullable_column_error() {
let engine = RelationalEngine::new();
engine
.create_table(
"addcolnn",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.insert(
"addcolnn",
HashMap::from([("id".to_string(), Value::Int(1))]),
)
.unwrap();
let result = engine.add_column("addcolnn", Column::new("name", ColumnType::String));
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::CannotAddColumn { .. }
));
}
#[test]
fn test_drop_column_blocked_by_unique() {
let engine = RelationalEngine::new();
engine
.create_table(
"dropcol2",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]),
)
.unwrap();
engine
.add_constraint(
"dropcol2",
Constraint::unique("uq_name2", vec!["name".to_string()]),
)
.unwrap();
let result = engine.drop_column("dropcol2", "name");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnHasConstraint { .. }
));
}
#[test]
fn test_rename_column_to_existing() {
let engine = RelationalEngine::new();
engine
.create_table(
"renamecol",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]),
)
.unwrap();
let result = engine.rename_column("renamecol", "id", "name");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnAlreadyExists { .. }
));
}
#[test]
fn test_tx_insert_type_mismatch_error() {
let engine = RelationalEngine::new();
engine
.create_table(
"txins",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let tx_id = engine.begin_transaction();
let result = engine.tx_insert(
tx_id,
"txins",
HashMap::from([("id".to_string(), Value::String("not_int".into()))]),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TypeMismatch { .. }
));
engine.rollback(tx_id).unwrap();
}
#[test]
fn test_tx_update_type_mismatch_error() {
let engine = RelationalEngine::new();
engine
.create_table(
"txupd",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.insert("txupd", HashMap::from([("id".to_string(), Value::Int(1))]))
.unwrap();
let tx_id = engine.begin_transaction();
let result = engine.tx_update(
tx_id,
"txupd",
Condition::True,
HashMap::from([("id".to_string(), Value::String("not_int".into()))]),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TypeMismatch { .. }
));
engine.rollback(tx_id).unwrap();
}
#[test]
fn test_commit_after_commit_returns_not_found() {
let engine = RelationalEngine::new();
engine
.create_table(
"txinact",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let tx_id = engine.begin_transaction();
engine.commit(tx_id).unwrap();
let result = engine.commit(tx_id);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_commit_nonexistent_transaction() {
let engine = RelationalEngine::new();
let result = engine.commit(99999);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_rollback_after_rollback_returns_not_found() {
let engine = RelationalEngine::new();
let tx_id = engine.begin_transaction();
engine.rollback(tx_id).unwrap();
let result = engine.rollback(tx_id);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_add_duplicate_constraint() {
let engine = RelationalEngine::new();
engine
.create_table(
"dupcons",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("email", ColumnType::String),
]),
)
.unwrap();
engine
.add_constraint(
"dupcons",
Constraint::unique("uq_email", vec!["email".to_string()]),
)
.unwrap();
let result = engine.add_constraint(
"dupcons",
Constraint::unique("uq_email", vec!["email".to_string()]),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ConstraintAlreadyExists { .. }
));
}
#[test]
fn test_drop_nonexistent_constraint() {
let engine = RelationalEngine::new();
engine
.create_table(
"nocons",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.drop_constraint("nocons", "nonexistent");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ConstraintNotFound { .. }
));
}
#[test]
fn test_drop_nonexistent_index() {
let engine = RelationalEngine::new();
engine
.create_table(
"noidx",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.drop_index("noidx", "nonexistent");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::IndexNotFound { .. }
));
}
#[test]
fn test_create_duplicate_btree_index() {
let engine = RelationalEngine::new();
engine
.create_table(
"dupbtree",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine.create_btree_index("dupbtree", "id").unwrap();
let result = engine.create_btree_index("dupbtree", "id");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::IndexAlreadyExists { .. }
));
}
#[test]
fn test_select_max_rows_exceeded() {
let config = RelationalConfig::new().with_max_query_result_rows(5);
let engine = RelationalEngine::with_config(config);
engine
.create_table(
"limited",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
for i in 0..10 {
engine
.insert(
"limited",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let result = engine.select("limited", Condition::True);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ResultTooLarge { .. }
));
}
#[test]
fn test_constraint_with_invalid_column() {
let engine = RelationalEngine::new();
engine
.create_table(
"consvalid",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.add_constraint(
"consvalid",
Constraint::unique("uq_bad", vec!["nonexistent".to_string()]),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnNotFound(_)
));
}
#[test]
fn test_select_grouped_invalid_column() {
let engine = RelationalEngine::new();
engine
.create_table(
"grpinvalid",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.select_grouped(
"grpinvalid",
Condition::True,
&["nonexistent".to_string()],
&[AggregateExpr::CountAll],
None,
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnNotFound(_)
));
}
#[test]
fn test_select_grouped_invalid_aggregate_column() {
let engine = RelationalEngine::new();
engine
.create_table(
"grpaggcol",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.select_grouped(
"grpaggcol",
Condition::True,
&["id".to_string()],
&[AggregateExpr::Sum("nonexistent".to_string())],
None,
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnNotFound(_)
));
}
#[test]
fn test_table_scan_count() {
let engine = RelationalEngine::new();
engine
.create_table(
"scancount",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
for i in 0..5 {
engine
.insert(
"scancount",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let count = engine.row_count("scancount").unwrap();
assert_eq!(count, 5);
}
#[test]
fn test_select_distinct_duplicates() {
let engine = RelationalEngine::new();
engine
.create_table(
"distopt",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
for _ in 0..3 {
engine
.insert(
"distopt",
HashMap::from([("id".to_string(), Value::Int(1))]),
)
.unwrap();
}
for _ in 0..2 {
engine
.insert(
"distopt",
HashMap::from([("id".to_string(), Value::Int(2))]),
)
.unwrap();
}
let cols = ["id".to_string()];
let rows = engine
.select_distinct("distopt", Condition::True, Some(&cols[..]))
.unwrap();
assert_eq!(rows.len(), 2); }
#[test]
fn test_batch_insert_type_mismatch() {
let engine = RelationalEngine::new();
engine
.create_table(
"batchtype",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let batch = vec![
HashMap::from([("id".to_string(), Value::Int(1))]),
HashMap::from([("id".to_string(), Value::String("bad".into()))]), ];
let result = engine.batch_insert("batchtype", batch);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TypeMismatch { .. }
));
}
#[test]
fn test_update_with_non_nullable_violation() {
let engine = RelationalEngine::new();
engine
.create_table(
"updnull",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String), ]),
)
.unwrap();
engine
.insert(
"updnull",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("test".into())),
]),
)
.unwrap();
let result = engine.update(
"updnull",
Condition::True,
HashMap::from([("name".to_string(), Value::Null)]),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::NullNotAllowed(_)
));
}
#[test]
fn test_delete_nonexistent_table() {
let engine = RelationalEngine::new();
let result = engine.delete_rows("nonexistent", Condition::True);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TableNotFound(_)
));
}
#[test]
fn test_create_index_on_nonexistent_column() {
let engine = RelationalEngine::new();
engine
.create_table(
"idxcol",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.create_index("idxcol", "nonexistent");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnNotFound(_)
));
}
#[test]
fn test_create_btree_index_on_nonexistent_column() {
let engine = RelationalEngine::new();
engine
.create_table(
"btreecol",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.create_btree_index("btreecol", "nonexistent");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnNotFound(_)
));
}
#[test]
fn test_tx_select_invalid_transaction() {
let engine = RelationalEngine::new();
engine
.create_table(
"txsel",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.tx_select(99999, "txsel", Condition::True);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_tx_delete_invalid_transaction() {
let engine = RelationalEngine::new();
engine
.create_table(
"txdel",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.tx_delete(99999, "txdel", Condition::True);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_tx_insert_invalid_transaction() {
let engine = RelationalEngine::new();
engine
.create_table(
"txinsert",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.tx_insert(
99999,
"txinsert",
HashMap::from([("id".to_string(), Value::Int(1))]),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_tx_update_invalid_transaction() {
let engine = RelationalEngine::new();
engine
.create_table(
"txupdval",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let result = engine.tx_update(
99999,
"txupdval",
Condition::True,
HashMap::from([("id".to_string(), Value::Int(1))]),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_table_exists() {
let engine = RelationalEngine::new();
assert!(!engine.table_exists("nonexistent"));
engine
.create_table(
"exists",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
assert!(engine.table_exists("exists"));
}
#[test]
fn test_list_tables_after_create() {
let engine = RelationalEngine::new();
assert!(engine.list_tables().is_empty());
engine
.create_table(
"table1",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.create_table(
"table2",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let tables = engine.list_tables();
assert_eq!(tables.len(), 2);
assert!(tables.contains(&"table1".to_string()));
assert!(tables.contains(&"table2".to_string()));
}
#[test]
fn test_engine_table_count() {
let engine = RelationalEngine::new();
assert_eq!(engine.table_count(), 0);
engine
.create_table(
"cnt1",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
assert_eq!(engine.table_count(), 1);
engine
.create_table(
"cnt2",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
assert_eq!(engine.table_count(), 2);
}
#[test]
fn test_config_with_max_timeout() {
let config = RelationalConfig::new().with_max_timeout_ms(60_000);
assert_eq!(config.max_query_timeout_ms, Some(60_000));
}
#[test]
fn test_config_with_slow_query_threshold() {
let config = RelationalConfig::new().with_slow_query_threshold_ms(500);
assert_eq!(config.slow_query_threshold_ms, 500);
}
#[test]
fn test_query_options_with_timeout() {
let opts = QueryOptions::new();
assert!(opts.timeout_ms.is_none());
let opts = QueryOptions::new().with_timeout_ms(5000);
assert_eq!(opts.timeout_ms, Some(5000));
}
#[test]
fn test_drop_table_nonexistent() {
let engine = RelationalEngine::new();
let result = engine.drop_table("nonexistent");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TableNotFound(_)
));
}
#[test]
fn test_drop_table_removes_table() {
let engine = RelationalEngine::new();
engine
.create_table(
"todrop",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
assert!(engine.table_exists("todrop"));
engine.drop_table("todrop").unwrap();
assert!(!engine.table_exists("todrop"));
}
#[test]
fn test_insert_null_in_nullable_column() {
let engine = RelationalEngine::new();
engine
.create_table(
"nullable",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String).nullable(),
]),
)
.unwrap();
engine
.insert(
"nullable",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::Null),
]),
)
.unwrap();
let rows = engine.select("nullable", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("name"), Some(&Value::Null));
}
#[test]
fn test_config_validate_default_exceeds_max_timeout() {
let config = RelationalConfig::new()
.with_default_timeout_ms(10_000)
.with_max_timeout_ms(5_000);
let result = config.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("exceeds max_query_timeout_ms"));
}
#[test]
fn test_config_validate_ok_when_default_less_than_max() {
let config = RelationalConfig::new()
.with_default_timeout_ms(1_000)
.with_max_timeout_ms(5_000);
assert!(config.validate().is_ok());
}
#[test]
fn test_config_validate_ok_when_only_max_set() {
let config = RelationalConfig::new().with_max_timeout_ms(60_000);
assert!(config.validate().is_ok());
}
#[test]
fn test_add_foreign_key_local_column_not_found() {
let engine = RelationalEngine::new();
engine
.create_table(
"parent",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.create_table(
"child",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let fk = Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_test".to_string(),
columns: vec!["nonexistent".to_string()],
referenced_table: "parent".to_string(),
referenced_columns: vec!["id".to_string()],
on_delete: ReferentialAction::Restrict,
on_update: ReferentialAction::Restrict,
});
let result = engine.add_constraint("child", fk);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnNotFound(_)
));
}
#[test]
fn test_add_foreign_key_referenced_column_not_found() {
let engine = RelationalEngine::new();
engine
.create_table(
"parent",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.create_table(
"child",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int),
]),
)
.unwrap();
let fk = Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_test".to_string(),
columns: vec!["parent_id".to_string()],
referenced_table: "parent".to_string(),
referenced_columns: vec!["nonexistent".to_string()],
on_delete: ReferentialAction::Restrict,
on_update: ReferentialAction::Restrict,
});
let result = engine.add_constraint("child", fk);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnNotFound(_)
));
}
#[test]
fn test_add_not_null_constraint_column_not_found() {
let engine = RelationalEngine::new();
engine
.create_table(
"test_table",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let not_null = Constraint::not_null("nn_test", "nonexistent");
let result = engine.add_constraint("test_table", not_null);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnNotFound(_)
));
}
#[test]
fn test_columnar_select_condition_true() {
let engine = RelationalEngine::new();
engine
.create_table(
"simd_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"simd_test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("value".to_string(), Value::Int(i * 10)),
]),
)
.unwrap();
}
let rows = engine.select("simd_test", Condition::True).unwrap();
assert_eq!(rows.len(), 100);
}
#[test]
fn test_columnar_select_ne_int() {
let engine = RelationalEngine::new();
engine
.create_table(
"ne_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("status", ColumnType::Int),
]),
)
.unwrap();
for i in 0..50 {
engine
.insert(
"ne_test",
HashMap::from([
("id".to_string(), Value::Int(i)),
(
"status".to_string(),
Value::Int(if i % 2 == 0 { 1 } else { 2 }),
),
]),
)
.unwrap();
}
let rows = engine
.select(
"ne_test",
Condition::Ne("status".to_string(), Value::Int(1)),
)
.unwrap();
assert_eq!(rows.len(), 25); }
#[test]
fn test_columnar_select_lt_int() {
let engine = RelationalEngine::new();
engine
.create_table(
"lt_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"lt_test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("score".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let rows = engine
.select(
"lt_test",
Condition::Lt("score".to_string(), Value::Int(50)),
)
.unwrap();
assert_eq!(rows.len(), 50); }
#[test]
fn test_columnar_select_le_int() {
let engine = RelationalEngine::new();
engine
.create_table(
"le_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"le_test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("score".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let rows = engine
.select(
"le_test",
Condition::Le("score".to_string(), Value::Int(49)),
)
.unwrap();
assert_eq!(rows.len(), 50); }
#[test]
fn test_columnar_select_gt_int() {
let engine = RelationalEngine::new();
engine
.create_table(
"gt_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"gt_test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("score".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let rows = engine
.select(
"gt_test",
Condition::Gt("score".to_string(), Value::Int(50)),
)
.unwrap();
assert_eq!(rows.len(), 49); }
#[test]
fn test_columnar_select_ge_int() {
let engine = RelationalEngine::new();
engine
.create_table(
"ge_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"ge_test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("score".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let rows = engine
.select(
"ge_test",
Condition::Ge("score".to_string(), Value::Int(50)),
)
.unwrap();
assert_eq!(rows.len(), 50); }
#[test]
fn test_columnar_select_lt_float() {
let engine = RelationalEngine::new();
engine
.create_table(
"float_lt",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("price", ColumnType::Float),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"float_lt",
HashMap::from([
("id".to_string(), Value::Int(i)),
("price".to_string(), Value::Float(i as f64 * 1.5)),
]),
)
.unwrap();
}
let rows = engine
.select(
"float_lt",
Condition::Lt("price".to_string(), Value::Float(75.0)),
)
.unwrap();
assert_eq!(rows.len(), 50); }
#[test]
fn test_columnar_select_le_float() {
let engine = RelationalEngine::new();
engine
.create_table(
"float_le",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("price", ColumnType::Float),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"float_le",
HashMap::from([
("id".to_string(), Value::Int(i)),
("price".to_string(), Value::Float(i as f64)),
]),
)
.unwrap();
}
let rows = engine
.select(
"float_le",
Condition::Le("price".to_string(), Value::Float(49.0)),
)
.unwrap();
assert_eq!(rows.len(), 50);
}
#[test]
fn test_columnar_select_gt_float() {
let engine = RelationalEngine::new();
engine
.create_table(
"float_gt",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("price", ColumnType::Float),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"float_gt",
HashMap::from([
("id".to_string(), Value::Int(i)),
("price".to_string(), Value::Float(i as f64)),
]),
)
.unwrap();
}
let rows = engine
.select(
"float_gt",
Condition::Gt("price".to_string(), Value::Float(49.0)),
)
.unwrap();
assert_eq!(rows.len(), 50);
}
#[test]
fn test_columnar_select_ge_float() {
let engine = RelationalEngine::new();
engine
.create_table(
"float_ge",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("price", ColumnType::Float),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"float_ge",
HashMap::from([
("id".to_string(), Value::Int(i)),
("price".to_string(), Value::Float(i as f64)),
]),
)
.unwrap();
}
let rows = engine
.select(
"float_ge",
Condition::Ge("price".to_string(), Value::Float(50.0)),
)
.unwrap();
assert_eq!(rows.len(), 50);
}
#[test]
fn test_columnar_select_ne_float() {
let engine = RelationalEngine::new();
engine
.create_table(
"float_ne",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("price", ColumnType::Float),
]),
)
.unwrap();
for i in 0..50 {
engine
.insert(
"float_ne",
HashMap::from([
("id".to_string(), Value::Int(i)),
(
"price".to_string(),
Value::Float(if i % 2 == 0 { 10.0 } else { 20.0 }),
),
]),
)
.unwrap();
}
let rows = engine
.select(
"float_ne",
Condition::Ne("price".to_string(), Value::Float(10.0)),
)
.unwrap();
assert_eq!(rows.len(), 25);
}
#[test]
fn test_columnar_select_empty_table_ne() {
let engine = RelationalEngine::new();
engine
.create_table(
"empty_ne",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]),
)
.unwrap();
let rows = engine
.select(
"empty_ne",
Condition::Ne("value".to_string(), Value::Int(1)),
)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_columnar_select_empty_table_lt() {
let engine = RelationalEngine::new();
engine
.create_table(
"empty_lt",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]),
)
.unwrap();
let rows = engine
.select(
"empty_lt",
Condition::Lt("value".to_string(), Value::Int(10)),
)
.unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_index_lookup_result_too_large() {
let config = RelationalConfig::new().with_max_query_result_rows(5);
let engine = RelationalEngine::with_config(config);
engine
.create_table(
"indexed_limit",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("category", ColumnType::Int),
]),
)
.unwrap();
engine.create_index("indexed_limit", "category").unwrap();
for i in 0..10 {
engine
.insert(
"indexed_limit",
HashMap::from([
("id".to_string(), Value::Int(i)),
("category".to_string(), Value::Int(1)), ]),
)
.unwrap();
}
let result = engine.select(
"indexed_limit",
Condition::Eq("category".to_string(), Value::Int(1)),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ResultTooLarge { .. }
));
}
#[test]
fn test_columnar_and_condition() {
let engine = RelationalEngine::new();
engine
.create_table(
"and_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("x", ColumnType::Int),
Column::new("y", ColumnType::Int),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"and_test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("x".to_string(), Value::Int(i)),
("y".to_string(), Value::Int(100 - i)),
]),
)
.unwrap();
}
let rows = engine
.select(
"and_test",
Condition::Gt("x".to_string(), Value::Int(25))
.and(Condition::Gt("y".to_string(), Value::Int(25))),
)
.unwrap();
assert_eq!(rows.len(), 49);
}
#[test]
fn test_columnar_or_condition() {
let engine = RelationalEngine::new();
engine
.create_table(
"or_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("status", ColumnType::Int),
]),
)
.unwrap();
for i in 0..100 {
engine
.insert(
"or_test",
HashMap::from([
("id".to_string(), Value::Int(i)),
("status".to_string(), Value::Int(i % 3)),
]),
)
.unwrap();
}
let rows = engine
.select(
"or_test",
Condition::Eq("status".to_string(), Value::Int(0))
.or(Condition::Eq("status".to_string(), Value::Int(2))),
)
.unwrap();
assert_eq!(rows.len(), 67);
}
#[test]
fn test_cross_join_empty_left_table() {
let engine = RelationalEngine::new();
engine
.create_table(
"left_empty",
Schema::new(vec![Column::new("a", ColumnType::Int)]),
)
.unwrap();
engine
.create_table(
"right_full",
Schema::new(vec![Column::new("b", ColumnType::Int)]),
)
.unwrap();
engine
.insert(
"right_full",
HashMap::from([("b".to_string(), Value::Int(1))]),
)
.unwrap();
let rows = engine.cross_join("left_empty", "right_full").unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_cross_join_empty_right_table() {
let engine = RelationalEngine::new();
engine
.create_table(
"left_full",
Schema::new(vec![Column::new("a", ColumnType::Int)]),
)
.unwrap();
engine
.create_table(
"right_empty",
Schema::new(vec![Column::new("b", ColumnType::Int)]),
)
.unwrap();
engine
.insert(
"left_full",
HashMap::from([("a".to_string(), Value::Int(1))]),
)
.unwrap();
let rows = engine.cross_join("left_full", "right_empty").unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_config_with_default_timeout() {
let config = RelationalConfig::new().with_default_timeout_ms(5000);
assert_eq!(config.default_query_timeout_ms, Some(5000));
}
#[test]
fn test_config_with_max_btree_entries() {
let config = RelationalConfig::new().with_max_btree_entries(50_000);
assert_eq!(config.max_btree_entries, 50_000);
}
#[test]
fn test_select_iter_offset_exceeds_rows() {
let engine = RelationalEngine::new();
engine
.create_table(
"iter_test",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
for i in 0..5 {
engine
.insert(
"iter_test",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let cursor = engine
.select_iter(
"iter_test",
Condition::True,
CursorOptions::new().with_offset(10),
)
.unwrap();
let rows: Vec<_> = cursor.collect();
assert!(rows.is_empty());
}
#[test]
fn test_select_iter_with_offset_only() {
let engine = RelationalEngine::new();
engine
.create_table(
"offset_only",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
for i in 0..10 {
engine
.insert(
"offset_only",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let cursor = engine
.select_iter(
"offset_only",
Condition::True,
CursorOptions::new().with_offset(5),
)
.unwrap();
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 5); }
#[test]
fn test_add_not_null_constraint_fails_with_existing_nulls() {
let engine = RelationalEngine::new();
engine
.create_table(
"nullable_data",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String).nullable(),
]),
)
.unwrap();
engine
.insert(
"nullable_data",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::Null),
]),
)
.unwrap();
let not_null = Constraint::not_null("nn_name", "name");
let result = engine.add_constraint("nullable_data", not_null);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::ColumnHasConstraint { .. }
));
}
#[test]
fn test_aggregate_sum_with_non_numeric() {
let engine = RelationalEngine::new();
engine
.create_table(
"mixed_types",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("category", ColumnType::String),
Column::new("value", ColumnType::Int),
]),
)
.unwrap();
engine
.insert(
"mixed_types",
HashMap::from([
("id".to_string(), Value::Int(1)),
("category".to_string(), Value::String("A".to_string())),
("value".to_string(), Value::Int(10)),
]),
)
.unwrap();
engine
.insert(
"mixed_types",
HashMap::from([
("id".to_string(), Value::Int(2)),
("category".to_string(), Value::String("A".to_string())),
("value".to_string(), Value::Int(20)),
]),
)
.unwrap();
let result = engine
.select_grouped(
"mixed_types",
Condition::True,
&[],
&[AggregateExpr::Sum("category".to_string())],
None,
)
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result[0].get_aggregate("sum_category"),
Some(&AggregateValue::Sum(0.0))
);
}
#[test]
fn test_aggregate_avg_empty_result_set() {
let engine = RelationalEngine::new();
engine
.create_table(
"avg_filter",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]),
)
.unwrap();
engine
.insert(
"avg_filter",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(10)),
]),
)
.unwrap();
let result = engine
.select_grouped(
"avg_filter",
Condition::Eq("id".to_string(), Value::Int(999)), &[],
&[AggregateExpr::Avg("value".to_string())],
None,
)
.unwrap();
assert_eq!(result.len(), 0);
}
#[test]
fn test_aggregate_avg_with_non_numeric() {
let engine = RelationalEngine::new();
engine
.create_table(
"avg_string",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]),
)
.unwrap();
engine
.insert(
"avg_string",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("test".to_string())),
]),
)
.unwrap();
let result = engine
.select_grouped(
"avg_string",
Condition::True,
&[],
&[AggregateExpr::Avg("name".to_string())],
None,
)
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result[0].get_aggregate("avg_name"),
Some(&AggregateValue::Avg(None))
);
}
#[test]
fn test_aggregate_min_with_nulls() {
let engine = RelationalEngine::new();
engine
.create_table(
"min_nulls",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int).nullable(),
]),
)
.unwrap();
engine
.insert(
"min_nulls",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Null),
]),
)
.unwrap();
engine
.insert(
"min_nulls",
HashMap::from([
("id".to_string(), Value::Int(2)),
("value".to_string(), Value::Int(10)),
]),
)
.unwrap();
engine
.insert(
"min_nulls",
HashMap::from([
("id".to_string(), Value::Int(3)),
("value".to_string(), Value::Int(5)),
]),
)
.unwrap();
let result = engine
.select_grouped(
"min_nulls",
Condition::True,
&[],
&[AggregateExpr::Min("value".to_string())],
None,
)
.unwrap();
assert_eq!(
result[0].get_aggregate("min_value"),
Some(&AggregateValue::Min(Some(Value::Int(5))))
);
}
#[test]
fn test_aggregate_max_with_nulls() {
let engine = RelationalEngine::new();
engine
.create_table(
"max_nulls",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int).nullable(),
]),
)
.unwrap();
engine
.insert(
"max_nulls",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Null),
]),
)
.unwrap();
engine
.insert(
"max_nulls",
HashMap::from([
("id".to_string(), Value::Int(2)),
("value".to_string(), Value::Int(10)),
]),
)
.unwrap();
engine
.insert(
"max_nulls",
HashMap::from([
("id".to_string(), Value::Int(3)),
("value".to_string(), Value::Int(5)),
]),
)
.unwrap();
let result = engine
.select_grouped(
"max_nulls",
Condition::True,
&[],
&[AggregateExpr::Max("value".to_string())],
None,
)
.unwrap();
assert_eq!(
result[0].get_aggregate("max_value"),
Some(&AggregateValue::Max(Some(Value::Int(10))))
);
}
#[test]
fn test_aggregate_min_finds_smaller_value() {
let engine = RelationalEngine::new();
engine
.create_table(
"min_compare",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]),
)
.unwrap();
engine
.insert(
"min_compare",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(100)),
]),
)
.unwrap();
engine
.insert(
"min_compare",
HashMap::from([
("id".to_string(), Value::Int(2)),
("value".to_string(), Value::Int(50)),
]),
)
.unwrap();
engine
.insert(
"min_compare",
HashMap::from([
("id".to_string(), Value::Int(3)),
("value".to_string(), Value::Int(75)),
]),
)
.unwrap();
let result = engine
.select_grouped(
"min_compare",
Condition::True,
&[],
&[AggregateExpr::Min("value".to_string())],
None,
)
.unwrap();
assert_eq!(
result[0].get_aggregate("min_value"),
Some(&AggregateValue::Min(Some(Value::Int(50))))
);
}
#[test]
fn test_update_with_type_mismatch() {
let engine = RelationalEngine::new();
engine
.create_table(
"update_err",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]),
)
.unwrap();
engine
.insert(
"update_err",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(10)),
]),
)
.unwrap();
let result = engine.update(
"update_err",
Condition::True,
HashMap::from([("value".to_string(), Value::String("not an int".to_string()))]),
);
assert!(result.is_err());
}
#[test]
fn test_columnar_eq_string() {
let engine = RelationalEngine::new();
engine
.create_table(
"string_eq",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]),
)
.unwrap();
for i in 0..50 {
engine
.insert(
"string_eq",
HashMap::from([
("id".to_string(), Value::Int(i)),
(
"name".to_string(),
Value::String(if i % 2 == 0 {
"even".to_string()
} else {
"odd".to_string()
}),
),
]),
)
.unwrap();
}
let rows = engine
.select(
"string_eq",
Condition::Eq("name".to_string(), Value::String("even".to_string())),
)
.unwrap();
assert_eq!(rows.len(), 25);
}
#[test]
fn test_unique_constraint_validation_fails() {
let engine = RelationalEngine::new();
engine
.create_table(
"unique_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("code", ColumnType::String),
]),
)
.unwrap();
engine
.insert(
"unique_test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("code".to_string(), Value::String("ABC".to_string())),
]),
)
.unwrap();
engine
.insert(
"unique_test",
HashMap::from([
("id".to_string(), Value::Int(2)),
("code".to_string(), Value::String("ABC".to_string())),
]),
)
.unwrap();
let unique = Constraint::unique("uq_code", vec!["code".to_string()]);
let result = engine.add_constraint("unique_test", unique);
assert!(result.is_err());
}
#[test]
fn test_primary_key_constraint_validation_fails() {
let engine = RelationalEngine::new();
engine
.create_table(
"pk_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]),
)
.unwrap();
engine
.insert(
"pk_test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(10)),
]),
)
.unwrap();
engine
.insert(
"pk_test",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(20)),
]),
)
.unwrap();
let pk = Constraint::primary_key("pk_id", vec!["id".to_string()]);
let result = engine.add_constraint("pk_test", pk);
assert!(result.is_err());
}
#[test]
fn test_foreign_key_validation_fails_on_existing_data() {
let engine = RelationalEngine::new();
engine
.create_table(
"fk_parent",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.create_table(
"fk_child",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int),
]),
)
.unwrap();
engine
.insert(
"fk_parent",
HashMap::from([("id".to_string(), Value::Int(1))]),
)
.unwrap();
engine
.insert(
"fk_child",
HashMap::from([
("id".to_string(), Value::Int(1)),
("parent_id".to_string(), Value::Int(999)), ]),
)
.unwrap();
let fk = Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_parent".to_string(),
columns: vec!["parent_id".to_string()],
referenced_table: "fk_parent".to_string(),
referenced_columns: vec!["id".to_string()],
on_delete: ReferentialAction::Restrict,
on_update: ReferentialAction::Restrict,
});
let result = engine.add_constraint("fk_child", fk);
assert!(result.is_err());
}
#[test]
fn test_foreign_key_null_value_allowed() {
let engine = RelationalEngine::new();
engine
.create_table(
"fk_null_parent",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.create_table(
"fk_null_child",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int).nullable(),
]),
)
.unwrap();
engine
.insert(
"fk_null_parent",
HashMap::from([("id".to_string(), Value::Int(1))]),
)
.unwrap();
engine
.insert(
"fk_null_child",
HashMap::from([
("id".to_string(), Value::Int(1)),
("parent_id".to_string(), Value::Null),
]),
)
.unwrap();
let fk = Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_parent".to_string(),
columns: vec!["parent_id".to_string()],
referenced_table: "fk_null_parent".to_string(),
referenced_columns: vec!["id".to_string()],
on_delete: ReferentialAction::Restrict,
on_update: ReferentialAction::Restrict,
});
let result = engine.add_constraint("fk_null_child", fk);
assert!(result.is_ok());
}
#[test]
fn test_drop_foreign_key_constraint() {
let engine = RelationalEngine::new();
engine
.create_table(
"drop_fk_parent",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.create_table(
"drop_fk_child",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int),
]),
)
.unwrap();
engine
.insert(
"drop_fk_parent",
HashMap::from([("id".to_string(), Value::Int(1))]),
)
.unwrap();
engine
.insert(
"drop_fk_child",
HashMap::from([
("id".to_string(), Value::Int(1)),
("parent_id".to_string(), Value::Int(1)),
]),
)
.unwrap();
let fk = Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_drop_test".to_string(),
columns: vec!["parent_id".to_string()],
referenced_table: "drop_fk_parent".to_string(),
referenced_columns: vec!["id".to_string()],
on_delete: ReferentialAction::Restrict,
on_update: ReferentialAction::Restrict,
});
engine.add_constraint("drop_fk_child", fk).unwrap();
let constraints = engine.get_constraints("drop_fk_child").unwrap();
assert_eq!(constraints.len(), 1);
let result = engine.drop_constraint("drop_fk_child", "fk_drop_test");
assert!(result.is_ok());
let constraints = engine.get_constraints("drop_fk_child").unwrap();
assert!(constraints.is_empty());
}
#[test]
fn test_bytes_column_type() {
let engine = RelationalEngine::new();
engine
.create_table(
"bytes_table",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::Bytes),
]),
)
.unwrap();
let bytes_data = vec![0x01, 0x02, 0x03, 0xff];
engine
.insert(
"bytes_table",
HashMap::from([
("id".to_string(), Value::Int(1)),
("data".to_string(), Value::Bytes(bytes_data.clone())),
]),
)
.unwrap();
let rows = engine.select("bytes_table", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("data"), Some(&Value::Bytes(bytes_data)));
}
#[test]
fn test_json_column_type() {
let engine = RelationalEngine::new();
engine
.create_table(
"json_table",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("meta", ColumnType::Json),
]),
)
.unwrap();
let json_val = serde_json::json!({"key": "value", "num": 42});
engine
.insert(
"json_table",
HashMap::from([
("id".to_string(), Value::Int(1)),
("meta".to_string(), Value::Json(json_val)),
]),
)
.unwrap();
let rows = engine.select("json_table", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_constraint_cache_updates_on_add() {
let engine = RelationalEngine::new();
engine
.create_table(
"cache_test",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("code", ColumnType::String),
]),
)
.unwrap();
let constraints = engine.get_constraints("cache_test").unwrap();
assert!(constraints.is_empty());
let unique = Constraint::unique("uq_cache_code", vec!["code".to_string()]);
engine.add_constraint("cache_test", unique).unwrap();
let constraints = engine.get_constraints("cache_test").unwrap();
assert_eq!(constraints.len(), 1);
}
#[test]
fn test_constraint_cache_updates_on_drop() {
let engine = RelationalEngine::new();
engine
.create_table(
"cache_drop",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("code", ColumnType::String),
]),
)
.unwrap();
let unique1 = Constraint::unique("uq1", vec!["code".to_string()]);
let unique2 = Constraint::unique("uq2", vec!["id".to_string()]);
engine.add_constraint("cache_drop", unique1).unwrap();
engine.add_constraint("cache_drop", unique2).unwrap();
let constraints = engine.get_constraints("cache_drop").unwrap();
assert_eq!(constraints.len(), 2);
engine.drop_constraint("cache_drop", "uq1").unwrap();
let constraints = engine.get_constraints("cache_drop").unwrap();
assert_eq!(constraints.len(), 1);
}
#[test]
fn test_fk_reference_conditions_empty() {
let engine = RelationalEngine::new();
engine
.create_table(
"fk_ref_parent",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
engine
.create_table(
"fk_ref_child",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int).nullable(),
]),
)
.unwrap();
engine
.insert(
"fk_ref_parent",
HashMap::from([("id".to_string(), Value::Int(1))]),
)
.unwrap();
engine
.insert(
"fk_ref_child",
HashMap::from([
("id".to_string(), Value::Int(1)),
("parent_id".to_string(), Value::Int(1)),
]),
)
.unwrap();
let fk = Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_ref_parent".to_string(),
columns: vec!["parent_id".to_string()],
referenced_table: "fk_ref_parent".to_string(),
referenced_columns: vec!["id".to_string()],
on_delete: ReferentialAction::Restrict,
on_update: ReferentialAction::Restrict,
});
let result = engine.add_constraint("fk_ref_child", fk);
assert!(result.is_ok());
}
#[test]
fn test_join_basic() {
let engine = RelationalEngine::new();
engine
.create_table(
"join_left",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]),
)
.unwrap();
engine
.create_table(
"join_right",
Schema::new(vec![
Column::new("left_id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]),
)
.unwrap();
engine
.insert(
"join_left",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("Alice".to_string())),
]),
)
.unwrap();
engine
.insert(
"join_left",
HashMap::from([
("id".to_string(), Value::Int(2)),
("name".to_string(), Value::String("Bob".to_string())),
]),
)
.unwrap();
engine
.insert(
"join_right",
HashMap::from([
("left_id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(100)),
]),
)
.unwrap();
engine
.insert(
"join_right",
HashMap::from([
("left_id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(200)),
]),
)
.unwrap();
let results = engine
.join("join_left", "join_right", "id", "left_id")
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_select_with_limit() {
let engine = RelationalEngine::new();
engine
.create_table(
"limit_test",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
for i in 0..20 {
engine
.insert(
"limit_test",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let cursor = engine
.select_iter(
"limit_test",
Condition::True,
CursorOptions::new().with_limit(5),
)
.unwrap();
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_select_iter_combined_limit_offset() {
let engine = RelationalEngine::new();
engine
.create_table(
"combined_opts",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
for i in 0..20 {
engine
.insert(
"combined_opts",
HashMap::from([("id".to_string(), Value::Int(i))]),
)
.unwrap();
}
let cursor = engine
.select_iter(
"combined_opts",
Condition::True,
CursorOptions::new().with_offset(5).with_limit(5),
)
.unwrap();
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_config_with_max_query_result_rows() {
let config = RelationalConfig::new().with_max_query_result_rows(100);
assert_eq!(config.max_query_result_rows, Some(100));
}
#[test]
fn test_aggregate_sum_float() {
let engine = RelationalEngine::new();
engine
.create_table(
"sum_float",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("price", ColumnType::Float),
]),
)
.unwrap();
engine
.insert(
"sum_float",
HashMap::from([
("id".to_string(), Value::Int(1)),
("price".to_string(), Value::Float(10.5)),
]),
)
.unwrap();
engine
.insert(
"sum_float",
HashMap::from([
("id".to_string(), Value::Int(2)),
("price".to_string(), Value::Float(20.5)),
]),
)
.unwrap();
let result = engine
.select_grouped(
"sum_float",
Condition::True,
&[],
&[AggregateExpr::Sum("price".to_string())],
None,
)
.unwrap();
assert_eq!(result.len(), 1);
if let Some(AggregateValue::Sum(sum)) = result[0].get_aggregate("sum_price") {
assert!((sum - 31.0).abs() < 0.01);
} else {
panic!("Expected Sum aggregate");
}
}
#[test]
fn test_aggregate_avg_float() {
let engine = RelationalEngine::new();
engine
.create_table(
"avg_float",
Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Float),
]),
)
.unwrap();
engine
.insert(
"avg_float",
HashMap::from([
("id".to_string(), Value::Int(1)),
("score".to_string(), Value::Float(80.0)),
]),
)
.unwrap();
engine
.insert(
"avg_float",
HashMap::from([
("id".to_string(), Value::Int(2)),
("score".to_string(), Value::Float(90.0)),
]),
)
.unwrap();
let result = engine
.select_grouped(
"avg_float",
Condition::True,
&[],
&[AggregateExpr::Avg("score".to_string())],
None,
)
.unwrap();
assert_eq!(result.len(), 1);
if let Some(AggregateValue::Avg(Some(avg))) = result[0].get_aggregate("avg_score") {
assert!((avg - 85.0).abs() < 0.01);
} else {
panic!("Expected Avg aggregate");
}
}
#[test]
fn test_tx_manager_accessor() {
let engine = RelationalEngine::new();
let tx_id = engine.begin_transaction();
let tx_manager = engine.tx_manager();
assert!(tx_manager.is_active(tx_id));
engine.commit(tx_id).unwrap();
assert!(!tx_manager.is_active(tx_id));
}
#[test]
fn test_commit_inactive_transaction() {
let engine = RelationalEngine::new();
let tx_id = engine.begin_transaction();
engine.commit(tx_id).unwrap();
let result = engine.commit(tx_id);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_rollback_inactive_transaction() {
let engine = RelationalEngine::new();
let tx_id = engine.begin_transaction();
engine.commit(tx_id).unwrap();
let result = engine.rollback(tx_id);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_tx_select_inactive_transaction() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("test_tx_sel", schema).unwrap();
let tx_id = engine.begin_transaction();
engine.commit(tx_id).unwrap();
let result = engine.tx_select(tx_id, "test_tx_sel", Condition::True);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_tx_delete_with_indexes() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("tx_del_idx", schema).unwrap();
engine.create_index("tx_del_idx", "id").unwrap();
engine.create_btree_index("tx_del_idx", "id").unwrap();
engine
.insert(
"tx_del_idx",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("test".into())),
]),
)
.unwrap();
let tx_id = engine.begin_transaction();
let deleted = engine
.tx_delete(
tx_id,
"tx_del_idx",
Condition::Eq("id".into(), Value::Int(1)),
)
.unwrap();
assert_eq!(deleted, 1);
engine.commit(tx_id).unwrap();
let rows = engine.select("tx_del_idx", Condition::True).unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn test_tx_update_with_indexes() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("tx_upd_idx", schema).unwrap();
engine.create_index("tx_upd_idx", "value").unwrap();
engine.create_btree_index("tx_upd_idx", "value").unwrap();
engine
.insert(
"tx_upd_idx",
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(100)),
]),
)
.unwrap();
let tx_id = engine.begin_transaction();
let updated = engine
.tx_update(
tx_id,
"tx_upd_idx",
Condition::Eq("id".into(), Value::Int(1)),
HashMap::from([("value".to_string(), Value::Int(200))]),
)
.unwrap();
assert_eq!(updated, 1);
engine.commit(tx_id).unwrap();
let rows = engine.select("tx_upd_idx", Condition::True).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("value"), Some(&Value::Int(200)));
}
#[test]
fn test_batch_insert_with_hash_index() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("batch_hash_idx", schema).unwrap();
engine.create_index("batch_hash_idx", "value").unwrap();
let rows = vec![
HashMap::from([
("id".to_string(), Value::Int(1)),
("value".to_string(), Value::Int(100)),
]),
HashMap::from([
("id".to_string(), Value::Int(2)),
("value".to_string(), Value::Int(200)),
]),
];
let ids = engine.batch_insert("batch_hash_idx", rows).unwrap();
assert_eq!(ids.len(), 2);
let results = engine
.select(
"batch_hash_idx",
Condition::Eq("value".into(), Value::Int(100)),
)
.unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_batch_insert_with_btree_index_coverage() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("score", ColumnType::Int),
]);
engine.create_table("batch_btree_cov", schema).unwrap();
engine
.create_btree_index("batch_btree_cov", "score")
.unwrap();
let rows = vec![
HashMap::from([
("id".to_string(), Value::Int(1)),
("score".to_string(), Value::Int(50)),
]),
HashMap::from([
("id".to_string(), Value::Int(2)),
("score".to_string(), Value::Int(75)),
]),
HashMap::from([
("id".to_string(), Value::Int(3)),
("score".to_string(), Value::Int(90)),
]),
];
let ids = engine.batch_insert("batch_btree_cov", rows).unwrap();
assert_eq!(ids.len(), 3);
let results = engine
.select(
"batch_btree_cov",
Condition::Gt("score".into(), Value::Int(60)),
)
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_tx_insert_with_not_found_tx() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("tx_ins_notfound", schema).unwrap();
let result = engine.tx_insert(
99999,
"tx_ins_notfound",
HashMap::from([("id".to_string(), Value::Int(1))]),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_tx_update_with_not_found_tx() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("tx_upd_notfound", schema).unwrap();
let result = engine.tx_update(
99999,
"tx_upd_notfound",
Condition::True,
HashMap::from([("id".to_string(), Value::Int(1))]),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_tx_delete_with_not_found_tx() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("tx_del_notfound", schema).unwrap();
let result = engine.tx_delete(99999, "tx_del_notfound", Condition::True);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RelationalError::TransactionNotFound(_)
));
}
#[test]
fn test_tx_insert_with_indexes() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("tx_ins_idx", schema).unwrap();
engine.create_index("tx_ins_idx", "val").unwrap();
engine.create_btree_index("tx_ins_idx", "val").unwrap();
let tx_id = engine.begin_transaction();
let row_id = engine
.tx_insert(
tx_id,
"tx_ins_idx",
HashMap::from([
("id".to_string(), Value::Int(1)),
("val".to_string(), Value::Int(100)),
]),
)
.unwrap();
assert!(row_id > 0);
engine.commit(tx_id).unwrap();
let rows = engine
.select("tx_ins_idx", Condition::Eq("val".into(), Value::Int(100)))
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn test_validate_fk_with_null_value() {
let engine = RelationalEngine::new();
engine
.create_table(
"parent_fk_null",
Schema::new(vec![Column::new("id", ColumnType::Int)]),
)
.unwrap();
let mut schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int).nullable(),
]);
schema.add_constraint(Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_parent".to_string(),
columns: vec!["parent_id".to_string()],
referenced_table: "parent_fk_null".to_string(),
referenced_columns: vec!["id".to_string()],
on_delete: ReferentialAction::Restrict,
on_update: ReferentialAction::Restrict,
}));
engine.create_table("child_fk_null", schema).unwrap();
let result = engine.insert(
"child_fk_null",
HashMap::from([
("id".to_string(), Value::Int(1)),
("parent_id".to_string(), Value::Null),
]),
);
assert!(result.is_ok());
}
#[test]
fn test_select_with_empty_projection() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("empty_proj", schema).unwrap();
engine
.insert(
"empty_proj",
HashMap::from([
("id".to_string(), Value::Int(1)),
("name".to_string(), Value::String("test".into())),
]),
)
.unwrap();
let rows = engine
.select_with_projection("empty_proj", Condition::True, Some(vec![]))
.unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].values.is_empty());
}
#[test]
fn test_add_column_float_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("add_float_col", schema).unwrap();
engine
.add_column("add_float_col", Column::new("price", ColumnType::Float))
.unwrap();
let schema = engine.get_schema("add_float_col").unwrap();
assert_eq!(schema.columns.len(), 2);
assert_eq!(schema.columns[1].column_type, ColumnType::Float);
}
#[test]
fn test_add_column_bool_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("add_bool_col", schema).unwrap();
engine
.add_column("add_bool_col", Column::new("active", ColumnType::Bool))
.unwrap();
let schema = engine.get_schema("add_bool_col").unwrap();
assert_eq!(schema.columns.len(), 2);
assert_eq!(schema.columns[1].column_type, ColumnType::Bool);
}
#[test]
fn test_add_column_bytes_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("add_bytes_col", schema).unwrap();
engine
.add_column("add_bytes_col", Column::new("data", ColumnType::Bytes))
.unwrap();
let schema = engine.get_schema("add_bytes_col").unwrap();
assert_eq!(schema.columns.len(), 2);
assert_eq!(schema.columns[1].column_type, ColumnType::Bytes);
}
#[test]
fn test_add_column_json_type() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("add_json_col", schema).unwrap();
engine
.add_column("add_json_col", Column::new("meta", ColumnType::Json))
.unwrap();
let schema = engine.get_schema("add_json_col").unwrap();
assert_eq!(schema.columns.len(), 2);
assert_eq!(schema.columns[1].column_type, ColumnType::Json);
}
#[test]
fn test_columnar_ne_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("empty_ne", schema).unwrap();
let results = engine
.select_columnar(
"empty_ne",
Condition::Ne("val".into(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_lt_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("empty_lt", schema).unwrap();
let results = engine
.select_columnar(
"empty_lt",
Condition::Lt("val".into(), Value::Int(10)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_le_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("empty_le", schema).unwrap();
let results = engine
.select_columnar(
"empty_le",
Condition::Le("val".into(), Value::Int(10)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_gt_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("empty_gt", schema).unwrap();
let results = engine
.select_columnar(
"empty_gt",
Condition::Gt("val".into(), Value::Int(0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_ge_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("empty_ge", schema).unwrap();
let results = engine
.select_columnar(
"empty_ge",
Condition::Ge("val".into(), Value::Int(0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_float_lt_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("empty_flt_lt", schema).unwrap();
let results = engine
.select_columnar(
"empty_flt_lt",
Condition::Lt("val".into(), Value::Float(10.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_float_gt_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("empty_flt_gt", schema).unwrap();
let results = engine
.select_columnar(
"empty_flt_gt",
Condition::Gt("val".into(), Value::Float(0.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_float_eq_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("empty_flt_eq", schema).unwrap();
let results = engine
.select_columnar(
"empty_flt_eq",
Condition::Eq("val".into(), Value::Float(5.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_true_condition() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("coltrue", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("val".to_string(), Value::Int(10));
engine.insert("coltrue", values).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(2));
values.insert("val".to_string(), Value::Int(20));
engine.insert("coltrue", values).unwrap();
let results = engine
.select_columnar("coltrue", Condition::True, ColumnarScanOptions::default())
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_columnar_and_with_true() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("and_true", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("val".to_string(), Value::Int(10));
engine.insert("and_true", values).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(2));
values.insert("val".to_string(), Value::Int(20));
engine.insert("and_true", values).unwrap();
let results = engine
.select_columnar(
"and_true",
Condition::And(
Box::new(Condition::True),
Box::new(Condition::Eq("val".into(), Value::Int(10))),
),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_columnar_or_with_true() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("or_true", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("val".to_string(), Value::Int(10));
engine.insert("or_true", values).unwrap();
let results = engine
.select_columnar(
"or_true",
Condition::Or(
Box::new(Condition::True),
Box::new(Condition::Eq("val".into(), Value::Int(10))),
),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_grouped_sum_string_column() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("dept", ColumnType::String),
]);
engine.create_table("grp_sum_str", schema).unwrap();
let mut values = HashMap::new();
values.insert("name".to_string(), Value::String("Alice".to_string()));
values.insert("dept".to_string(), Value::String("Sales".to_string()));
engine.insert("grp_sum_str", values).unwrap();
let group_by = vec!["dept".to_string()];
let result = engine
.select_grouped(
"grp_sum_str",
Condition::True,
&group_by,
&[AggregateExpr::Sum("name".to_string())],
None,
)
.unwrap();
assert_eq!(result.len(), 1);
if let AggregateValue::Sum(s) = &result[0].aggregates[0].1 {
assert!((s - 0.0).abs() < f64::EPSILON);
}
}
#[test]
fn test_delete_rows_nonexistent_table() {
let engine = RelationalEngine::new();
let result = engine.delete_rows("nonexistent_table", Condition::True);
assert!(result.is_err());
if let Err(RelationalError::TableNotFound(name)) = result {
assert_eq!(name, "nonexistent_table");
} else {
panic!("Expected TableNotFound error");
}
}
#[test]
fn test_columnar_float_le_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("empty_flt_le", schema).unwrap();
let results = engine
.select_columnar(
"empty_flt_le",
Condition::Le("val".into(), Value::Float(10.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_float_ge_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("empty_flt_ge", schema).unwrap();
let results = engine
.select_columnar(
"empty_flt_ge",
Condition::Ge("val".into(), Value::Float(0.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_float_ne_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Float)]);
engine.create_table("empty_flt_ne", schema).unwrap();
let results = engine
.select_columnar(
"empty_flt_ne",
Condition::Ne("val".into(), Value::Float(5.0)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_columnar_eq_empty_table() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("empty_eq", schema).unwrap();
let results = engine
.select_columnar(
"empty_eq",
Condition::Eq("val".into(), Value::Int(5)),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_drop_constraint_removes_from_cache() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("drop_cache_test", schema).unwrap();
engine
.add_constraint(
"drop_cache_test",
Constraint::Unique {
name: "uniq_name".to_string(),
columns: vec!["name".to_string()],
},
)
.unwrap();
let constraints = engine.get_constraints("drop_cache_test").unwrap();
assert_eq!(constraints.len(), 1);
engine
.drop_constraint("drop_cache_test", "uniq_name")
.unwrap();
let constraints = engine.get_constraints("drop_cache_test").unwrap();
assert_eq!(constraints.len(), 0);
}
#[test]
fn test_drop_fk_constraint_removes_from_references() {
let engine = RelationalEngine::new();
let parent_schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine
.create_table("parent_fk_drop", parent_schema)
.unwrap();
engine
.add_constraint(
"parent_fk_drop",
Constraint::PrimaryKey {
name: "pk_parent".to_string(),
columns: vec!["id".to_string()],
},
)
.unwrap();
let child_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int),
]);
engine.create_table("child_fk_drop", child_schema).unwrap();
engine
.add_constraint(
"child_fk_drop",
Constraint::ForeignKey(ForeignKeyConstraint {
name: "fk_parent".to_string(),
columns: vec!["parent_id".to_string()],
referenced_table: "parent_fk_drop".to_string(),
referenced_columns: vec!["id".to_string()],
on_delete: ReferentialAction::NoAction,
on_update: ReferentialAction::NoAction,
}),
)
.unwrap();
engine
.drop_constraint("child_fk_drop", "fk_parent")
.unwrap();
let constraints = engine.get_constraints("child_fk_drop").unwrap();
assert_eq!(constraints.len(), 0);
}
#[test]
fn test_columnar_and_multiple_conditions() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
Column::new("amt", ColumnType::Int),
]);
engine.create_table("and_multi", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("val".to_string(), Value::Int(10));
values.insert("amt".to_string(), Value::Int(100));
engine.insert("and_multi", values).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(2));
values.insert("val".to_string(), Value::Int(20));
values.insert("amt".to_string(), Value::Int(200));
engine.insert("and_multi", values).unwrap();
let results = engine
.select_columnar(
"and_multi",
Condition::And(
Box::new(Condition::Eq("val".into(), Value::Int(10))),
Box::new(Condition::Gt("amt".into(), Value::Int(50))),
),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_columnar_or_multiple_conditions() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("or_multi", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("val".to_string(), Value::Int(10));
engine.insert("or_multi", values).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(2));
values.insert("val".to_string(), Value::Int(20));
engine.insert("or_multi", values).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(3));
values.insert("val".to_string(), Value::Int(30));
engine.insert("or_multi", values).unwrap();
let results = engine
.select_columnar(
"or_multi",
Condition::Or(
Box::new(Condition::Eq("val".into(), Value::Int(10))),
Box::new(Condition::Eq("val".into(), Value::Int(30))),
),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_condition_depth_limit_exceeded() {
let config = RelationalConfig::default().with_max_condition_depth(5);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("depth_test", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("val".to_string(), Value::Int(100));
engine.insert("depth_test", values).unwrap();
let mut condition = Condition::Eq("val".to_string(), Value::Int(100));
for _ in 0..10 {
condition = Condition::And(
Box::new(condition),
Box::new(Condition::Eq("id".to_string(), Value::Int(1))),
);
}
let row = Row {
id: 1,
values: vec![
("id".to_string(), Value::Int(1)),
("val".to_string(), Value::Int(100)),
],
};
let result = condition.evaluate_with_depth(&row, 0, 5);
assert!(result.is_err());
if let Err(RelationalError::ConditionTooDeep { max_depth }) = result {
assert_eq!(max_depth, 5);
} else {
panic!("Expected ConditionTooDeep error");
}
}
#[test]
fn test_condition_depth_within_limit() {
let config = RelationalConfig::default().with_max_condition_depth(64);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("depth_ok", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("val".to_string(), Value::Int(100));
engine.insert("depth_ok", values).unwrap();
let condition = Condition::And(
Box::new(Condition::Eq("val".to_string(), Value::Int(100))),
Box::new(Condition::Eq("id".to_string(), Value::Int(1))),
);
let row = Row {
id: 1,
values: vec![
("id".to_string(), Value::Int(1)),
("val".to_string(), Value::Int(100)),
],
};
let result = condition.evaluate_with_depth(&row, 0, 64);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_default_query_timeout_is_30_seconds() {
let config = RelationalConfig::default();
assert_eq!(config.default_query_timeout_ms, Some(30_000));
}
#[test]
fn test_condition_too_deep_error_display() {
let err = RelationalError::ConditionTooDeep { max_depth: 64 };
let msg = format!("{err}");
assert!(msg.contains("64"));
assert!(msg.contains("depth"));
}
#[test]
fn test_max_condition_depth_config_builder() {
let config = RelationalConfig::default().with_max_condition_depth(100);
assert_eq!(config.max_condition_depth, 100);
}
#[test]
fn test_config_presets_have_max_condition_depth() {
let high = RelationalConfig::high_throughput();
assert_eq!(high.max_condition_depth, 64);
let low = RelationalConfig::low_memory();
assert_eq!(low.max_condition_depth, 64);
}
#[test]
fn test_condition_true_evaluate() {
let row = Row {
id: 1,
values: vec![("name".to_string(), Value::String("test".to_string()))],
};
let condition = Condition::True;
assert!(condition.evaluate(&row));
}
#[test]
fn test_condition_gt_evaluate() {
let row = Row {
id: 1,
values: vec![("age".to_string(), Value::Int(30))],
};
let condition = Condition::Gt("age".to_string(), Value::Int(20));
assert!(condition.evaluate(&row));
let condition_false = Condition::Gt("age".to_string(), Value::Int(40));
assert!(!condition_false.evaluate(&row));
}
#[test]
fn test_condition_and_evaluate() {
let row = Row {
id: 1,
values: vec![
("age".to_string(), Value::Int(30)),
("name".to_string(), Value::String("Alice".to_string())),
],
};
let condition = Condition::And(
Box::new(Condition::Eq("age".to_string(), Value::Int(30))),
Box::new(Condition::Eq(
"name".to_string(),
Value::String("Alice".to_string()),
)),
);
assert!(condition.evaluate(&row));
let condition_false = Condition::And(
Box::new(Condition::Eq("age".to_string(), Value::Int(99))),
Box::new(Condition::Eq(
"name".to_string(),
Value::String("Alice".to_string()),
)),
);
assert!(!condition_false.evaluate(&row));
}
#[test]
fn test_condition_or_evaluate() {
let row = Row {
id: 1,
values: vec![("age".to_string(), Value::Int(30))],
};
let condition = Condition::Or(
Box::new(Condition::Eq("age".to_string(), Value::Int(30))),
Box::new(Condition::Eq("age".to_string(), Value::Int(99))),
);
assert!(condition.evaluate(&row));
let condition2 = Condition::Or(
Box::new(Condition::Eq("age".to_string(), Value::Int(99))),
Box::new(Condition::Eq("age".to_string(), Value::Int(30))),
);
assert!(condition2.evaluate(&row));
let condition_false = Condition::Or(
Box::new(Condition::Eq("age".to_string(), Value::Int(99))),
Box::new(Condition::Eq("age".to_string(), Value::Int(100))),
);
assert!(!condition_false.evaluate(&row));
}
#[test]
fn test_condition_evaluate_tensor_id_column() {
let mut tensor = TensorData::new();
tensor.set("_id".to_string(), TensorValue::Scalar(ScalarValue::Int(42)));
tensor.set(
"name".to_string(),
TensorValue::Scalar(ScalarValue::String("alice".into())),
);
let row = Row {
id: 42,
values: vec![
("_id".to_string(), Value::Int(42)),
("name".to_string(), Value::String("alice".into())),
],
};
let cond_eq = Condition::Eq("_id".to_string(), Value::Int(42));
assert_eq!(cond_eq.evaluate(&row), cond_eq.evaluate_tensor(&tensor));
let cond_ne = Condition::Ne("_id".to_string(), Value::Int(99));
assert_eq!(cond_ne.evaluate(&row), cond_ne.evaluate_tensor(&tensor));
let cond_lt = Condition::Lt("_id".to_string(), Value::Int(100));
assert_eq!(cond_lt.evaluate(&row), cond_lt.evaluate_tensor(&tensor));
let cond_name = Condition::Eq("name".to_string(), Value::String("alice".into()));
assert_eq!(cond_name.evaluate(&row), cond_name.evaluate_tensor(&tensor));
}
#[test]
fn test_select_with_condition_true() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("users", schema).unwrap();
for i in 1..=5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String(format!("user{i}")));
engine.insert("users", values).unwrap();
}
let rows = engine.select("users", Condition::True).unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_select_with_condition_gt() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("age", ColumnType::Int),
]);
engine.create_table("people", schema).unwrap();
for i in 1..=10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("age".to_string(), Value::Int(i * 10));
engine.insert("people", values).unwrap();
}
let rows = engine
.select("people", Condition::Gt("age".to_string(), Value::Int(50)))
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn test_select_with_condition_and() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("age", ColumnType::Int),
Column::new("active", ColumnType::Bool),
]);
engine.create_table("users", schema).unwrap();
for i in 1..=10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("age".to_string(), Value::Int(i * 10));
values.insert("active".to_string(), Value::Bool(i % 2 == 0));
engine.insert("users", values).unwrap();
}
let condition = Condition::And(
Box::new(Condition::Gt("age".to_string(), Value::Int(30))),
Box::new(Condition::Eq("active".to_string(), Value::Bool(true))),
);
let rows = engine.select("users", condition).unwrap();
assert_eq!(rows.len(), 4);
}
#[test]
fn test_select_with_condition_or() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("status", ColumnType::String),
]);
engine.create_table("orders", schema).unwrap();
let statuses = ["pending", "shipped", "delivered", "cancelled", "returned"];
for (i, status) in statuses.iter().enumerate() {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i as i64 + 1));
values.insert("status".to_string(), Value::String(status.to_string()));
engine.insert("orders", values).unwrap();
}
let condition = Condition::Or(
Box::new(Condition::Eq(
"status".to_string(),
Value::String("shipped".to_string()),
)),
Box::new(Condition::Eq(
"status".to_string(),
Value::String("delivered".to_string()),
)),
);
let rows = engine.select("orders", condition).unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn test_value_from_malformed_json() {
use tensor_store::relational_slab::ColumnValue as SlabColumnValue;
let malformed_json = "not valid json {{{";
let slab_value = SlabColumnValue::Json(malformed_json.to_string());
let value = Value::from(slab_value);
if let Value::Json(json_val) = value {
assert!(json_val.is_string());
} else {
panic!("Expected Value::Json");
}
}
#[test]
fn test_schema_corrupted_error() {
let err = RelationalError::SchemaCorrupted {
table: "users".to_string(),
reason: "invalid column type".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("users"));
assert!(msg.contains("invalid column type"));
}
#[test]
fn test_table_not_found_error() {
let err = RelationalError::TableNotFound("missing_table".to_string());
let msg = format!("{err}");
assert!(msg.contains("missing_table"));
}
#[test]
fn test_drop_table_with_foreign_key_references() {
use crate::{Constraint, ForeignKeyConstraint, ReferentialAction};
let engine = RelationalEngine::new();
let parent_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("parent", parent_schema).unwrap();
let child_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int),
]);
engine.create_table("child", child_schema).unwrap();
let fk = ForeignKeyConstraint::new(
"fk_parent",
vec!["parent_id".to_string()],
"parent",
vec!["id".to_string()],
)
.on_delete(ReferentialAction::Cascade);
engine
.add_constraint("child", Constraint::ForeignKey(fk))
.unwrap();
let mut parent_values = HashMap::new();
parent_values.insert("id".to_string(), Value::Int(1));
parent_values.insert("name".to_string(), Value::String("Parent1".to_string()));
engine.insert("parent", parent_values).unwrap();
let mut child_values = HashMap::new();
child_values.insert("id".to_string(), Value::Int(1));
child_values.insert("parent_id".to_string(), Value::Int(1));
engine.insert("child", child_values).unwrap();
engine.drop_table("parent").unwrap();
assert!(engine.list_tables().contains(&"child".to_string()));
}
#[test]
fn test_drop_table_cleans_up_indexes() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("indexed_table", schema).unwrap();
engine.create_index("indexed_table", "name").unwrap();
engine.create_btree_index("indexed_table", "age").unwrap();
for i in 1..=5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("age".to_string(), Value::Int(20 + i));
engine.insert("indexed_table", values).unwrap();
}
engine.drop_table("indexed_table").unwrap();
assert!(!engine.list_tables().contains(&"indexed_table".to_string()));
}
#[test]
fn test_drop_table_with_row_data() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::String),
]);
engine.create_table("data_table", schema).unwrap();
for i in 1..=100 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("data".to_string(), Value::String(format!("Data entry {i}")));
engine.insert("data_table", values).unwrap();
}
let rows = engine.select("data_table", Condition::True).unwrap();
assert_eq!(rows.len(), 100);
engine.drop_table("data_table").unwrap();
assert!(!engine.list_tables().contains(&"data_table".to_string()));
}
#[test]
fn test_select_with_aggregates_empty_result() {
use crate::AggregateExpr;
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("category", ColumnType::String),
Column::new("amount", ColumnType::Float),
]);
engine.create_table("empty_agg", schema).unwrap();
let results = engine
.select_grouped(
"empty_agg",
Condition::True,
&["category".to_string()],
&[
AggregateExpr::CountAll,
AggregateExpr::Sum("amount".to_string()),
AggregateExpr::Avg("amount".to_string()),
],
None,
)
.unwrap();
assert!(results.is_empty());
}
#[test]
fn test_select_with_min_max_null_values() {
use crate::{AggregateExpr, AggregateValue};
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("group", ColumnType::String),
Column::new("value", ColumnType::Int).nullable(),
]);
engine.create_table("nullable_agg", schema).unwrap();
let data = [
(1, "A", Some(10)),
(2, "A", None),
(3, "B", None),
(4, "B", None),
];
for (id, group, value) in data {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(id));
values.insert("group".to_string(), Value::String(group.to_string()));
values.insert("value".to_string(), value.map_or(Value::Null, Value::Int));
engine.insert("nullable_agg", values).unwrap();
}
let results = engine
.select_grouped(
"nullable_agg",
Condition::True,
&["group".to_string()],
&[
AggregateExpr::Min("value".to_string()),
AggregateExpr::Max("value".to_string()),
],
None,
)
.unwrap();
assert_eq!(results.len(), 2);
for result in &results {
let group = result.get_key("group").unwrap();
if *group == Value::String("A".to_string()) {
let min = result.get_aggregate("min_value").unwrap();
let max = result.get_aggregate("max_value").unwrap();
assert_eq!(min, &AggregateValue::Min(Some(Value::Int(10))));
assert_eq!(max, &AggregateValue::Max(Some(Value::Int(10))));
} else {
let min = result.get_aggregate("min_value").unwrap();
let max = result.get_aggregate("max_value").unwrap();
assert_eq!(min, &AggregateValue::Min(None));
assert_eq!(max, &AggregateValue::Max(None));
}
}
}
#[test]
fn test_aggregate_sum_with_mixed_types() {
use crate::AggregateExpr;
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("category", ColumnType::String),
Column::new("amount", ColumnType::Int),
]);
engine.create_table("sum_test", schema).unwrap();
for i in 1..=5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("category".to_string(), Value::String("A".to_string()));
values.insert("amount".to_string(), Value::Int(i * 10));
engine.insert("sum_test", values).unwrap();
}
let results = engine
.select_grouped(
"sum_test",
Condition::True,
&["category".to_string()],
&[AggregateExpr::Sum("amount".to_string())],
None,
)
.unwrap();
assert_eq!(results.len(), 1);
let sum = results[0].get_aggregate("sum_amount").unwrap();
if let crate::AggregateValue::Sum(val) = sum {
assert!((val - 150.0).abs() < 0.01);
} else {
panic!("Expected Sum aggregate");
}
}
#[test]
fn test_count_column_excludes_nulls() {
use crate::{AggregateExpr, AggregateValue};
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("group", ColumnType::String),
Column::new("optional", ColumnType::String).nullable(),
]);
engine.create_table("count_null", schema).unwrap();
let data = [
(1, "A", Some("value1")),
(2, "A", None),
(3, "A", Some("value3")),
(4, "A", None),
];
for (id, group, optional) in data {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(id));
values.insert("group".to_string(), Value::String(group.to_string()));
values.insert(
"optional".to_string(),
optional.map_or(Value::Null, |s| Value::String(s.to_string())),
);
engine.insert("count_null", values).unwrap();
}
let results = engine
.select_grouped(
"count_null",
Condition::True,
&["group".to_string()],
&[
AggregateExpr::CountAll,
AggregateExpr::Count("optional".to_string()),
],
None,
)
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(
results[0].get_aggregate("count_all"),
Some(&AggregateValue::Count(4))
);
assert_eq!(
results[0].get_aggregate("count_optional"),
Some(&AggregateValue::Count(2))
);
}
#[test]
fn test_commit_already_committed_transaction() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("tx_test", schema).unwrap();
let tx_id = engine.begin_transaction();
engine.commit(tx_id).unwrap();
let result = engine.commit(tx_id);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_rollback_already_rolled_back_transaction() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("tx_test2", schema).unwrap();
let tx_id = engine.begin_transaction();
engine.rollback(tx_id).unwrap();
let result = engine.rollback(tx_id);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_commit_after_rollback() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("tx_test3", schema).unwrap();
let tx_id = engine.begin_transaction();
engine.rollback(tx_id).unwrap();
let result = engine.commit(tx_id);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_transaction_insert_after_commit() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("tx_test4", schema).unwrap();
let tx_id = engine.begin_transaction();
engine.commit(tx_id).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("name".to_string(), Value::String("test".to_string()));
let result = engine.tx_insert(tx_id, "tx_test4", values);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_transaction_select_after_rollback() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("tx_test5", schema).unwrap();
let tx_id = engine.begin_transaction();
engine.rollback(tx_id).unwrap();
let result = engine.tx_select(tx_id, "tx_test5", Condition::True);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_transaction_delete_after_commit() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("tx_test6", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
engine.insert("tx_test6", values).unwrap();
let tx_id = engine.begin_transaction();
engine.commit(tx_id).unwrap();
let result = engine.tx_delete(
tx_id,
"tx_test6",
Condition::Eq("id".to_string(), Value::Int(1)),
);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_transaction_update_after_rollback() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("val", ColumnType::Int),
]);
engine.create_table("tx_test7", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("val".to_string(), Value::Int(10));
engine.insert("tx_test7", values).unwrap();
let tx_id = engine.begin_transaction();
engine.rollback(tx_id).unwrap();
let mut updates = HashMap::new();
updates.insert("val".to_string(), Value::Int(20));
let result = engine.tx_update(
tx_id,
"tx_test7",
Condition::Eq("id".to_string(), Value::Int(1)),
updates,
);
assert!(matches!(
result,
Err(RelationalError::TransactionNotFound(_))
));
}
#[test]
fn test_get_schema_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.get_schema("nonexistent_table");
assert!(matches!(result, Err(RelationalError::TableNotFound(_))));
}
#[test]
fn test_select_with_zero_timeout() {
use crate::QueryOptions;
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("data", ColumnType::String),
]);
engine.create_table("timeout_test", schema).unwrap();
for i in 1..=100 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("data".to_string(), Value::String(format!("Data {i}")));
engine.insert("timeout_test", values).unwrap();
}
let options = QueryOptions::default().with_timeout_ms(0);
let result = engine.select_with_options("timeout_test", Condition::True, options);
assert!(result.is_ok() || matches!(result, Err(RelationalError::QueryTimeout { .. })));
}
#[test]
fn test_relational_config_builder_methods() {
let config = RelationalConfig::default()
.with_max_tables(10)
.with_max_indexes_per_table(5)
.with_default_timeout_ms(1000)
.with_slow_query_threshold_ms(500);
assert_eq!(config.max_tables, Some(10));
assert_eq!(config.max_indexes_per_table, Some(5));
assert_eq!(config.default_query_timeout_ms, Some(1000));
assert_eq!(config.slow_query_threshold_ms, 500);
}
#[test]
fn test_engine_with_config_creates_instance() {
let config = RelationalConfig::default()
.with_max_tables(5)
.with_default_timeout_ms(5000);
let engine = RelationalEngine::with_config(config);
assert!(engine.list_tables().is_empty());
}
#[test]
fn test_engine_with_store_creates_instance() {
use tensor_store::TensorStore;
let store = TensorStore::new();
let engine = RelationalEngine::with_store(store);
assert!(engine.list_tables().is_empty());
}
#[test]
fn test_engine_with_store_and_config() {
use tensor_store::TensorStore;
let store = TensorStore::new();
let config = RelationalConfig::default().with_max_tables(10);
let engine = RelationalEngine::with_store_and_config(store, config);
assert!(engine.list_tables().is_empty());
}
#[test]
fn test_sum_with_non_numeric_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("amount", ColumnType::Int),
]);
engine.create_table("mixed_sum", schema).unwrap();
for i in 1..=5 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String(format!("Item{i}")));
values.insert("amount".to_string(), Value::Int(i * 10));
engine.insert("mixed_sum", values).unwrap();
}
let sum = engine.sum("mixed_sum", "name", Condition::True).unwrap();
assert!((sum - 0.0).abs() < f64::EPSILON);
let sum_amount = engine.sum("mixed_sum", "amount", Condition::True).unwrap();
assert!((sum_amount - 150.0).abs() < f64::EPSILON); }
#[test]
fn test_avg_with_non_numeric_values() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("label", ColumnType::String),
Column::new("value", ColumnType::Float),
]);
engine.create_table("mixed_avg", schema).unwrap();
for i in 1..=4 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("label".to_string(), Value::String(format!("Label{i}")));
values.insert("value".to_string(), Value::Float(i as f64 * 10.0));
engine.insert("mixed_avg", values).unwrap();
}
let avg = engine.avg("mixed_avg", "label", Condition::True).unwrap();
assert!(avg.is_none());
let avg_value = engine.avg("mixed_avg", "value", Condition::True).unwrap();
assert!(avg_value.is_some());
assert!((avg_value.unwrap() - 25.0).abs() < f64::EPSILON); }
#[test]
fn test_sum_parallel_threshold() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("category", ColumnType::String),
Column::new("amount", ColumnType::Int),
]);
engine.create_table("parallel_sum", schema).unwrap();
for i in 1..=1100 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert(
"category".to_string(),
Value::String(format!("Cat{}", i % 10)),
);
values.insert("amount".to_string(), Value::Int(1));
engine.insert("parallel_sum", values).unwrap();
}
let sum = engine
.sum("parallel_sum", "category", Condition::True)
.unwrap();
assert!((sum - 0.0).abs() < f64::EPSILON);
let sum_amount = engine
.sum("parallel_sum", "amount", Condition::True)
.unwrap();
assert!((sum_amount - 1100.0).abs() < f64::EPSILON);
}
#[test]
fn test_avg_parallel_threshold() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
Column::new("score", ColumnType::Int),
]);
engine.create_table("parallel_avg", schema).unwrap();
for i in 1..=1100 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String(format!("User{i}")));
values.insert("score".to_string(), Value::Int(100)); engine.insert("parallel_avg", values).unwrap();
}
let avg = engine.avg("parallel_avg", "name", Condition::True).unwrap();
assert!(avg.is_none());
let avg_score = engine
.avg("parallel_avg", "score", Condition::True)
.unwrap();
assert!(avg_score.is_some());
assert!((avg_score.unwrap() - 100.0).abs() < f64::EPSILON);
}
#[test]
fn test_select_grouped_with_having_gt() {
use crate::{AggregateExpr, AggregateRef, HavingCondition};
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("dept", ColumnType::String),
Column::new("salary", ColumnType::Int),
]);
engine.create_table("employees", schema).unwrap();
let data = [
(1, "eng", 100),
(2, "eng", 200),
(3, "eng", 150),
(4, "sales", 80),
(5, "sales", 90),
(6, "hr", 50),
];
for (id, dept, salary) in data {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(id));
values.insert("dept".to_string(), Value::String(dept.to_string()));
values.insert("salary".to_string(), Value::Int(salary));
engine.insert("employees", values).unwrap();
}
let having = HavingCondition::Gt(AggregateRef::CountAll, Value::Int(2));
let results = engine
.select_grouped(
"employees",
Condition::True,
&["dept".to_string()],
&[AggregateExpr::CountAll],
Some(having),
)
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(
results[0].get_key("dept"),
Some(&Value::String("eng".to_string()))
);
}
#[test]
fn test_select_grouped_with_having_ge() {
use crate::{AggregateExpr, AggregateRef, HavingCondition};
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("category", ColumnType::String),
]);
engine.create_table("items", schema).unwrap();
for i in 1..=10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert(
"category".to_string(),
Value::String(if i <= 5 { "A" } else { "B" }.to_string()),
);
engine.insert("items", values).unwrap();
}
let having = HavingCondition::Ge(AggregateRef::CountAll, Value::Int(5));
let results = engine
.select_grouped(
"items",
Condition::True,
&["category".to_string()],
&[AggregateExpr::CountAll],
Some(having),
)
.unwrap();
assert_eq!(results.len(), 2); }
#[test]
fn test_select_grouped_with_having_lt() {
use crate::{AggregateExpr, AggregateRef, HavingCondition};
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("status", ColumnType::String),
]);
engine.create_table("orders", schema).unwrap();
let statuses = [
"pending", "pending", "shipped", "shipped", "shipped", "done",
];
for (i, status) in statuses.iter().enumerate() {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i as i64 + 1));
values.insert("status".to_string(), Value::String(status.to_string()));
engine.insert("orders", values).unwrap();
}
let having = HavingCondition::Lt(AggregateRef::CountAll, Value::Int(3));
let results = engine
.select_grouped(
"orders",
Condition::True,
&["status".to_string()],
&[AggregateExpr::CountAll],
Some(having),
)
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_select_grouped_with_having_le() {
use crate::{AggregateExpr, AggregateRef, HavingCondition};
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("type", ColumnType::String),
]);
engine.create_table("events", schema).unwrap();
for i in 1..=7 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert(
"type".to_string(),
Value::String(
if i <= 2 {
"A"
} else if i <= 5 {
"B"
} else {
"C"
}
.to_string(),
),
);
engine.insert("events", values).unwrap();
}
let having = HavingCondition::Le(AggregateRef::CountAll, Value::Int(2));
let results = engine
.select_grouped(
"events",
Condition::True,
&["type".to_string()],
&[AggregateExpr::CountAll],
Some(having),
)
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_select_grouped_with_having_eq() {
use crate::{AggregateExpr, AggregateRef, HavingCondition};
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("region", ColumnType::String),
]);
engine.create_table("sales", schema).unwrap();
let regions = ["north", "north", "south", "south", "east"];
for (i, region) in regions.iter().enumerate() {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i as i64 + 1));
values.insert("region".to_string(), Value::String(region.to_string()));
engine.insert("sales", values).unwrap();
}
let having = HavingCondition::Eq(AggregateRef::CountAll, Value::Int(2));
let results = engine
.select_grouped(
"sales",
Condition::True,
&["region".to_string()],
&[AggregateExpr::CountAll],
Some(having),
)
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_select_grouped_with_having_ne() {
use crate::{AggregateExpr, AggregateRef, HavingCondition};
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("tier", ColumnType::String),
]);
engine.create_table("customers", schema).unwrap();
let tiers = ["gold", "gold", "silver", "silver", "silver", "bronze"];
for (i, tier) in tiers.iter().enumerate() {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i as i64 + 1));
values.insert("tier".to_string(), Value::String(tier.to_string()));
engine.insert("customers", values).unwrap();
}
let having = HavingCondition::Ne(AggregateRef::CountAll, Value::Int(2));
let results = engine
.select_grouped(
"customers",
Condition::True,
&["tier".to_string()],
&[AggregateExpr::CountAll],
Some(having),
)
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_select_grouped_with_having_and() {
use crate::{AggregateExpr, AggregateRef, HavingCondition};
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("group", ColumnType::String),
]);
engine.create_table("data", schema).unwrap();
for (group, count) in [("A", 1), ("B", 2), ("C", 3), ("D", 4)] {
for i in 0..count {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i64::from(i)));
values.insert("group".to_string(), Value::String(group.to_string()));
engine.insert("data", values).unwrap();
}
}
let having = HavingCondition::And(
Box::new(HavingCondition::Ge(AggregateRef::CountAll, Value::Int(2))),
Box::new(HavingCondition::Le(AggregateRef::CountAll, Value::Int(3))),
);
let results = engine
.select_grouped(
"data",
Condition::True,
&["group".to_string()],
&[AggregateExpr::CountAll],
Some(having),
)
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_select_grouped_with_having_or() {
use crate::{AggregateExpr, AggregateRef, HavingCondition};
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("category", ColumnType::String),
]);
engine.create_table("products", schema).unwrap();
for (cat, count) in [("X", 1), ("Y", 2), ("Z", 5)] {
for i in 0..count {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i64::from(i)));
values.insert("category".to_string(), Value::String(cat.to_string()));
engine.insert("products", values).unwrap();
}
}
let having = HavingCondition::Or(
Box::new(HavingCondition::Eq(AggregateRef::CountAll, Value::Int(1))),
Box::new(HavingCondition::Ge(AggregateRef::CountAll, Value::Int(5))),
);
let results = engine
.select_grouped(
"products",
Condition::True,
&["category".to_string()],
&[AggregateExpr::CountAll],
Some(having),
)
.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_select_grouped_having_depth_exceeded() {
use crate::{AggregateExpr, AggregateRef, HavingCondition, RelationalConfig};
let config = RelationalConfig::default().with_max_condition_depth(3);
let engine = RelationalEngine::with_config(config);
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("group", ColumnType::String),
]);
engine.create_table("test", schema).unwrap();
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(1));
values.insert("group".to_string(), Value::String("A".to_string()));
engine.insert("test", values).unwrap();
let mut having = HavingCondition::Gt(AggregateRef::CountAll, Value::Int(0));
for _ in 0..10 {
having = HavingCondition::And(
Box::new(having),
Box::new(HavingCondition::Lt(AggregateRef::CountAll, Value::Int(100))),
);
}
let result = engine.select_grouped(
"test",
Condition::True,
&["group".to_string()],
&[AggregateExpr::CountAll],
Some(having),
);
assert!(matches!(
result,
Err(RelationalError::ConditionTooDeep { .. })
));
}
#[test]
fn test_select_streaming() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("users", schema).unwrap();
for i in 0..10 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("name".to_string(), Value::String(format!("user{i}")));
engine.insert("users", values).unwrap();
}
let cursor = engine.select_streaming("users", Condition::True);
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 10);
for row_result in &rows {
let row = row_result.as_ref().unwrap();
assert!(row.get("id").is_some());
assert!(row.get("name").is_some());
}
}
#[test]
fn test_select_streaming_builder() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int),
]);
engine.create_table("items", schema).unwrap();
for i in 0..100 {
let mut values = HashMap::new();
values.insert("id".to_string(), Value::Int(i));
values.insert("value".to_string(), Value::Int(i * 10));
engine.insert("items", values).unwrap();
}
let cursor = engine
.select_streaming_builder("items", Condition::True)
.batch_size(25)
.build();
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 100);
let cursor = engine
.select_streaming_builder("items", Condition::True)
.max_rows(50)
.build();
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 50);
let cursor = engine
.select_streaming_builder("items", Condition::Lt("id".to_string(), Value::Int(30)))
.build();
let rows: Vec<_> = cursor.collect();
assert_eq!(rows.len(), 30);
}
#[test]
fn test_drop_fk_constraint() {
let engine = RelationalEngine::new();
let parent_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("name", ColumnType::String),
]);
engine.create_table("parents", parent_schema).unwrap();
let child_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("parent_id", ColumnType::Int),
]);
engine.create_table("children", child_schema).unwrap();
let fk = ForeignKeyConstraint::new(
"fk_parent",
vec!["parent_id".to_string()],
"parents".to_string(),
vec!["id".to_string()],
);
engine
.add_constraint("children", Constraint::ForeignKey(fk))
.unwrap();
let constraints = engine.get_constraints("children").unwrap();
assert_eq!(constraints.len(), 1);
engine.drop_constraint("children", "fk_parent").unwrap();
let constraints = engine.get_constraints("children").unwrap();
assert!(constraints.is_empty());
}
#[test]
fn test_drop_constraint_updates_fk_references() {
let engine = RelationalEngine::new();
let ref_schema = Schema::new(vec![Column::new("id", ColumnType::Int)]);
engine.create_table("ref_table", ref_schema).unwrap();
let child1_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("ref_id", ColumnType::Int),
]);
engine.create_table("child1", child1_schema).unwrap();
let child2_schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("ref_id", ColumnType::Int),
]);
engine.create_table("child2", child2_schema).unwrap();
let fk1 = ForeignKeyConstraint::new(
"fk_ref1",
vec!["ref_id".to_string()],
"ref_table".to_string(),
vec!["id".to_string()],
);
engine
.add_constraint("child1", Constraint::ForeignKey(fk1))
.unwrap();
let fk2 = ForeignKeyConstraint::new(
"fk_ref2",
vec!["ref_id".to_string()],
"ref_table".to_string(),
vec!["id".to_string()],
);
engine
.add_constraint("child2", Constraint::ForeignKey(fk2))
.unwrap();
engine.drop_constraint("child1", "fk_ref1").unwrap();
let c1 = engine.get_constraints("child1").unwrap();
let c2 = engine.get_constraints("child2").unwrap();
assert!(c1.is_empty());
assert_eq!(c2.len(), 1);
}
#[test]
fn test_count_index_accelerated_path() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("age", ColumnType::Int),
]);
engine.create_table("cnt_idx", schema).unwrap();
engine.create_index("cnt_idx", "name").unwrap();
for i in 0..20 {
engine
.insert(
"cnt_idx",
HashMap::from([
("name".to_string(), Value::String(format!("user_{i}"))),
("age".to_string(), Value::Int(i)),
]),
)
.unwrap();
}
let count = engine
.count(
"cnt_idx",
Condition::Eq("name".to_string(), Value::String("user_5".to_string())),
)
.unwrap();
assert_eq!(count, 1);
let count = engine
.count(
"cnt_idx",
Condition::Eq("name".to_string(), Value::String("nonexistent".to_string())),
)
.unwrap();
assert_eq!(count, 0);
}
#[test]
fn test_count_column_filtered_full_scan() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("id", ColumnType::Int),
Column::new("value", ColumnType::Int).nullable(),
]);
engine.create_table("cnt_col_filt", schema).unwrap();
for i in 0..10 {
let val = if i % 3 == 0 {
Value::Null
} else {
Value::Int(i)
};
engine
.insert(
"cnt_col_filt",
HashMap::from([
("id".to_string(), Value::Int(i)),
("value".to_string(), val),
]),
)
.unwrap();
}
let count = engine
.count_column(
"cnt_col_filt",
"value",
Condition::Gt("id".to_string(), Value::Int(3)),
)
.unwrap();
assert_eq!(count, 4);
}
#[test]
fn test_count_column_index_accelerated_path() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![
Column::new("name", ColumnType::String),
Column::new("score", ColumnType::Int).nullable(),
]);
engine.create_table("cnt_col_idx", schema).unwrap();
engine.create_index("cnt_col_idx", "name").unwrap();
for i in 0..5 {
let score = if i % 2 == 0 {
Value::Int(i * 10)
} else {
Value::Null
};
engine
.insert(
"cnt_col_idx",
HashMap::from([
("name".to_string(), Value::String("alice".to_string())),
("score".to_string(), score),
]),
)
.unwrap();
}
engine
.insert(
"cnt_col_idx",
HashMap::from([
("name".to_string(), Value::String("bob".to_string())),
("score".to_string(), Value::Int(99)),
]),
)
.unwrap();
let count = engine
.count_column(
"cnt_col_idx",
"score",
Condition::Eq("name".to_string(), Value::String("alice".to_string())),
)
.unwrap();
assert_eq!(count, 3);
}
#[test]
fn test_count_column_nonexistent_column_error() {
let engine = RelationalEngine::new();
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table("cnt_err", schema).unwrap();
engine
.insert(
"cnt_err",
HashMap::from([("val".to_string(), Value::Int(1))]),
)
.unwrap();
let result = engine.count_column("cnt_err", "missing", Condition::True);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, RelationalError::ColumnNotFound(ref c) if c == "missing"),
"expected ColumnNotFound, got: {err:?}"
);
}
fn setup_for_each_row_table(engine: &RelationalEngine, table: &str, n: usize) {
let schema = Schema::new(vec![Column::new("val", ColumnType::Int)]);
engine.create_table(table, schema).unwrap();
for i in 0..n {
engine
.insert(
table,
HashMap::from([("val".to_string(), Value::Int(i as i64))]),
)
.unwrap();
}
}
#[test]
fn test_for_each_row_basic() {
let engine = RelationalEngine::new();
setup_for_each_row_table(&engine, "t", 5);
let mut count = 0;
engine
.for_each_row("t", None, None, &mut |_| count += 1)
.unwrap();
assert_eq!(count, 5);
}
#[test]
fn test_for_each_row_empty_table() {
let engine = RelationalEngine::new();
setup_for_each_row_table(&engine, "t", 0);
let mut count = 0;
engine
.for_each_row("t", None, None, &mut |_| count += 1)
.unwrap();
assert_eq!(count, 0);
}
#[test]
fn test_for_each_row_table_not_found() {
let engine = RelationalEngine::new();
let result = engine.for_each_row("nonexistent", None, None, &mut |_| {});
assert!(
matches!(result, Err(RelationalError::TableNotFound(ref t)) if t == "nonexistent"),
"expected TableNotFound, got: {result:?}"
);
}
#[test]
fn test_for_each_row_max_rows_caps_iteration() {
let engine = RelationalEngine::new();
setup_for_each_row_table(&engine, "t", 100);
let mut count = 0;
engine
.for_each_row("t", None, Some(10), &mut |_| count += 1)
.unwrap();
assert_eq!(count, 10);
}
#[test]
fn test_for_each_row_max_rows_larger_than_table() {
let engine = RelationalEngine::new();
setup_for_each_row_table(&engine, "t", 5);
let mut count = 0;
engine
.for_each_row("t", None, Some(100), &mut |_| count += 1)
.unwrap();
assert_eq!(count, 5);
}
#[test]
fn test_for_each_row_max_rows_zero() {
let engine = RelationalEngine::new();
setup_for_each_row_table(&engine, "t", 10);
let mut count = 0;
engine
.for_each_row("t", None, Some(0), &mut |_| count += 1)
.unwrap();
assert_eq!(count, 0);
}
#[test]
fn test_for_each_row_zero_timeout_returns_timeout_error() {
let engine = RelationalEngine::new();
setup_for_each_row_table(&engine, "t", 10);
let result = engine.for_each_row("t", Some(0), None, &mut |_| {});
assert!(
matches!(result, Err(RelationalError::QueryTimeout { .. })),
"expected QueryTimeout, got: {result:?}"
);
}
#[test]
fn test_for_each_row_zero_timeout_table_not_found_takes_precedence() {
let engine = RelationalEngine::new();
let result = engine.for_each_row("nonexistent", Some(0), None, &mut |_| {});
assert!(
matches!(result, Err(RelationalError::TableNotFound(ref t)) if t == "nonexistent"),
"expected TableNotFound over QueryTimeout, got: {result:?}"
);
}
#[test]
fn test_for_each_row_no_timeout_override_uses_default() {
let config = RelationalConfig::default().with_default_timeout_ms(0);
let store = TensorStore::new();
let engine = RelationalEngine::with_store_and_config(store, config);
setup_for_each_row_table(&engine, "t", 10);
let result = engine.for_each_row("t", None, None, &mut |_| {});
assert!(
matches!(result, Err(RelationalError::QueryTimeout { .. })),
"expected QueryTimeout from engine default, got: {result:?}"
);
}
#[test]
fn test_for_each_row_callback_receives_correct_rows() {
let engine = RelationalEngine::new();
setup_for_each_row_table(&engine, "t", 5);
let mut values = Vec::new();
engine
.for_each_row("t", None, None, &mut |row| {
if let Some(Value::Int(v)) = row.get("val") {
values.push(*v);
}
})
.unwrap();
values.sort_unstable();
assert_eq!(values, vec![0, 1, 2, 3, 4]);
}
#[test]
fn test_for_each_row_max_rows_returns_ok() {
let engine = RelationalEngine::new();
setup_for_each_row_table(&engine, "t", 100);
let result = engine.for_each_row("t", None, Some(5), &mut |_| {});
assert!(result.is_ok());
}