use std::collections::HashMap;
use reifydb_test_harness::engine::TestEngine;
use reifydb_value::{
error::Diagnostic,
params::Params,
value::{Value, digest::Digest, duration::Duration, frame::frame::Frame, value_type::ValueType},
};
const ACCURACY: u32 = 10_000;
fn engine() -> TestEngine {
let t = TestEngine::new();
t.admin("CREATE NAMESPACE test");
t.admin("CREATE TABLE test::t { a: int4, b: int4 }");
t.command("INSERT test::t [{ a: 2, b: 1 }, { a: 1, b: 2 }]");
t
}
fn float_digest() -> Digest {
let mut digest = Digest::new(ValueType::Float8, ACCURACY).unwrap();
for value in [10.0, 20.0, 30.0] {
digest.add_value(&Value::float8(value)).unwrap();
}
digest
}
fn duration_digest() -> Digest {
let mut digest = Digest::new(ValueType::Duration, ACCURACY).unwrap();
for ms in [5, 50, 500, 5000] {
digest.add_value(&Value::Duration(Duration::from_milliseconds(ms).unwrap())).unwrap();
}
digest
}
fn with_digest(digest: &Digest) -> Params {
let map: HashMap<String, Value> = HashMap::from([("d".to_string(), Value::Digest(Box::new(digest.clone())))]);
Params::from(map)
}
fn query(t: &TestEngine, rql: &str, params: Params) -> Result<Vec<Frame>, Box<Diagnostic>> {
let r = t.inner().query_as(TestEngine::identity(), rql, params);
match r.error {
Some(e) => Err(e.0),
None => Ok(r.frames),
}
}
fn column_values(frames: &[Frame], name: &str) -> Vec<Value> {
assert_eq!(frames.len(), 1, "expected exactly one frame, got {}", frames.len());
let column = frames[0].column(name).unwrap().unwrap_or_else(|| panic!("no column {name}"));
(0..column.len()).map(|row| column.get_value(row)).collect()
}
#[test]
fn sorting_by_a_digest_column_reports_sort_002_instead_of_panicking() {
let t = engine();
let err = query(&t, "FROM test::t | extend { d: $d } | sort { d }", with_digest(&float_digest())).unwrap_err();
assert_eq!(err.code, "SORT_002", "got: {err:?}");
}
#[test]
fn top_k_by_a_digest_column_reports_sort_002_instead_of_panicking() {
let t = engine();
let err = query(&t, "FROM test::t | extend { d: $d } | sort { d } | take 1", with_digest(&float_digest()))
.unwrap_err();
assert_eq!(err.code, "SORT_002", "got: {err:?}");
}
#[test]
fn grouping_by_a_digest_column_reports_aggregate_008_instead_of_panicking() {
let t = engine();
let err = query(
&t,
"FROM test::t | extend { d: $d } | aggregate { n: math::count(a) } by { d }",
with_digest(&float_digest()),
)
.unwrap_err();
assert_eq!(err.code, "AGGREGATE_008", "got: {err:?}");
}
#[test]
fn distinct_on_a_digest_column_is_an_error_not_a_panic() {
let t = engine();
let err = query(&t, "FROM test::t | extend { d: $d } | distinct { d }", with_digest(&float_digest()))
.unwrap_err();
assert_eq!(err.code, "DISTINCT_001", "got: {err:?}");
}
#[test]
fn distinct_over_all_columns_including_a_digest_is_an_error() {
let t = engine();
let err = query(&t, "FROM test::t | extend { d: $d } | distinct {}", with_digest(&float_digest())).unwrap_err();
assert_eq!(err.code, "DISTINCT_001", "got: {err:?}");
}
#[test]
fn a_digest_parameter_reaches_the_result_and_renders_as_its_count() {
let t = engine();
let frames = query(&t, "FROM test::t | extend { d: $d } | map { a, d }", with_digest(&float_digest())).unwrap();
assert_eq!(column_values(&frames, "d"), vec![Value::Digest(Box::new(float_digest())); 2]);
assert!(frames[0].to_string().contains("digest(n: 3)"), "rendered:\n{}", frames[0]);
}
#[test]
fn approx_percentile_in_map_reads_p_from_each_row_of_a_float8_digest() {
let t = engine();
let digest = float_digest();
let frames = query(
&t,
"FROM test::t | extend { d: $d } | map { a, p: stats::approx_percentile(d, if a == 1 { 0.0 } else { 1.0 }) }",
with_digest(&digest),
)
.unwrap();
let a = column_values(&frames, "a");
let p = column_values(&frames, "p");
for (row, a) in a.iter().enumerate() {
let expected_p = if *a == Value::Int4(1) {
0.0
} else {
1.0
};
assert_eq!(p[row], digest.percentile_value(expected_p).unwrap(), "row {row} with a {a}");
}
assert_ne!(p[0], p[1], "p 0 and p 1 over three distinct values must differ");
}
#[test]
fn approx_percentile_in_map_returns_duration_for_a_duration_digest() {
let t = engine();
let digest = duration_digest();
let frames = query(
&t,
"FROM test::t | extend { d: $d } | map { p: stats::approx_percentile(d, 0.75) }",
with_digest(&digest),
)
.unwrap();
let expected = digest.percentile_value(0.75).unwrap();
assert!(matches!(expected, Value::Duration(_)));
assert_eq!(column_values(&frames, "p"), vec![expected; 2]);
}
#[test]
fn approx_percentile_in_map_with_a_literal_p_out_of_range_is_an_error() {
let t = engine();
let err = query(
&t,
"FROM test::t | extend { d: $d } | map { p: stats::approx_percentile(d, 1.5) }",
with_digest(&float_digest()),
)
.unwrap_err();
assert!(format!("{err:?}").contains("p must be between 0 and 1"), "got: {err:?}");
}
#[test]
fn approx_percentile_in_map_on_a_raw_column_is_an_error() {
let t = engine();
let err = query(&t, "FROM test::t | map { p: stats::approx_percentile(a, 0.5, 0.01) }", Params::None)
.unwrap_err();
assert!(format!("{err:?}").contains("only supported inside window or aggregate"), "got: {err:?}");
}
#[test]
fn comparing_two_digests_with_equals_is_an_error_not_a_panic() {
let t = engine();
let result = query(&t, "FROM test::t | extend { d: $d } | map { same: d == d }", with_digest(&float_digest()));
assert!(result.is_err(), "d == d on a digest must fail, got frames: {result:?}");
}