use stoolap::api::Database;
#[test]
fn test_analyze_creates_system_tables() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT, value INTEGER)",
(),
)
.expect("Failed to create table");
let result: Vec<_> = db
.query("ANALYZE test_table", ())
.expect("ANALYZE failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
assert_eq!(result.len(), 1);
let tables_analyzed: i64 = result[0].get(0).expect("Failed to get value");
assert_eq!(tables_analyzed, 1);
let tables: Vec<_> = db
.query("SHOW TABLES", ())
.expect("SHOW TABLES failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let table_names: Vec<String> = tables.iter().map(|r| r.get::<String>(0).unwrap()).collect();
assert!(
table_names.iter().any(|t| t == "_sys_table_stats"),
"System table _sys_table_stats should exist"
);
assert!(
table_names.iter().any(|t| t == "_sys_column_stats"),
"System table _sys_column_stats should exist"
);
}
#[test]
fn test_analyze_collects_table_stats() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price INTEGER)",
(),
)
.expect("Failed to create table");
db.execute("INSERT INTO products VALUES (1, 'Apple', 100)", ())
.expect("Insert failed");
db.execute("INSERT INTO products VALUES (2, 'Banana', 50)", ())
.expect("Insert failed");
db.execute("INSERT INTO products VALUES (3, 'Cherry', 200)", ())
.expect("Insert failed");
let _: Vec<_> = db
.query("ANALYZE products", ())
.expect("ANALYZE failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let stats: Vec<_> = db
.query(
"SELECT table_name, row_count FROM _sys_table_stats WHERE table_name = 'products'",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
assert_eq!(stats.len(), 1, "Should have stats for products table");
let table_name: String = stats[0].get(0).expect("Failed to get table name");
let row_count: i64 = stats[0].get(1).expect("Failed to get row count");
assert_eq!(table_name, "products");
assert_eq!(row_count, 3, "Row count should be 3");
}
#[test]
fn test_analyze_collects_column_stats() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary INTEGER, dept TEXT)",
(),
)
.expect("Failed to create table");
db.execute(
"INSERT INTO employees VALUES (1, 'Alice', 50000, 'Engineering')",
(),
)
.expect("Insert failed");
db.execute(
"INSERT INTO employees VALUES (2, 'Bob', 60000, 'Engineering')",
(),
)
.expect("Insert failed");
db.execute(
"INSERT INTO employees VALUES (3, 'Charlie', 55000, 'Sales')",
(),
)
.expect("Insert failed");
db.execute(
"INSERT INTO employees VALUES (4, 'Diana', 70000, 'Engineering')",
(),
)
.expect("Insert failed");
db.execute(
"INSERT INTO employees VALUES (5, 'Eve', 45000, 'Sales')",
(),
)
.expect("Insert failed");
let _: Vec<_> = db
.query("ANALYZE employees", ())
.expect("ANALYZE failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let stats: Vec<_> = db
.query(
"SELECT column_name, distinct_count, min_value, max_value
FROM _sys_column_stats
WHERE table_name = 'employees' AND column_name = 'salary'",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
assert_eq!(stats.len(), 1, "Should have stats for salary column");
let distinct_count: i64 = stats[0].get(1).expect("Failed to get distinct count");
assert_eq!(distinct_count, 5, "All salary values are distinct");
let dept_stats: Vec<_> = db
.query(
"SELECT distinct_count FROM _sys_column_stats
WHERE table_name = 'employees' AND column_name = 'dept'",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let dept_distinct: i64 = dept_stats[0].get(0).expect("Failed to get distinct count");
assert_eq!(dept_distinct, 2, "Should have 2 distinct departments");
}
#[test]
fn test_analyze_null_handling() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE nullable_test (id INTEGER PRIMARY KEY, value TEXT)",
(),
)
.expect("Failed to create table");
db.execute("INSERT INTO nullable_test VALUES (1, 'A')", ())
.expect("Insert failed");
db.execute("INSERT INTO nullable_test VALUES (2, NULL)", ())
.expect("Insert failed");
db.execute("INSERT INTO nullable_test VALUES (3, 'B')", ())
.expect("Insert failed");
db.execute("INSERT INTO nullable_test VALUES (4, NULL)", ())
.expect("Insert failed");
db.execute("INSERT INTO nullable_test VALUES (5, 'A')", ())
.expect("Insert failed");
let _: Vec<_> = db
.query("ANALYZE nullable_test", ())
.expect("ANALYZE failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let stats: Vec<_> = db
.query(
"SELECT null_count, distinct_count FROM _sys_column_stats
WHERE table_name = 'nullable_test' AND column_name = 'value'",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let null_count: i64 = stats[0].get(0).expect("Failed to get null count");
let distinct_count: i64 = stats[0].get(1).expect("Failed to get distinct count");
assert_eq!(null_count, 2, "Should have 2 NULL values");
assert_eq!(
distinct_count, 2,
"Should have 2 distinct non-NULL values (A, B)"
);
}
#[test]
fn test_analyze_all_tables() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE table1 (id INTEGER PRIMARY KEY, val INTEGER)",
(),
)
.expect("Failed to create table1");
db.execute(
"CREATE TABLE table2 (id INTEGER PRIMARY KEY, name TEXT)",
(),
)
.expect("Failed to create table2");
db.execute(
"CREATE TABLE table3 (id INTEGER PRIMARY KEY, score FLOAT)",
(),
)
.expect("Failed to create table3");
db.execute("INSERT INTO table1 VALUES (1, 100)", ())
.expect("Insert failed");
db.execute("INSERT INTO table2 VALUES (1, 'test')", ())
.expect("Insert failed");
db.execute("INSERT INTO table3 VALUES (1, 3.14)", ())
.expect("Insert failed");
let result: Vec<_> = db
.query("ANALYZE", ())
.expect("ANALYZE failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let analyzed_count: i64 = result[0].get(0).expect("Failed to get count");
assert_eq!(analyzed_count, 3, "Should analyze 3 tables");
let stats: Vec<_> = db
.query(
"SELECT COUNT(*) FROM _sys_table_stats WHERE table_name IN ('table1', 'table2', 'table3')",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let stats_count: i64 = stats[0].get(0).expect("Failed to get count");
assert_eq!(stats_count, 3, "Should have stats for all 3 tables");
}
#[test]
fn test_analyze_updates_stats() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE update_test (id INTEGER PRIMARY KEY, val INTEGER)",
(),
)
.expect("Failed to create table");
db.execute("INSERT INTO update_test VALUES (1, 10)", ())
.expect("Insert failed");
let _: Vec<_> = db
.query("ANALYZE update_test", ())
.expect("ANALYZE failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let first_stats: Vec<_> = db
.query(
"SELECT row_count FROM _sys_table_stats WHERE table_name = 'update_test'",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let first_count: i64 = first_stats[0].get(0).expect("Failed to get count");
assert_eq!(first_count, 1);
db.execute("INSERT INTO update_test VALUES (2, 20)", ())
.expect("Insert failed");
db.execute("INSERT INTO update_test VALUES (3, 30)", ())
.expect("Insert failed");
let _: Vec<_> = db
.query("ANALYZE update_test", ())
.expect("ANALYZE failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let second_stats: Vec<_> = db
.query(
"SELECT row_count FROM _sys_table_stats WHERE table_name = 'update_test'",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let second_count: i64 = second_stats[0].get(0).expect("Failed to get count");
assert_eq!(second_count, 3, "Stats should be updated to 3 rows");
}
#[test]
fn test_analyze_empty_table() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE empty_table (id INTEGER PRIMARY KEY, name TEXT)",
(),
)
.expect("Failed to create table");
let _: Vec<_> = db
.query("ANALYZE empty_table", ())
.expect("ANALYZE failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let stats: Vec<_> = db
.query(
"SELECT row_count, avg_row_size FROM _sys_table_stats WHERE table_name = 'empty_table'",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
assert_eq!(stats.len(), 1, "Should have stats for empty table");
let row_count: i64 = stats[0].get(0).expect("Failed to get row count");
assert_eq!(row_count, 0, "Row count should be 0");
}
#[test]
fn test_analyze_nonexistent_table() {
let db = Database::open_in_memory().expect("Failed to create database");
let result: Vec<_> = db
.query("ANALYZE nonexistent_table", ())
.expect("ANALYZE should not error")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let analyzed_count: i64 = result[0].get(0).expect("Failed to get count");
assert_eq!(analyzed_count, 0, "Should return 0 for non-existent table");
}
#[test]
fn test_analyze_excludes_system_tables() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute("CREATE TABLE user_table (id INTEGER PRIMARY KEY)", ())
.expect("Failed to create table");
let _: Vec<_> = db
.query("ANALYZE", ())
.expect("First ANALYZE failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let result: Vec<_> = db
.query("ANALYZE", ())
.expect("Second ANALYZE failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect results");
let analyzed_count: i64 = result[0].get(0).expect("Failed to get count");
assert_eq!(
analyzed_count, 1,
"Should only analyze user table, not system tables"
);
}
#[test]
fn test_selectivity_estimator() {
use stoolap::storage::SelectivityEstimator;
let eq_sel = SelectivityEstimator::equality(100); assert!(
(eq_sel - 0.01).abs() < 0.0001,
"Equality selectivity should be 1/100"
);
let range_sel = SelectivityEstimator::range();
assert!(
(range_sel - 0.33).abs() < 0.01,
"Range selectivity should be ~0.33"
);
let prefix_sel = SelectivityEstimator::like("test%", 100);
assert!(
prefix_sel < 0.5,
"Prefix LIKE should have reasonable selectivity"
);
let contains_sel = SelectivityEstimator::like("%test%", 100);
assert!(
contains_sel > prefix_sel,
"Contains LIKE should be less selective than prefix"
);
let in_sel = SelectivityEstimator::in_list(5, 100); assert!(
(in_sel - 0.05).abs() < 0.0001,
"IN list selectivity should be 5/100"
);
let null_sel = SelectivityEstimator::is_null(20, 100); assert!(
(null_sel - 0.2).abs() < 0.0001,
"IS NULL selectivity should be 20/100"
);
let not_null_sel = SelectivityEstimator::is_not_null(20, 100);
assert!(
(not_null_sel - 0.8).abs() < 0.0001,
"IS NOT NULL selectivity should be 80/100"
);
}
#[test]
fn test_join_cardinality() {
use stoolap::storage::SelectivityEstimator;
let cardinality = SelectivityEstimator::join_cardinality(1000, 500, 100, 50);
assert_eq!(cardinality, 5000, "Join cardinality should be 5000");
}