use powdb_query::executor::Engine;
use powdb_query::result::QueryResult;
use powdb_storage::types::Value;
const BIG: f64 = 9_007_199_254_740_992.0;
const EXACT_TOTAL: f64 = 631.25;
fn temp_dir(name: &str) -> std::path::PathBuf {
std::env::temp_dir().join(format!(
"powdb_float_sum_{name}_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
))
}
fn exec(engine: &mut Engine, query: &str) -> QueryResult {
engine
.execute_powql(query)
.unwrap_or_else(|e| panic!("failed to execute `{query}`: {e}"))
}
fn scalar_float(result: QueryResult, query: &str) -> f64 {
match result {
QueryResult::Scalar(Value::Float(f)) => f,
QueryResult::Rows { rows, .. } if rows.len() == 1 && rows[0].len() == 1 => {
match rows[0][0] {
Value::Float(f) => f,
ref other => panic!("`{query}` answered {other:?}, wanted a float"),
}
}
other => panic!("`{query}` answered {other:?}, wanted a float"),
}
}
fn float_of(engine: &mut Engine, query: &str) -> f64 {
let result = exec(engine, query);
scalar_float(result, query)
}
fn readonly_float(engine: &Engine, query: &str) -> f64 {
let result = engine
.execute_powql_readonly(query)
.unwrap_or_else(|e| panic!("failed to execute `{query}` read-only: {e}"));
scalar_float(result, query)
}
fn load(engine: &mut Engine) {
exec(engine, "type F { required id: int, v: float }");
for i in 0..100i64 {
let big = if i % 2 == 0 { BIG } else { -BIG };
exec(
engine,
&format!("insert F {{ id := {i}, v := {big:?} }}", big = big),
);
let small = f64::from((i + 1) as i32) / 8.0;
exec(
engine,
&format!("insert F {{ id := {}, v := {small:?} }}", 100 + i),
);
}
exec(engine, "alter F add index .id");
}
#[test]
fn a_float_sum_is_the_same_number_on_every_access_path() {
let dir = temp_dir("sum");
std::fs::create_dir_all(&dir).unwrap();
let mut engine = Engine::new(&dir).unwrap();
load(&mut engine);
let scan = float_of(&mut engine, "sum(F { .v })");
let indexed = float_of(&mut engine, "sum(F filter .id >= 0 { .v })");
let read_only = readonly_float(&engine, "sum(F filter .id >= 0 { .v })");
let grouped = {
let query = "F group .id >= 0 { total: sum(.v) }";
match exec(&mut engine, query) {
QueryResult::Rows { rows, columns } => {
assert_eq!(rows.len(), 1, "one group expected, got {rows:?}");
let idx = columns.iter().position(|c| c == "total").unwrap();
match rows[0][idx] {
Value::Float(f) => f,
ref other => panic!("grouped sum answered {other:?}"),
}
}
other => panic!("`{query}` answered {other:?}"),
}
};
assert_eq!(
scan.to_bits(),
indexed.to_bits(),
"the same rows summed through a scan ({scan:?}) and through an index \
({indexed:?}) must be the same number"
);
assert_eq!(
scan.to_bits(),
read_only.to_bits(),
"the read-only handle answered {read_only:?}, the writable one {scan:?}"
);
assert_eq!(
scan.to_bits(),
grouped.to_bits(),
"the grouped path answered {grouped:?}, the ungrouped one {scan:?}"
);
assert_eq!(
scan, EXACT_TOTAL,
"the exact total is {EXACT_TOTAL}, got {scan:?}"
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_float_avg_is_the_same_number_on_every_access_path() {
let dir = temp_dir("avg");
std::fs::create_dir_all(&dir).unwrap();
let mut engine = Engine::new(&dir).unwrap();
load(&mut engine);
let scan = float_of(&mut engine, "avg(F { .v })");
let indexed = float_of(&mut engine, "avg(F filter .id >= 0 { .v })");
let read_only = readonly_float(&engine, "avg(F filter .id >= 0 { .v })");
assert_eq!(
scan.to_bits(),
indexed.to_bits(),
"avg through a scan ({scan:?}) and through an index ({indexed:?}) must \
be the same number"
);
assert_eq!(
scan.to_bits(),
read_only.to_bits(),
"the read-only handle answered {read_only:?}, the writable one {scan:?}"
);
assert_eq!(
scan,
EXACT_TOTAL / 200.0,
"the exact average is {}, got {scan:?}",
EXACT_TOTAL / 200.0
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn an_infinite_float_sum_stays_infinite() {
let dir = temp_dir("infinite");
std::fs::create_dir_all(&dir).unwrap();
let mut engine = Engine::new(&dir).unwrap();
exec(&mut engine, "type I { required id: int, v: float }");
for i in 0..3i64 {
exec(
&mut engine,
&format!("insert I {{ id := {i}, v := 1.7e308 }}"),
);
}
let total = float_of(&mut engine, "sum(I { .v })");
assert!(
total.is_infinite() && total.is_sign_positive(),
"a total past f64's range is +inf, got {total:?}"
);
std::fs::remove_dir_all(&dir).ok();
}