Skip to main content

activecube_rs/
stats.rs

1use std::sync::Arc;
2
3use crate::compiler::ir::{QueryIR, SelectExpr, FilterNode};
4
5/// Query-level metadata collected after resolver execution.
6/// Application layer uses this for observability, billing, or logging.
7/// This struct intentionally contains no cost/credit/price fields —
8/// billing logic belongs in the application, not the generic library.
9#[derive(Debug, Clone)]
10pub struct QueryStats {
11    pub cube: String,
12    pub table: String,
13    pub limit: u32,
14    pub offset: u32,
15    pub select_count: usize,
16    pub aggregate_count: usize,
17    pub has_group_by: bool,
18    pub has_having: bool,
19    pub filter_depth: usize,
20    pub row_count: usize,
21    pub sql_generated: String,
22}
23
24impl QueryStats {
25    /// Build stats from a validated QueryIR and execution results.
26    pub fn from_ir(ir: &QueryIR, row_count: usize, sql: &str) -> Self {
27        let aggregate_count = ir.selects.iter()
28            .filter(|s| matches!(s, SelectExpr::Aggregate { .. }))
29            .count();
30
31        Self {
32            cube: ir.cube.clone(),
33            table: ir.table.clone(),
34            limit: ir.limit,
35            offset: ir.offset,
36            select_count: ir.selects.len(),
37            aggregate_count,
38            has_group_by: !ir.group_by.is_empty(),
39            has_having: !ir.having.is_empty(),
40            filter_depth: filter_depth(&ir.filters),
41            row_count,
42            sql_generated: sql.to_string(),
43        }
44    }
45}
46
47fn filter_depth(node: &FilterNode) -> usize {
48    match node {
49        FilterNode::Empty | FilterNode::Condition { .. } => 1,
50        FilterNode::And(children) | FilterNode::Or(children) => {
51            1 + children.iter().map(filter_depth).max().unwrap_or(0)
52        }
53        FilterNode::ArrayIncludes { element_conditions, .. } => {
54            1 + element_conditions.iter()
55                .flat_map(|conds| conds.iter().map(filter_depth))
56                .max()
57                .unwrap_or(0)
58        }
59    }
60}
61
62/// Callback invoked after each cube query execution with query metadata.
63/// The application layer registers this to collect stats for billing, logging, etc.
64pub type StatsCallback = Arc<dyn Fn(QueryStats) + Send + Sync>;