use rudb::Database;
use rudb_common::Value;
fn database() -> Database {
let db = Database::new();
db.execute(
"CREATE TABLE t AS SELECT
i AS k,
CASE WHEN i % 7 = 0 THEN NULL ELSE i % 100 END AS v,
CASE WHEN i % 11 = 0 THEN NULL ELSE 'v' || (i % 50) END AS s
FROM range(5000) r(i)",
)
.expect("the table is created");
db
}
fn one(db: &Database, query: &str) -> Value {
let result = db.query(query).expect("the query ran");
let rows: Vec<Vec<Value>> = result.rows().collect();
assert_eq!(rows.len(), 1, "{query} answered {} rows", rows.len());
rows[0].first().expect("one column").clone()
}
fn filtered(db: &Database, query: &str) -> bool {
let result = db.query(query).expect("the query ran");
let metrics = result.metrics().expect("the query was measured");
metrics.operators.iter().any(|operator| operator.kind == "Filter")
}
fn agree(db: &Database, predicate: &str, blocked: &str) {
let pushed = format!("SELECT count(*), sum(k) FROM t WHERE {predicate}");
let above = format!("SELECT count(*), sum(k) FROM t WHERE {blocked}");
let result = db.query(&pushed).expect("the pushed query ran");
let wanted = db.query(&above).expect("the blocked query ran");
let (result, wanted): (Vec<_>, Vec<_>) = (result.rows().collect(), wanted.rows().collect());
assert_eq!(result, wanted, "{predicate} and {blocked} differ");
assert!(!filtered(db, &pushed), "{predicate} built a filter operator");
assert!(filtered(db, &above), "{blocked} was pushed down after all");
}
#[test]
fn a_filter_of_one_comparison_is_applied_by_the_scan_and_no_operator_is_built_for_it() {
let db = database();
agree(&db, "k >= 2500", "k + 0 >= 2500");
agree(&db, "k < 2500", "k + 0 < 2500");
agree(&db, "k = 2500", "k + 0 = 2500");
agree(&db, "k > 4999", "k + 0 > 4999");
agree(&db, "k <= 0", "k + 0 <= 0");
}
#[test]
fn a_filter_most_of_a_clustered_column_passes_answers_the_same_as_one_that_compares_every_row() {
let db = database();
agree(&db, "k >= 10", "k + 0 >= 10");
assert_eq!(one(&db, "SELECT count(*) FROM t WHERE k >= 10"), Value::BigInt(4990));
}
#[test]
fn every_conjunct_of_an_and_goes_into_the_scan_together() {
let db = database();
agree(&db, "k >= 10 AND k < 4000", "k + 0 >= 10 AND k < 4000");
agree(&db, "k >= 10 AND v < 50", "k + 0 >= 10 AND v < 50");
agree(&db, "k >= 10 AND v < 50 AND s >= 'v'", "k + 0 >= 10 AND v < 50 AND s >= 'v'");
agree(&db, "k BETWEEN 100 AND 3000", "k + 0 BETWEEN 100 AND 3000");
}
#[test]
fn a_column_with_nulls_in_it_is_never_waved_through_on_its_bounds_alone() {
let db = database();
agree(&db, "v >= 0", "v + 0 >= 0");
agree(&db, "v < 100", "v + 0 < 100");
let nulls = 5000_i64.div_euclid(7) + 1;
assert_eq!(one(&db, "SELECT count(*) FROM t WHERE v >= 0"), Value::BigInt(5000 - nulls));
assert_eq!(one(&db, "SELECT count(*) FROM t WHERE v IS NULL"), Value::BigInt(nulls));
}
#[test]
fn a_string_column_is_pushed_down_the_same_way_a_number_is() {
let db = database();
agree(&db, "s >= 'v'", "s || '' >= 'v'");
agree(&db, "s = 'v3'", "s || '' = 'v3'");
}
#[test]
fn a_predicate_the_scan_cannot_express_keeps_its_filter_operator() {
let db = database();
for predicate in ["k < 10 OR k > 4990", "k = v", "k % 3 = 0", "s IS NULL"] {
let query = format!("SELECT count(*) FROM t WHERE {predicate}");
assert!(filtered(&db, &query), "{predicate} was pushed into the scan");
}
}
#[test]
fn one_conjunct_the_scan_cannot_take_leaves_the_whole_filter_where_it_was() {
let db = database();
let query = "SELECT count(*), sum(k) FROM t WHERE k >= 10 AND k % 3 = 0";
assert!(filtered(&db, query), "the readable conjunct took the rest of the predicate with it");
assert_eq!(one(&db, "SELECT count(*) FROM t WHERE k >= 10 AND k % 3 = 0"), Value::BigInt(1663));
}
#[test]
fn a_filter_no_chunk_can_match_answers_nothing_without_reading_a_row() {
let db = database();
agree(&db, "k >= 100000", "k + 0 >= 100000");
assert_eq!(one(&db, "SELECT count(*) FROM t WHERE k >= 100000"), Value::BigInt(0));
}
#[test]
fn the_rows_a_pushed_filter_keeps_are_the_rows_the_filter_names() {
let db = database();
let result = db.query("SELECT k FROM t WHERE k >= 4997 ORDER BY k").expect("the query ran");
let kept: Vec<Value> = result.rows().map(|row| row[0].clone()).collect();
assert_eq!(kept, vec![Value::BigInt(4997), Value::BigInt(4998), Value::BigInt(4999)]);
}
#[test]
fn a_stored_table_answers_a_pushed_filter_the_way_a_table_in_memory_does() {
let path = std::env::temp_dir().join(format!("rudb-pushdown-{}.rudb", std::process::id()));
let _ = std::fs::remove_file(&path);
let name = path.to_str().expect("a UTF-8 temporary path");
let create = "CREATE TABLE t AS SELECT i AS k, CASE WHEN i % 7 = 0 THEN NULL ELSE i % 100 END \
AS v FROM range(5000) r(i)";
{
let writing = Database::open(name).expect("a file name starts a native database");
writing.execute(create).expect("the file table is created");
writing.execute("CHECKPOINT").expect("the file table is committed");
}
let file = Database::open(name).expect("the written file opens again");
let memory = Database::new();
memory.execute(create).expect("the memory table is created");
for predicate in ["k >= 10", "k >= 2500 AND k < 4000", "v >= 0", "v < 100", "k = 4999"] {
let query = format!("SELECT count(*), sum(k) FROM t WHERE {predicate}");
let stored = file.query(&query).expect("the file query ran");
let held = memory.query(&query).expect("the memory query ran");
let stored: Vec<Vec<Value>> = stored.rows().collect();
let held: Vec<Vec<Value>> = held.rows().collect();
assert_eq!(stored, held, "{predicate} differs between a file and memory");
assert!(!filtered(&file, &query), "{predicate} built a filter over the file");
}
let _ = std::fs::remove_file(&path);
}