use stoolap::Database;
fn setup_numbers_db(name: &str) -> Database {
let db = Database::open(&format!("memory://{}", name)).expect("Failed to create database");
db.execute(
"CREATE TABLE numbers (id INTEGER PRIMARY KEY, value FLOAT, category TEXT)",
(),
)
.unwrap();
db.execute("INSERT INTO numbers VALUES (1, 2, 'A')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (2, 4, 'A')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (3, 4, 'A')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (4, 4, 'A')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (5, 5, 'A')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (6, 5, 'A')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (7, 7, 'A')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (8, 9, 'A')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (9, 1, 'B')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (10, 2, 'B')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (11, 3, 'B')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (12, 4, 'B')", ())
.unwrap();
db.execute("INSERT INTO numbers VALUES (13, 5, 'B')", ())
.unwrap();
db
}
fn setup_names_db(name: &str) -> Database {
let db = Database::open(&format!("memory://{}", name)).expect("Failed to create database");
db.execute(
"CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT, dept TEXT)",
(),
)
.unwrap();
db.execute("INSERT INTO people VALUES (1, 'Alice', 'Engineering')", ())
.unwrap();
db.execute("INSERT INTO people VALUES (2, 'Bob', 'Engineering')", ())
.unwrap();
db.execute("INSERT INTO people VALUES (3, 'Charlie', 'Sales')", ())
.unwrap();
db.execute("INSERT INTO people VALUES (4, 'Diana', 'Engineering')", ())
.unwrap();
db.execute("INSERT INTO people VALUES (5, 'Eve', 'Sales')", ())
.unwrap();
db
}
#[test]
fn test_string_agg_basic() {
let db = setup_names_db("string_agg_basic");
let result: String = db
.query_one("SELECT STRING_AGG(name) FROM people", ())
.unwrap();
assert!(result.contains("Alice"));
assert!(result.contains("Bob"));
assert!(result.contains("Charlie"));
assert!(result.contains(","));
}
#[test]
fn test_string_agg_with_group_by() {
let db = setup_names_db("string_agg_group");
let result = db
.query(
"SELECT dept, STRING_AGG(name) FROM people GROUP BY dept ORDER BY dept",
(),
)
.expect("Query failed");
let rows: Vec<_> = result.map(|r| r.unwrap()).collect();
assert_eq!(rows.len(), 2);
let dept: String = rows[0].get(0).unwrap();
let names: String = rows[0].get(1).unwrap();
assert_eq!(dept, "Engineering");
assert!(names.contains("Alice"));
assert!(names.contains("Bob"));
assert!(names.contains("Diana"));
let dept2: String = rows[1].get(0).unwrap();
let names2: String = rows[1].get(1).unwrap();
assert_eq!(dept2, "Sales");
assert!(names2.contains("Charlie"));
assert!(names2.contains("Eve"));
}
#[test]
fn test_group_concat() {
let db = setup_names_db("group_concat");
let result: String = db
.query_one(
"SELECT GROUP_CONCAT(name) FROM people WHERE dept = 'Sales'",
(),
)
.unwrap();
assert!(result.contains("Charlie"));
assert!(result.contains("Eve"));
}
#[test]
fn test_string_agg_with_numbers() {
let db = setup_numbers_db("string_agg_nums");
let result: String = db
.query_one(
"SELECT STRING_AGG(id) FROM numbers WHERE category = 'B'",
(),
)
.unwrap();
assert!(result.contains("9"));
assert!(result.contains("10"));
assert!(result.contains(","));
}
#[test]
fn test_stddev_pop() {
let db = setup_numbers_db("stddev_pop");
let stddev: f64 = db
.query_one(
"SELECT STDDEV_POP(value) FROM numbers WHERE category = 'A'",
(),
)
.unwrap();
assert!(
(stddev - 2.0).abs() < 0.0001,
"Expected 2.0, got {}",
stddev
);
}
#[test]
fn test_stddev_alias() {
let db = setup_numbers_db("stddev_alias");
let stddev: f64 = db
.query_one("SELECT STDDEV(value) FROM numbers WHERE category = 'A'", ())
.unwrap();
assert!((stddev - 2.0).abs() < 0.0001);
}
#[test]
fn test_stddev_samp() {
let db = setup_numbers_db("stddev_samp");
let stddev: f64 = db
.query_one(
"SELECT STDDEV_SAMP(value) FROM numbers WHERE category = 'A'",
(),
)
.unwrap();
assert!(
(stddev - 2.138).abs() < 0.01,
"Expected ~2.138, got {}",
stddev
);
}
#[test]
fn test_stddev_with_group_by() {
let db = setup_numbers_db("stddev_group");
let result = db
.query(
"SELECT category, STDDEV_POP(value) FROM numbers GROUP BY category ORDER BY category",
(),
)
.expect("Query failed");
let rows: Vec<_> = result.map(|r| r.unwrap()).collect();
assert_eq!(rows.len(), 2);
let cat_a: String = rows[0].get(0).unwrap();
let stddev_a: f64 = rows[0].get(1).unwrap();
assert_eq!(cat_a, "A");
assert!((stddev_a - 2.0).abs() < 0.0001);
let cat_b: String = rows[1].get(0).unwrap();
let stddev_b: f64 = rows[1].get(1).unwrap();
assert_eq!(cat_b, "B");
assert!((stddev_b - 1.414).abs() < 0.01);
}
#[test]
fn test_var_pop() {
let db = setup_numbers_db("var_pop");
let var: f64 = db
.query_one(
"SELECT VAR_POP(value) FROM numbers WHERE category = 'A'",
(),
)
.unwrap();
assert!((var - 4.0).abs() < 0.0001, "Expected 4.0, got {}", var);
}
#[test]
fn test_variance_alias() {
let db = setup_numbers_db("variance_alias");
let var: f64 = db
.query_one(
"SELECT VARIANCE(value) FROM numbers WHERE category = 'A'",
(),
)
.unwrap();
assert!((var - 4.0).abs() < 0.0001);
}
#[test]
fn test_var_samp() {
let db = setup_numbers_db("var_samp");
let var: f64 = db
.query_one(
"SELECT VAR_SAMP(value) FROM numbers WHERE category = 'A'",
(),
)
.unwrap();
assert!((var - 4.571).abs() < 0.01, "Expected ~4.571, got {}", var);
}
#[test]
fn test_median_odd_count() {
let db = setup_numbers_db("median_odd");
let median: f64 = db
.query_one("SELECT MEDIAN(value) FROM numbers WHERE category = 'B'", ())
.unwrap();
assert!(
(median - 3.0).abs() < 0.0001,
"Expected 3.0, got {}",
median
);
}
#[test]
fn test_median_even_count() {
let db = setup_numbers_db("median_even");
let median: f64 = db
.query_one("SELECT MEDIAN(value) FROM numbers WHERE category = 'A'", ())
.unwrap();
assert!(
(median - 4.5).abs() < 0.0001,
"Expected 4.5, got {}",
median
);
}
#[test]
fn test_median_with_group_by() {
let db = setup_numbers_db("median_group");
let result = db
.query(
"SELECT category, MEDIAN(value) FROM numbers GROUP BY category ORDER BY category",
(),
)
.expect("Query failed");
let rows: Vec<_> = result.map(|r| r.unwrap()).collect();
assert_eq!(rows.len(), 2);
let median_a: f64 = rows[0].get(1).unwrap();
assert!((median_a - 4.5).abs() < 0.0001);
let median_b: f64 = rows[1].get(1).unwrap();
assert!((median_b - 3.0).abs() < 0.0001);
}
#[test]
fn test_multiple_aggregates_in_query() {
let db = setup_numbers_db("multi_agg");
let result = db
.query(
"SELECT AVG(value), STDDEV_POP(value), VARIANCE(value), MEDIAN(value)
FROM numbers WHERE category = 'A'",
(),
)
.expect("Query failed");
let rows: Vec<_> = result.map(|r| r.unwrap()).collect();
assert_eq!(rows.len(), 1);
let avg: f64 = rows[0].get(0).unwrap();
let stddev: f64 = rows[0].get(1).unwrap();
let var: f64 = rows[0].get(2).unwrap();
let median: f64 = rows[0].get(3).unwrap();
assert!((avg - 5.0).abs() < 0.0001);
assert!((stddev - 2.0).abs() < 0.0001);
assert!((var - 4.0).abs() < 0.0001);
assert!((median - 4.5).abs() < 0.0001);
}
#[test]
fn test_aggregates_with_null() {
let db = Database::open("memory://agg_null").expect("Failed to create database");
db.execute(
"CREATE TABLE with_nulls (id INTEGER PRIMARY KEY, value FLOAT)",
(),
)
.unwrap();
db.execute("INSERT INTO with_nulls VALUES (1, 1)", ())
.unwrap();
db.execute("INSERT INTO with_nulls VALUES (2, NULL)", ())
.unwrap();
db.execute("INSERT INTO with_nulls VALUES (3, 3)", ())
.unwrap();
db.execute("INSERT INTO with_nulls VALUES (4, NULL)", ())
.unwrap();
db.execute("INSERT INTO with_nulls VALUES (5, 5)", ())
.unwrap();
let result = db
.query("SELECT AVG(value), MEDIAN(value) FROM with_nulls", ())
.expect("Query failed");
let rows: Vec<_> = result.map(|r| r.unwrap()).collect();
let avg: f64 = rows[0].get(0).unwrap();
let median: f64 = rows[0].get(1).unwrap();
assert!((avg - 3.0).abs() < 0.0001);
assert!((median - 3.0).abs() < 0.0001);
}
#[test]
fn test_string_agg_custom_separator() {
let db = setup_names_db("string_agg_custom_sep");
let result: String = db
.query_one("SELECT STRING_AGG(name, ' | ') FROM people", ())
.unwrap();
assert!(result.contains("Alice"));
assert!(result.contains("Bob"));
assert!(result.contains(" | "));
assert!(!result.contains(",")); }
#[test]
fn test_string_agg_dash_separator() {
let db = setup_names_db("string_agg_dash_sep");
let result: String = db
.query_one(
"SELECT STRING_AGG(name, '-') FROM people WHERE dept = 'Sales'",
(),
)
.unwrap();
assert!(result.contains("Charlie"));
assert!(result.contains("Eve"));
assert!(result.contains("-"));
assert!(!result.contains(","));
}
#[test]
fn test_string_agg_custom_separator_with_group_by() {
let db = setup_names_db("string_agg_group_custom");
let result = db
.query(
"SELECT dept, STRING_AGG(name, '; ') FROM people GROUP BY dept ORDER BY dept",
(),
)
.expect("Query failed");
let rows: Vec<_> = result.map(|r| r.unwrap()).collect();
assert_eq!(rows.len(), 2);
let names: String = rows[0].get(1).unwrap();
assert!(names.contains("Alice"));
assert!(names.contains("Bob"));
assert!(names.contains("Diana"));
assert!(names.contains("; "));
assert!(!names.contains(","));
let names2: String = rows[1].get(1).unwrap();
assert!(names2.contains("Charlie"));
assert!(names2.contains("Eve"));
assert!(names2.contains("; "));
}
#[test]
fn test_group_concat_custom_separator() {
let db = setup_names_db("group_concat_custom");
let result: String = db
.query_one(
"SELECT GROUP_CONCAT(name, ' and ') FROM people WHERE dept = 'Sales'",
(),
)
.unwrap();
assert!(result.contains("Charlie"));
assert!(result.contains("Eve"));
assert!(result.contains(" and "));
}
#[test]
fn test_string_agg_newline_separator() {
let db = setup_names_db("string_agg_newline");
let result: String = db
.query_one(
"SELECT STRING_AGG(name, '\n') FROM people WHERE dept = 'Engineering'",
(),
)
.unwrap();
assert!(result.contains("Alice"));
assert!(result.contains("Bob"));
assert!(result.contains("Diana"));
assert!(result.contains("\n"));
}
#[test]
fn test_string_agg_empty_separator() {
let db = setup_names_db("string_agg_empty");
let result: String = db
.query_one(
"SELECT STRING_AGG(name, '') FROM people WHERE dept = 'Sales'",
(),
)
.unwrap();
assert!(result.contains("Charlie"));
assert!(result.contains("Eve"));
assert!(!result.contains(","));
assert!(!result.contains(" "));
}