use stoolap::api::Database;
#[test]
fn test_explain_analyze_shows_estimates_and_actuals() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE feedback_test (id INTEGER PRIMARY KEY, value INTEGER, category TEXT)",
(),
)
.expect("Failed to create table");
for i in 0..100 {
db.execute(
&format!(
"INSERT INTO feedback_test VALUES ({}, {}, 'cat_{}')",
i,
i * 10,
i % 5
),
(),
)
.expect("Insert failed");
}
db.execute("ANALYZE feedback_test", ()).unwrap();
let result: Vec<_> = db
.query(
"EXPLAIN ANALYZE SELECT * FROM feedback_test WHERE value > 500",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
let mut found_actual_rows = false;
for row in &result {
let plan_line: String = row.get(0).expect("Failed to get plan line");
if plan_line.contains("actual rows=") || plan_line.contains("actual_rows=") {
found_actual_rows = true;
break;
}
}
assert!(found_actual_rows, "EXPLAIN ANALYZE should show actual rows");
}
#[test]
fn test_repeated_queries_use_feedback() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE users (id INTEGER PRIMARY KEY, status TEXT, age INTEGER)",
(),
)
.expect("Failed to create table");
for i in 0..100 {
let status = if i % 10 == 0 { "inactive" } else { "active" };
db.execute(
&format!(
"INSERT INTO users VALUES ({}, '{}', {})",
i,
status,
20 + i % 50
),
(),
)
.expect("Insert failed");
}
db.execute("ANALYZE users", ()).unwrap();
let result1: Vec<_> = db
.query(
"EXPLAIN ANALYZE SELECT * FROM users WHERE status = 'inactive'",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
let result2: Vec<_> = db
.query("EXPLAIN SELECT * FROM users WHERE status = 'inactive'", ())
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
assert!(!result1.is_empty(), "First query should return results");
assert!(!result2.is_empty(), "Second query should return results");
}
#[test]
fn test_join_cardinality_feedback() {
let db = Database::open("memory://join_feedback").expect("Failed to create database");
db.execute(
"CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT)",
(),
)
.expect("Failed to create customers table");
db.execute(
"CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, total INTEGER)",
(),
)
.expect("Failed to create orders table");
for i in 0..50 {
db.execute(
&format!("INSERT INTO customers VALUES ({}, 'customer_{}')", i, i),
(),
)
.expect("Insert failed");
}
let mut order_id = 0;
for i in 0..50 {
for _ in 0..i {
db.execute(
&format!(
"INSERT INTO orders VALUES ({}, {}, {})",
order_id,
i,
(order_id + 1) * 100
),
(),
)
.expect("Insert failed");
order_id += 1;
}
}
db.execute("ANALYZE customers", ()).unwrap();
db.execute("ANALYZE orders", ()).unwrap();
let result: Vec<_> = db
.query(
"EXPLAIN ANALYZE SELECT c.name, o.total
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
WHERE o.total > 5000",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
assert!(
!result.is_empty(),
"Join EXPLAIN ANALYZE should return results"
);
}
#[test]
fn test_selective_predicate_feedback() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE products (id INTEGER PRIMARY KEY, price INTEGER, in_stock INTEGER)",
(),
)
.expect("Failed to create table");
for i in 0..1000 {
let in_stock = if i % 100 == 0 { 0 } else { 1 }; db.execute(
&format!(
"INSERT INTO products VALUES ({}, {}, {})",
i,
(i % 100) * 10,
in_stock
),
(),
)
.expect("Insert failed");
}
db.execute("ANALYZE products", ()).unwrap();
let result: Vec<_> = db
.query(
"EXPLAIN ANALYZE SELECT * FROM products WHERE in_stock = 0",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
let mut found_actual = false;
for row in &result {
let plan_line: String = row.get(0).expect("Failed to get plan line");
if plan_line.contains("actual rows=") || plan_line.contains("actual_rows=") {
found_actual = true;
}
}
assert!(found_actual, "Should show actual row count");
}
#[test]
fn test_compound_predicate_feedback() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE events (
id INTEGER PRIMARY KEY,
event_type TEXT,
severity INTEGER,
timestamp INTEGER
)",
(),
)
.expect("Failed to create table");
for i in 0..500 {
let event_type = match i % 4 {
0 => "error",
1 => "warning",
2 => "info",
_ => "debug",
};
let severity = i % 5;
db.execute(
&format!(
"INSERT INTO events VALUES ({}, '{}', {}, {})",
i,
event_type,
severity,
i * 1000
),
(),
)
.expect("Insert failed");
}
db.execute("ANALYZE events", ()).unwrap();
let result: Vec<_> = db
.query(
"EXPLAIN ANALYZE SELECT * FROM events
WHERE event_type = 'error' AND severity >= 3",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
assert!(
!result.is_empty(),
"Compound predicate query should complete"
);
let actual_result: Vec<_> = db
.query(
"SELECT COUNT(*) FROM events WHERE event_type = 'error' AND severity >= 3",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
let count: i64 = actual_result[0].get(0).expect("Failed to get count");
assert!(count > 0, "Should have matching rows");
}
#[test]
fn test_aggregation_feedback() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE sales (
id INTEGER PRIMARY KEY,
region TEXT,
amount INTEGER,
year INTEGER
)",
(),
)
.expect("Failed to create table");
let regions = ["North", "South", "East", "West"];
for i in 0..200 {
let region = regions[i % 4];
db.execute(
&format!(
"INSERT INTO sales VALUES ({}, '{}', {}, {})",
i,
region,
(i + 1) * 100,
2020 + (i % 5)
),
(),
)
.expect("Insert failed");
}
db.execute("ANALYZE sales", ()).unwrap();
let result: Vec<_> = db
.query(
"EXPLAIN ANALYZE SELECT region, SUM(amount) as total
FROM sales
WHERE year = 2024
GROUP BY region",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
assert!(
!result.is_empty(),
"Aggregation EXPLAIN ANALYZE should complete"
);
}
#[test]
fn test_feedback_with_nulls() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE nullable_test (id INTEGER PRIMARY KEY, value INTEGER)",
(),
)
.expect("Failed to create table");
for i in 0..100 {
if i % 5 == 0 {
db.execute(
&format!("INSERT INTO nullable_test VALUES ({}, NULL)", i),
(),
)
.expect("Insert failed");
} else {
db.execute(
&format!("INSERT INTO nullable_test VALUES ({}, {})", i, i * 10),
(),
)
.expect("Insert failed");
}
}
db.execute("ANALYZE nullable_test", ()).unwrap();
let result: Vec<_> = db
.query(
"EXPLAIN ANALYZE SELECT * FROM nullable_test WHERE value IS NULL",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
assert!(!result.is_empty(), "NULL predicate query should complete");
let count_result: Vec<_> = db
.query("SELECT COUNT(*) FROM nullable_test WHERE value IS NULL", ())
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
let count: i64 = count_result[0].get(0).expect("Failed to get count");
assert_eq!(count, 20, "Should have 20 NULL values");
}
#[test]
fn test_subquery_feedback() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)",
(),
)
.expect("Failed to create departments table");
db.execute(
"CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER, salary INTEGER)",
(),
)
.expect("Failed to create employees table");
db.execute("INSERT INTO departments VALUES (1, 'Engineering')", ())
.expect("Insert failed");
db.execute("INSERT INTO departments VALUES (2, 'Sales')", ())
.expect("Insert failed");
db.execute("INSERT INTO departments VALUES (3, 'Marketing')", ())
.expect("Insert failed");
for i in 0..30 {
let dept_id = (i % 3) + 1;
db.execute(
&format!(
"INSERT INTO employees VALUES ({}, 'emp_{}', {}, {})",
i,
i,
dept_id,
40000 + i * 1000
),
(),
)
.expect("Insert failed");
}
db.execute("ANALYZE departments", ()).unwrap();
db.execute("ANALYZE employees", ()).unwrap();
let result: Vec<_> = db
.query(
"EXPLAIN ANALYZE SELECT * FROM employees
WHERE dept_id IN (SELECT id FROM departments WHERE name = 'Engineering')",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
assert!(
!result.is_empty(),
"Subquery EXPLAIN ANALYZE should complete"
);
}
#[test]
fn test_feedback_empty_table() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE empty_table (id INTEGER PRIMARY KEY, value INTEGER)",
(),
)
.expect("Failed to create table");
db.execute("ANALYZE empty_table", ()).unwrap();
let result: Vec<_> = db
.query(
"EXPLAIN ANALYZE SELECT * FROM empty_table WHERE value > 100",
(),
)
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
assert!(!result.is_empty(), "Should handle empty table");
}
#[test]
fn test_feedback_consistency() {
let db = Database::open("memory://feedback_consistency").expect("Failed to create database");
db.execute(
"CREATE TABLE consistency_test (id INTEGER PRIMARY KEY, value INTEGER)",
(),
)
.expect("Failed to create table");
for i in 0..100 {
db.execute(
&format!("INSERT INTO consistency_test VALUES ({}, {})", i, i % 10),
(),
)
.expect("Insert failed");
}
db.execute("ANALYZE consistency_test", ()).unwrap();
for _ in 0..3 {
let result: Vec<_> = db
.query("SELECT * FROM consistency_test WHERE value = 5", ())
.expect("Query failed")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to collect");
assert_eq!(result.len(), 10, "Should consistently return 10 rows");
}
}