use std::collections::HashSet;
use stoolap::Database;
fn setup_products_table(db: &Database) {
db.execute(
"CREATE TABLE products (id INTEGER PRIMARY KEY, category TEXT, region TEXT, price FLOAT)",
(),
)
.expect("Failed to create products table");
let inserts = [
"INSERT INTO products (id, category, region, price) VALUES (1, 'Electronics', 'North', 100.0)",
"INSERT INTO products (id, category, region, price) VALUES (2, 'Electronics', 'South', 200.0)",
"INSERT INTO products (id, category, region, price) VALUES (3, 'Electronics', 'North', 150.0)",
"INSERT INTO products (id, category, region, price) VALUES (4, 'Clothing', 'East', 50.0)",
"INSERT INTO products (id, category, region, price) VALUES (5, 'Clothing', 'West', 75.0)",
"INSERT INTO products (id, category, region, price) VALUES (6, 'Clothing', 'East', 60.0)",
"INSERT INTO products (id, category, region, price) VALUES (7, 'Books', 'North', 20.0)",
"INSERT INTO products (id, category, region, price) VALUES (8, 'Books', 'South', 25.0)",
"INSERT INTO products (id, category, region, price) VALUES (9, 'Books', 'North', 20.0)",
"INSERT INTO products (id, category, region, price) VALUES (10, 'Electronics', 'West', 180.0)",
];
for insert in &inserts {
db.execute(insert, ()).expect("Failed to insert data");
}
}
#[test]
fn test_single_column_distinct() {
let db = Database::open("memory://distinct_single").expect("Failed to create database");
setup_products_table(&db);
let result = db
.query("SELECT DISTINCT category FROM products", ())
.expect("Failed to execute SELECT DISTINCT");
let mut categories: Vec<String> = Vec::new();
for row in result {
let row = row.expect("Failed to get row");
let category: String = row.get(0).unwrap();
categories.push(category);
}
let expected: HashSet<String> = ["Electronics", "Clothing", "Books"]
.iter()
.map(|s| s.to_string())
.collect();
let actual: HashSet<String> = categories.iter().cloned().collect();
assert_eq!(
actual.len(),
3,
"Expected 3 distinct categories, got {}",
actual.len()
);
assert_eq!(actual, expected, "Category sets don't match");
let distinct_count: i64 = db
.query_one("SELECT COUNT(DISTINCT category) FROM products", ())
.expect("Failed to execute COUNT DISTINCT");
assert_eq!(
distinct_count, 3,
"COUNT DISTINCT should return 3, got {}",
distinct_count
);
}
#[test]
fn test_multi_column_distinct() {
let db = Database::open("memory://distinct_multi").expect("Failed to create database");
setup_products_table(&db);
let result = db
.query("SELECT DISTINCT category, region FROM products", ())
.expect("Failed to execute multi-column SELECT DISTINCT");
let mut combinations: Vec<(String, String)> = Vec::new();
for row in result {
let row = row.expect("Failed to get row");
let category: String = row.get(0).unwrap();
let region: String = row.get(1).unwrap();
combinations.push((category, region));
}
let expected: HashSet<(String, String)> = [
("Electronics", "North"),
("Electronics", "South"),
("Electronics", "West"),
("Clothing", "East"),
("Clothing", "West"),
("Books", "North"),
("Books", "South"),
]
.iter()
.map(|(c, r)| (c.to_string(), r.to_string()))
.collect();
let actual: HashSet<(String, String)> = combinations.iter().cloned().collect();
assert_eq!(
actual.len(),
7,
"Expected 7 distinct category-region combinations, got {}",
actual.len()
);
assert_eq!(actual, expected, "Combination sets don't match");
}
#[test]
fn test_distinct_with_order_by() {
let db = Database::open("memory://distinct_order").expect("Failed to create database");
setup_products_table(&db);
let result = db
.query("SELECT DISTINCT region FROM products ORDER BY region", ())
.expect("Failed to execute SELECT DISTINCT with ORDER BY");
let mut regions: Vec<String> = Vec::new();
for row in result {
let row = row.expect("Failed to get row");
let region: String = row.get(0).unwrap();
regions.push(region);
}
let expected = ["East", "North", "South", "West"];
assert_eq!(
regions.len(),
expected.len(),
"Expected {} distinct regions, got {}",
expected.len(),
regions.len()
);
for (i, region) in regions.iter().enumerate() {
assert_eq!(
region, expected[i],
"Expected region {} at position {}, got {}",
expected[i], i, region
);
}
}
#[test]
fn test_distinct_on_numeric() {
let db = Database::open("memory://distinct_numeric").expect("Failed to create database");
setup_products_table(&db);
let result = db
.query("SELECT DISTINCT price FROM products ORDER BY price", ())
.expect("Failed to execute SELECT DISTINCT on numeric column");
let mut prices: Vec<f64> = Vec::new();
for row in result {
let row = row.expect("Failed to get row");
let price: f64 = row.get(0).unwrap();
prices.push(price);
}
let expected_prices = vec![20.0, 25.0, 50.0, 60.0, 75.0, 100.0, 150.0, 180.0, 200.0];
assert_eq!(
prices.len(),
expected_prices.len(),
"Expected {} distinct prices, got {}",
expected_prices.len(),
prices.len()
);
let distinct_count: i64 = db
.query_one("SELECT COUNT(DISTINCT price) FROM products", ())
.expect("Failed to execute COUNT DISTINCT");
assert_eq!(
distinct_count,
expected_prices.len() as i64,
"COUNT DISTINCT mismatch"
);
}
#[test]
fn test_complex_distinct() {
let db = Database::open("memory://distinct_complex").expect("Failed to create database");
db.execute(
"CREATE TABLE candle_data (id INTEGER PRIMARY KEY, symbol TEXT, open FLOAT, high FLOAT, low FLOAT, close FLOAT, volume FLOAT)", ())
.expect("Failed to create candle_data table");
let symbols = [
"BTC-USD", "ETH-USD", "SOL-USD", "BTC-USD", "ETH-USD", "BTC-USD",
];
for (i, symbol) in symbols.iter().enumerate() {
let sql = format!(
"INSERT INTO candle_data (id, symbol, open, high, low, close, volume) VALUES ({}, '{}', 100.0, 105.0, 95.0, 102.0, 1000.0)",
i + 1,
symbol
);
db.execute(&sql, ()).expect("Failed to insert candle data");
}
let result = db
.query(
"SELECT DISTINCT symbol FROM candle_data ORDER BY symbol",
(),
)
.expect("Failed to execute SELECT DISTINCT on symbols");
let mut distinct_symbols: Vec<String> = Vec::new();
for row in result {
let row = row.expect("Failed to get row");
let symbol: String = row.get(0).unwrap();
distinct_symbols.push(symbol);
}
let expected = ["BTC-USD", "ETH-USD", "SOL-USD"];
assert_eq!(
distinct_symbols.len(),
expected.len(),
"Expected {} distinct symbols, got {}",
expected.len(),
distinct_symbols.len()
);
for (i, symbol) in distinct_symbols.iter().enumerate() {
assert_eq!(
symbol, expected[i],
"Expected symbol {} at position {}, got {}",
expected[i], i, symbol
);
}
let symbol_count: i64 = db
.query_one("SELECT COUNT(DISTINCT symbol) FROM candle_data", ())
.expect("Failed to execute COUNT DISTINCT on symbols");
assert_eq!(symbol_count, 3, "COUNT DISTINCT should return 3");
}
#[test]
fn test_distinct_with_no_duplicates() {
let db = Database::open("memory://distinct_unique").expect("Failed to create database");
db.execute("CREATE TABLE unique_data (id INTEGER PRIMARY KEY)", ())
.expect("Failed to create unique_data table");
for i in 1..=10 {
let sql = format!("INSERT INTO unique_data (id) VALUES ({})", i);
db.execute(&sql, ()).expect("Failed to insert data");
}
let result = db
.query("SELECT DISTINCT id FROM unique_data ORDER BY id", ())
.expect("Failed to execute SELECT DISTINCT");
let mut ids: Vec<i64> = Vec::new();
for row in result {
let row = row.expect("Failed to get row");
let id: i64 = row.get(0).unwrap();
ids.push(id);
}
assert_eq!(ids.len(), 10, "Expected 10 distinct ids, got {}", ids.len());
for (i, id) in ids.iter().enumerate() {
assert_eq!(
*id,
(i + 1) as i64,
"Expected id {} at position {}, got {}",
i + 1,
i,
id
);
}
}
#[test]
fn test_distinct_with_large_dataset() {
let db = Database::open("memory://distinct_large").expect("Failed to create database");
db.execute(
"CREATE TABLE large_data (id INTEGER PRIMARY KEY, category TEXT)",
(),
)
.expect("Failed to create large_data table");
let categories = ["A", "B", "C", "D", "E"];
for i in 1..=1000 {
let category = categories[(i - 1) % categories.len()];
let sql = format!(
"INSERT INTO large_data (id, category) VALUES ({}, '{}')",
i, category
);
db.execute(&sql, ()).expect("Failed to insert data");
}
let result = db
.query(
"SELECT DISTINCT category FROM large_data ORDER BY category",
(),
)
.expect("Failed to execute SELECT DISTINCT on large dataset");
let mut distinct_categories: Vec<String> = Vec::new();
for row in result {
let row = row.expect("Failed to get row");
let category: String = row.get(0).unwrap();
distinct_categories.push(category);
}
assert_eq!(
distinct_categories.len(),
5,
"Expected 5 distinct categories, got {}",
distinct_categories.len()
);
let expected = ["A", "B", "C", "D", "E"];
for (i, category) in distinct_categories.iter().enumerate() {
assert_eq!(
category, expected[i],
"Expected category {} at position {}, got {}",
expected[i], i, category
);
}
let category_count: i64 = db
.query_one("SELECT COUNT(DISTINCT category) FROM large_data", ())
.expect("Failed to execute COUNT DISTINCT");
assert_eq!(category_count, 5, "COUNT DISTINCT should return 5");
}
#[test]
fn test_distinct_with_where() {
let db = Database::open("memory://distinct_where").expect("Failed to create database");
setup_products_table(&db);
let result = db
.query(
"SELECT DISTINCT category FROM products WHERE price > 50.0 ORDER BY category",
(),
)
.expect("Failed to execute SELECT DISTINCT with WHERE");
let mut categories: Vec<String> = Vec::new();
for row in result {
let row = row.expect("Failed to get row");
let category: String = row.get(0).unwrap();
categories.push(category);
}
let expected: HashSet<String> = ["Clothing", "Electronics"]
.iter()
.map(|s| s.to_string())
.collect();
let actual: HashSet<String> = categories.iter().cloned().collect();
assert_eq!(
actual.len(),
2,
"Expected 2 distinct categories with price > 50, got {}",
actual.len()
);
assert_eq!(actual, expected, "Category sets don't match");
}
#[test]
fn test_distinct_with_nulls() {
let db = Database::open("memory://distinct_nulls").expect("Failed to create database");
db.execute(
"CREATE TABLE nullable_data (id INTEGER PRIMARY KEY, value TEXT)",
(),
)
.expect("Failed to create table");
db.execute("INSERT INTO nullable_data (id, value) VALUES (1, 'A')", ())
.unwrap();
db.execute("INSERT INTO nullable_data (id, value) VALUES (2, 'B')", ())
.unwrap();
db.execute("INSERT INTO nullable_data (id, value) VALUES (3, NULL)", ())
.unwrap();
db.execute("INSERT INTO nullable_data (id, value) VALUES (4, 'A')", ())
.unwrap();
db.execute("INSERT INTO nullable_data (id, value) VALUES (5, NULL)", ())
.unwrap();
let result = db
.query("SELECT DISTINCT value FROM nullable_data", ())
.expect("Failed to execute SELECT DISTINCT with NULLs");
let mut count = 0;
let mut has_null = false;
let mut values: HashSet<String> = HashSet::new();
for row in result {
let row = row.expect("Failed to get row");
count += 1;
let value: Option<String> = row.get(0).unwrap();
match value {
Some(v) => {
values.insert(v);
}
None => {
has_null = true;
}
}
}
assert_eq!(count, 3, "Expected 3 distinct values (including NULL)");
assert!(values.contains("A"), "Should contain 'A'");
assert!(values.contains("B"), "Should contain 'B'");
assert!(has_null, "Should contain NULL");
}