use reifydb_test_harness::engine::TestEngine;
use reifydb_value::value::frame::frame::Frame;
fn engine() -> TestEngine {
let t = TestEngine::new();
t.admin("CREATE NAMESPACE test");
t.admin("CREATE TABLE test::t { g: int4, a: int4, b: int4 }");
t.command("INSERT test::t [{ g: 1, a: 2, b: 100 }, { g: 1, a: 3, b: 100 }, { g: 2, a: 10, b: 100 }]");
t
}
fn column_text(frames: &[Frame], name: &str) -> Vec<String> {
assert_eq!(frames.len(), 1, "expected exactly one frame, got {}", frames.len());
let column = frames[0]
.columns
.iter()
.find(|c| c.name == name)
.unwrap_or_else(|| panic!("column {name} missing from {:?}", frames[0].columns));
(0..column.data.len()).map(|i| column.data.as_string(i)).collect()
}
#[test]
fn a_map_entry_over_an_aggregate_is_computed_per_group() {
let t = engine();
let frames = t.query("FROM test::t | aggregate { total: math::sum(a) + 1 } by { g }");
let mut pairs: Vec<(String, String)> =
column_text(&frames, "g").into_iter().zip(column_text(&frames, "total")).collect();
pairs.sort();
assert_eq!(
pairs,
vec![("1".to_string(), "6".to_string()), ("2".to_string(), "11".to_string())],
"each group must add 1 to its own sum"
);
}
#[test]
fn a_map_entry_without_an_aggregate_reports_an_error_instead_of_panicking() {
let t = engine();
let err = t.query_err("FROM test::t | aggregate { x: a } by { g }");
assert!(err.contains("AGGREGATE_009"), "a map entry with no aggregate must report AGGREGATE_009, got: {err}");
}
#[test]
fn a_non_column_group_key_reports_an_error_instead_of_panicking() {
let t = engine();
let err = t.query_err("FROM test::t | aggregate { n: math::count(a) } by { g + 1 }");
assert!(err.contains("AGGREGATE_007"), "a non-column group key must report AGGREGATE_007, got: {err}");
}
#[test]
fn extra_arguments_to_an_aggregate_report_too_many_arguments() {
let t = engine();
let err = t.query_err("FROM test::t | aggregate { s: math::sum(a, b) } by { g }");
assert!(err.contains("FUNCTION_003"), "math::sum(a, b) must report FUNCTION_003, got: {err}");
}
#[test]
fn an_unknown_aggregate_is_reported_as_an_aggregate() {
let t = engine();
let err = t.query_err("FROM test::t | aggregate { s: math::nope(a) } by { g }");
assert!(err.contains("FUNCTION_009"), "a missing aggregate must report FUNCTION_009, got: {err}");
assert!(err.contains("Aggregate function"), "the message must name an aggregate function, got: {err}");
assert!(!err.contains("Generator"), "the message must not name a generator function, got: {err}");
}
#[test]
fn grouping_by_a_list_column_reports_an_error_instead_of_panicking() {
let t = engine();
let err = t.query_err("FROM test::t | extend { l: [a, b] } | aggregate { n: math::count(a) } by { l }");
assert!(err.contains("AGGREGATE_008"), "a list group key must report AGGREGATE_008, got: {err}");
}
#[test]
fn two_aggregates_in_one_map_entry_each_feed_their_own_operand() {
let t = engine();
let frames = t.query("FROM test::t | aggregate { d: math::sum(a) - math::sum(b) } by { g }");
let mut pairs: Vec<(String, String)> =
column_text(&frames, "g").into_iter().zip(column_text(&frames, "d")).collect();
pairs.sort();
assert_eq!(
pairs,
vec![("1".to_string(), "-195".to_string()), ("2".to_string(), "-90".to_string())],
"each group must subtract its own sum(b) from its own sum(a)"
);
}
#[test]
fn grouping_by_an_optional_list_column_reports_an_error_instead_of_panicking() {
let t = engine();
let err = t.query_err(
"FROM test::t | extend { l: if a > 2 { [a, b] } else { none } } | aggregate { n: math::count(a) } by { l }",
);
assert!(err.contains("AGGREGATE_008"), "an optional list group key must report AGGREGATE_008, got: {err}");
}