use super::*;
fn product_mix_engine() -> Engine {
let id = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
let dir = std::env::temp_dir().join(format!("powdb_product_mix_{}_{}", std::process::id(), id));
let mut engine = Engine::new(&dir).unwrap();
engine
.execute_powql(
"type Product { required name: str, required price: float, required stock: int }",
)
.unwrap();
engine
.execute_powql(r#"insert Product { name := "Apple", price := 1.5, stock := 10 }"#)
.unwrap();
engine
.execute_powql(r#"insert Product { name := "Banana", price := 0.25, stock := 4 }"#)
.unwrap();
engine
.execute_powql(r#"insert Product { name := "Cherry", price := 2.0, stock := 3 }"#)
.unwrap();
engine
}
fn as_float(v: &Value) -> f64 {
match v {
Value::Float(f) => *f,
other => panic!("expected Float, got {other:?}"),
}
}
#[test]
fn test_arith_float_times_int() {
let mut engine = product_mix_engine();
let result = engine
.execute_powql("Product { .name, total: .price * .stock }")
.unwrap();
match result {
QueryResult::Rows { columns, rows } => {
assert_eq!(columns, vec!["name", "total"]);
let mut by_name: std::collections::HashMap<String, f64> =
std::collections::HashMap::new();
for row in &rows {
let name = match &row[0] {
Value::Str(s) => s.clone(),
_ => panic!(),
};
by_name.insert(name, as_float(&row[1]));
}
assert!((by_name["Apple"] - 15.0).abs() < 1e-9);
assert!((by_name["Banana"] - 1.0).abs() < 1e-9);
assert!((by_name["Cherry"] - 6.0).abs() < 1e-9);
}
_ => panic!("expected rows"),
}
}
#[test]
fn test_arith_int_plus_float() {
let mut engine = product_mix_engine();
let result = engine
.execute_powql("Product { .name, bumped: .stock + .price }")
.unwrap();
match result {
QueryResult::Rows { rows, .. } => {
let mut by_name: std::collections::HashMap<String, f64> =
std::collections::HashMap::new();
for row in &rows {
let name = match &row[0] {
Value::Str(s) => s.clone(),
_ => panic!(),
};
by_name.insert(name, as_float(&row[1]));
}
assert!((by_name["Apple"] - 11.5).abs() < 1e-9);
assert!((by_name["Banana"] - 4.25).abs() < 1e-9);
assert!((by_name["Cherry"] - 5.0).abs() < 1e-9);
}
_ => panic!("expected rows"),
}
}
#[test]
fn test_arith_float_div_int() {
let mut engine = product_mix_engine();
let result = engine
.execute_powql("Product { .name, unit: .price / .stock }")
.unwrap();
match result {
QueryResult::Rows { rows, .. } => {
let mut by_name: std::collections::HashMap<String, f64> =
std::collections::HashMap::new();
for row in &rows {
let name = match &row[0] {
Value::Str(s) => s.clone(),
_ => panic!(),
};
by_name.insert(name, as_float(&row[1]));
}
assert!((by_name["Apple"] - 0.15).abs() < 1e-9);
assert!((by_name["Banana"] - 0.0625).abs() < 1e-9);
assert!((by_name["Cherry"] - (2.0 / 3.0)).abs() < 1e-9);
}
_ => panic!("expected rows"),
}
}
#[test]
fn test_arith_int_minus_float() {
let mut engine = product_mix_engine();
let result = engine
.execute_powql("Product { .name, delta: .stock - .price }")
.unwrap();
match result {
QueryResult::Rows { rows, .. } => {
let mut by_name: std::collections::HashMap<String, f64> =
std::collections::HashMap::new();
for row in &rows {
let name = match &row[0] {
Value::Str(s) => s.clone(),
_ => panic!(),
};
by_name.insert(name, as_float(&row[1]));
}
assert!((by_name["Apple"] - 8.5).abs() < 1e-9);
assert!((by_name["Banana"] - 3.75).abs() < 1e-9);
assert!((by_name["Cherry"] - 1.0).abs() < 1e-9);
}
_ => panic!("expected rows"),
}
}
#[test]
fn test_sum_float_scalar() {
let mut engine = product_mix_engine();
let result = engine.execute_powql("sum(Product { .price })").unwrap();
match result {
QueryResult::Scalar(v) => {
assert!(
(as_float(&v) - 3.75).abs() < 1e-9,
"expected 3.75, got {v:?}"
);
}
_ => panic!("expected scalar result, got {result:?}"),
}
}
#[test]
fn test_sum_float_group_by() {
let id = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
let dir =
std::env::temp_dir().join(format!("powdb_sum_float_gb_{}_{}", std::process::id(), id));
let mut engine = Engine::new(&dir).unwrap();
engine
.execute_powql("type Sale { required region: str, required amount: float }")
.unwrap();
engine
.execute_powql(r#"insert Sale { region := "E", amount := 1.5 }"#)
.unwrap();
engine
.execute_powql(r#"insert Sale { region := "E", amount := 2.25 }"#)
.unwrap();
engine
.execute_powql(r#"insert Sale { region := "W", amount := 4.0 }"#)
.unwrap();
engine
.execute_powql(r#"insert Sale { region := "W", amount := 0.5 }"#)
.unwrap();
let result = engine
.execute_powql("Sale group .region { .region, total: sum(.amount) }")
.unwrap();
match result {
QueryResult::Rows { columns, rows } => {
assert_eq!(columns, vec!["region", "total"]);
let mut by_region: std::collections::HashMap<String, f64> =
std::collections::HashMap::new();
for row in &rows {
let region = match &row[0] {
Value::Str(s) => s.clone(),
_ => panic!(),
};
by_region.insert(region, as_float(&row[1]));
}
assert!(
(by_region["E"] - 3.75).abs() < 1e-9,
"E: {:?}",
by_region.get("E")
);
assert!(
(by_region["W"] - 4.5).abs() < 1e-9,
"W: {:?}",
by_region.get("W")
);
}
_ => panic!("expected rows, got {result:?}"),
}
}
fn float_fast_engine() -> Engine {
let id = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
let dir = std::env::temp_dir().join(format!("powdb_float_fast_{}_{}", std::process::id(), id));
let mut engine = Engine::new(&dir).unwrap();
engine
.execute_powql("type Price { required name: str, price: float, required qty: int }")
.unwrap();
let rows = [
("a", "price := 1.5", "qty := 1"),
("b", "price := 0.25", "qty := 2"),
("c", "price := 2.0", "qty := 3"),
("d", "price := -3.5", "qty := 4"),
("e", "price := 10.0", "qty := 5"),
("f", "price := 0.5", "qty := 6"),
("g", "price := 100.0", "qty := 7"),
("h", "price := -0.0", "qty := 8"),
];
for (name, price, qty) in rows {
engine
.execute_powql(&format!(
r#"insert Price {{ name := "{name}", {price}, {qty} }}"#
))
.unwrap();
}
engine
}
#[test]
fn test_d10_agg_sum_float_fast_path() {
let mut engine = float_fast_engine();
let result = engine.execute_powql("sum(Price { .price })").unwrap();
match result {
QueryResult::Scalar(v) => {
assert!((as_float(&v) - 110.75).abs() < 1e-9, "got {v:?}");
}
_ => panic!("expected scalar, got {result:?}"),
}
}
#[test]
fn test_d10_agg_avg_float_fast_path() {
let mut engine = float_fast_engine();
let result = engine.execute_powql("avg(Price { .price })").unwrap();
match result {
QueryResult::Scalar(v) => {
assert!((as_float(&v) - 13.84375).abs() < 1e-9, "got {v:?}");
}
_ => panic!("expected scalar, got {result:?}"),
}
}
#[test]
fn test_d10_agg_min_float_fast_path() {
let mut engine = float_fast_engine();
let result = engine.execute_powql("min(Price { .price })").unwrap();
match result {
QueryResult::Scalar(v) => {
assert!((as_float(&v) - (-3.5)).abs() < 1e-9, "got {v:?}");
}
_ => panic!("expected scalar, got {result:?}"),
}
}
#[test]
fn test_d10_agg_max_float_fast_path() {
let mut engine = float_fast_engine();
let result = engine.execute_powql("max(Price { .price })").unwrap();
match result {
QueryResult::Scalar(v) => {
assert!((as_float(&v) - 100.0).abs() < 1e-9, "got {v:?}");
}
_ => panic!("expected scalar, got {result:?}"),
}
}
#[test]
fn test_d10_agg_count_distinct_float_fast_path() {
let mut engine = float_fast_engine();
let result = engine
.execute_powql("count(distinct Price { .price })")
.unwrap();
match result {
QueryResult::Scalar(Value::Int(n)) => assert_eq!(n, 8, "got {n}"),
_ => panic!("expected scalar int, got {result:?}"),
}
}
#[test]
fn test_d10_agg_float_with_compiled_where() {
let mut engine = float_fast_engine();
let result = engine
.execute_powql("sum(Price filter .price > 1.0 { .price })")
.unwrap();
match result {
QueryResult::Scalar(v) => {
assert!((as_float(&v) - 113.5).abs() < 1e-9, "got {v:?}");
}
_ => panic!("expected scalar, got {result:?}"),
}
}
#[test]
fn test_d10_agg_float_with_compiled_where_int_literal() {
let mut engine = float_fast_engine();
let result = engine
.execute_powql("sum(Price filter .price > 1 { .price })")
.unwrap();
match result {
QueryResult::Scalar(v) => {
assert!((as_float(&v) - 113.5).abs() < 1e-9, "got {v:?}");
}
_ => panic!("expected scalar, got {result:?}"),
}
}
#[test]
fn test_d10_agg_float_with_reversed_literal() {
let mut engine = float_fast_engine();
let result = engine
.execute_powql("count(Price filter 1.0 < .price { .price })")
.unwrap();
match result {
QueryResult::Scalar(Value::Int(n)) => assert_eq!(n, 4, "got {n}"),
_ => panic!("expected scalar int, got {result:?}"),
}
}
#[test]
fn test_d10_sort_float_desc_limit_fast_path() {
let mut engine = float_fast_engine();
let result = engine
.execute_powql("Price order .price desc limit 3 { .name, .price }")
.unwrap();
match result {
QueryResult::Rows { columns, rows } => {
assert_eq!(columns, vec!["name", "price"]);
assert_eq!(rows.len(), 3);
assert_eq!(rows[0][0], Value::Str("g".into())); assert!((as_float(&rows[0][1]) - 100.0).abs() < 1e-9);
assert_eq!(rows[1][0], Value::Str("e".into())); assert!((as_float(&rows[1][1]) - 10.0).abs() < 1e-9);
assert_eq!(rows[2][0], Value::Str("c".into())); assert!((as_float(&rows[2][1]) - 2.0).abs() < 1e-9);
}
_ => panic!("expected rows, got {result:?}"),
}
}
#[test]
fn test_d10_sort_float_asc_limit_fast_path() {
let mut engine = float_fast_engine();
let result = engine
.execute_powql("Price order .price limit 3 { .name, .price }")
.unwrap();
match result {
QueryResult::Rows { rows, .. } => {
assert_eq!(rows.len(), 3);
assert_eq!(rows[0][0], Value::Str("d".into())); assert_eq!(rows[1][0], Value::Str("h".into())); assert_eq!(rows[2][0], Value::Str("b".into())); }
_ => panic!("expected rows, got {result:?}"),
}
}
#[test]
fn test_d10_sort_float_with_compiled_filter() {
let mut engine = float_fast_engine();
let result = engine
.execute_powql("Price filter .price > 0.0 order .price desc limit 2 { .name }")
.unwrap();
match result {
QueryResult::Rows { rows, .. } => {
assert_eq!(rows.len(), 2);
assert_eq!(rows[0][0], Value::Str("g".into())); assert_eq!(rows[1][0], Value::Str("e".into())); }
_ => panic!("expected rows, got {result:?}"),
}
}
#[test]
fn test_f64_sortable_transform_monotonic() {
let samples: [f64; 11] = [
f64::NEG_INFINITY,
-1e100,
-1.0,
-f64::MIN_POSITIVE,
-0.0,
0.0,
f64::MIN_POSITIVE,
1.0,
1e100,
f64::INFINITY,
f64::NAN, ];
let mut sorted = samples;
sorted.sort_by(|a, b| a.total_cmp(b));
let as_sortable: Vec<u64> = sorted
.iter()
.map(|f| f64_bits_to_sortable_u64(f.to_bits()))
.collect();
for pair in as_sortable.windows(2) {
assert!(
pair[0] < pair[1],
"sortable u64 not monotonic: {:#x} >= {:#x}",
pair[0],
pair[1]
);
}
}