use crate::query::ast::{AggFunc, Attr, Query, SelectItem};
use crate::query::execute::{
class_name_matches, class_name_matches_spec, column_name, compile_from_regex, query_columns,
};
use crate::query::model::{QueryResult, QueryValue};
use crate::query::plan::QueryPlan;
pub struct ClassSummary<'a> {
pub name: &'a str,
pub count: u64,
pub shallow_total: u64,
}
pub fn run_histogram(q: &Query, plan: &QueryPlan, classes: &[ClassSummary]) -> QueryResult {
let _ = plan; let mut count = 0u64;
let mut shallow = 0u64;
let from_regex = q
.from
.class_spec()
.and_then(|spec| compile_from_regex(spec).ok().flatten());
for c in classes {
let matches = match q.from.class_spec() {
Some(spec) => class_name_matches_spec(c.name, spec, from_regex.as_ref()),
None => class_name_matches(c.name, q.from.class_name()),
};
if matches {
count += c.count;
shallow += c.shallow_total;
}
}
let cols = query_columns(q);
let mut row: Vec<QueryValue> = Vec::new();
for item in &q.select {
let (_name, val) = eval_agg(item, count, shallow);
row.push(val);
}
QueryResult {
name: String::new(),
oql: String::new(),
columns: cols,
rows: vec![row],
row_count: 1,
truncated: false,
error: None,
note: None,
viz: None,
elapsed_ms: None,
}
}
fn eval_agg(item: &SelectItem, count: u64, shallow: u64) -> (String, QueryValue) {
match item {
SelectItem::Aggregate { func, arg } => {
let label = column_name(item);
let arg_is_shallow = matches!(arg.as_ref(), SelectItem::Attr(Attr::UsedHeapSize));
let v = match func {
AggFunc::Count => QueryValue::Int(count as i64),
AggFunc::Sum if arg_is_shallow => QueryValue::Int(shallow as i64),
AggFunc::Avg if arg_is_shallow && count > 0 => {
QueryValue::Float(shallow as f64 / count as f64)
}
_ => QueryValue::Null,
};
(label, v)
}
_ => (column_name(item), QueryValue::Null),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::query::ast::{AggFunc, Attr, SelectItem};
use crate::query::parse::parse;
use crate::query::plan::QueryPlan;
use crate::query::plan::plan_query;
fn pq(q: &crate::query::ast::Query) -> QueryPlan {
plan_query(q, crate::query::DEFAULT_PATH_DEPTH_CAP).unwrap()
}
fn summaries() -> Vec<ClassSummary<'static>> {
vec![
ClassSummary {
name: "java.lang.String",
count: 100,
shallow_total: 2400,
},
ClassSummary {
name: "java.util.HashMap",
count: 10,
shallow_total: 480,
},
]
}
#[test]
fn count_star_of_one_class() {
let q = parse("SELECT COUNT(*) FROM java.lang.String").unwrap();
let plan = pq(&q);
let cs = summaries();
let res = run_histogram(&q, &plan, &cs);
assert_eq!(res.rows[0][0], QueryValue::Int(100));
}
#[test]
fn sum_shallow_of_one_class() {
let q = parse("SELECT SUM(@usedHeapSize) FROM java.lang.String").unwrap();
let plan = pq(&q);
let cs = summaries();
let res = run_histogram(&q, &plan, &cs);
assert_eq!(res.rows[0][0], QueryValue::Int(2400));
}
#[test]
fn glob_matches_multiple_classes() {
let q = parse("SELECT COUNT(*) FROM java.util.*").unwrap();
let plan = pq(&q);
let cs = summaries();
let res = run_histogram(&q, &plan, &cs);
assert_eq!(res.rows[0][0], QueryValue::Int(10));
}
#[test]
fn regex_from_matches_multiple_java_classes() {
let q = parse(r#"SELECT COUNT(*) FROM "java\..*""#).unwrap();
let plan = pq(&q);
let res = run_histogram(&q, &plan, &summaries());
assert_eq!(res.rows[0][0], QueryValue::Int(110));
}
#[test]
fn regex_from_trailing_string_matches_one_class() {
let q = parse(r#"SELECT COUNT(*) FROM ".*String""#).unwrap();
let plan = pq(&q);
let res = run_histogram(&q, &plan, &summaries());
assert_eq!(res.rows[0][0], QueryValue::Int(100));
}
#[test]
fn regex_from_matches_nothing_is_zero() {
let q = parse(r#"SELECT COUNT(*) FROM "no\.such\..*""#).unwrap();
let plan = pq(&q);
let res = run_histogram(&q, &plan, &summaries());
assert_eq!(res.rows[0][0], QueryValue::Int(0));
}
#[test]
fn avg_shallow_is_histogram_only_and_correct() {
let q = parse("SELECT AVG(@usedHeapSize) FROM java.lang.String").unwrap();
let plan = pq(&q);
assert_eq!(
plan.kind,
crate::query::plan::StageKind::HistogramOnly,
"AVG(@usedHeapSize) must plan as HistogramOnly, got {:?}",
plan.kind
);
let cs = summaries();
let res = run_histogram(&q, &plan, &cs);
assert_eq!(res.rows[0][0], QueryValue::Float(24.0));
}
#[test]
fn avg_no_match_is_null() {
let q = parse("SELECT AVG(@usedHeapSize) FROM com.nonexistent.Class").unwrap();
let plan = pq(&q);
let cs = summaries();
let res = run_histogram(&q, &plan, &cs);
assert_eq!(res.row_count, 1);
assert_eq!(res.rows[0][0], QueryValue::Null);
}
#[test]
fn count_no_match_is_zero() {
let q = parse("SELECT COUNT(*) FROM com.nonexistent.Class").unwrap();
let plan = pq(&q);
let cs = summaries();
let res = run_histogram(&q, &plan, &cs);
assert_eq!(res.row_count, 1);
assert_eq!(res.rows[0][0], QueryValue::Int(0));
}
#[test]
fn sum_no_match_is_zero() {
let q = parse("SELECT SUM(@usedHeapSize) FROM com.nonexistent.Class").unwrap();
let plan = pq(&q);
let cs = summaries();
let res = run_histogram(&q, &plan, &cs);
assert_eq!(res.row_count, 1);
assert_eq!(res.rows[0][0], QueryValue::Int(0));
}
#[test]
fn multiple_aggregates_in_one_select() {
let q = parse("SELECT COUNT(*), SUM(@usedHeapSize) FROM java.lang.String").unwrap();
let plan = pq(&q);
let cs = summaries();
let res = run_histogram(&q, &plan, &cs);
assert_eq!(res.rows[0].len(), 2, "must have two columns");
assert_eq!(res.rows[0][0], QueryValue::Int(100), "COUNT(*)");
assert_eq!(res.rows[0][1], QueryValue::Int(2400), "SUM(@usedHeapSize)");
assert_eq!(res.columns[0].name, "COUNT(*)");
assert_eq!(res.columns[1].name, "SUM(@usedHeapSize)");
}
#[test]
fn glob_java_star_sums_both_classes() {
let q = parse("SELECT COUNT(*), SUM(@usedHeapSize) FROM java.*").unwrap();
let plan = pq(&q);
let cs = summaries();
let res = run_histogram(&q, &plan, &cs);
assert_eq!(res.rows[0][0], QueryValue::Int(110), "COUNT(*)");
assert_eq!(res.rows[0][1], QueryValue::Int(2880), "SUM(@usedHeapSize)");
}
#[test]
fn sum_non_shallow_arg_is_null() {
let item = SelectItem::Aggregate {
func: AggFunc::Sum,
arg: Box::new(SelectItem::Attr(Attr::ObjectId)),
};
let (_label, val) = eval_agg(&item, 50, 1200);
assert_eq!(val, QueryValue::Null);
}
#[test]
fn min_is_null() {
let item = SelectItem::Aggregate {
func: AggFunc::Min,
arg: Box::new(SelectItem::Attr(Attr::UsedHeapSize)),
};
let (_label, val) = eval_agg(&item, 50, 1200);
assert_eq!(val, QueryValue::Null);
}
#[test]
fn max_is_null() {
let item = SelectItem::Aggregate {
func: AggFunc::Max,
arg: Box::new(SelectItem::Attr(Attr::UsedHeapSize)),
};
let (_label, val) = eval_agg(&item, 50, 1200);
assert_eq!(val, QueryValue::Null);
}
#[test]
fn non_aggregate_select_item_is_null() {
let item = SelectItem::Star;
let (_label, val) = eval_agg(&item, 10, 100);
assert_eq!(val, QueryValue::Null);
}
#[test]
fn avg_zero_count_is_null() {
let item = SelectItem::Aggregate {
func: AggFunc::Avg,
arg: Box::new(SelectItem::Attr(Attr::UsedHeapSize)),
};
let (_label, val) = eval_agg(&item, 0, 0);
assert_eq!(val, QueryValue::Null);
}
#[test]
fn result_always_has_exactly_one_row() {
let q = parse("SELECT COUNT(*) FROM java.lang.String").unwrap();
let plan = pq(&q);
let res = run_histogram(&q, &plan, &summaries());
assert_eq!(res.row_count, 1);
assert_eq!(res.rows.len(), 1);
let q2 = parse("SELECT COUNT(*) FROM no.such.Class").unwrap();
let plan2 = pq(&q2);
let res2 = run_histogram(&q2, &plan2, &summaries());
assert_eq!(res2.row_count, 1);
assert_eq!(res2.rows.len(), 1);
}
#[test]
fn count_star_alias_wins_in_column_header() {
let q = parse("SELECT COUNT(*) AS n FROM java.lang.String").unwrap();
let plan = pq(&q);
assert_eq!(
plan.kind,
crate::query::plan::StageKind::HistogramOnly,
"COUNT(*) with no WHERE must route to HistogramOnly"
);
let cs = summaries();
let res = run_histogram(&q, &plan, &cs);
assert_eq!(
res.columns[0].name, "n",
"alias 'n' must override derived 'COUNT(*)' on the histogram path"
);
assert_eq!(
res.rows[0][0],
QueryValue::Int(100),
"row value must be unchanged"
);
}
#[test]
fn count_star_of_prim_array_matches_pretty_name() {
let q = parse("SELECT COUNT(*) FROM char[]").unwrap();
let plan = pq(&q);
let cs = vec![ClassSummary {
name: "char[]",
count: 225,
shallow_total: 9000,
}];
let res = run_histogram(&q, &plan, &cs);
assert_eq!(res.rows[0][0], QueryValue::Int(225));
}
#[test]
fn raw_descriptor_does_not_match_pretty_pattern() {
assert!(
!class_name_matches("[C", "char[]"),
"raw descriptor `[C` must NOT match `char[]` — this is the bug: pass2 \
fed summaries the raw name, so the histogram path never matched"
);
assert!(
class_name_matches("char[]", "char[]"),
"pretty name `char[]` must match `char[]`"
);
let q = parse("SELECT COUNT(*) FROM char[]").unwrap();
let plan = pq(&q);
let raw = vec![ClassSummary {
name: "[C",
count: 225,
shallow_total: 9000,
}];
let res = run_histogram(&q, &plan, &raw);
assert_eq!(
res.rows[0][0],
QueryValue::Int(0),
"raw-named summary must (wrongly) count 0 — the exact bug"
);
}
#[test]
fn result_metadata_defaults() {
let q = parse("SELECT COUNT(*) FROM java.lang.String").unwrap();
let plan = pq(&q);
let res = run_histogram(&q, &plan, &summaries());
assert!(res.name.is_empty(), "name must be empty");
assert!(res.oql.is_empty(), "oql must be empty");
assert!(!res.truncated);
assert!(res.error.is_none());
}
}