Skip to main content

sqry_core/query/
pipeline.rs

1//! Pipeline/aggregation result types for post-query analysis.
2//!
3//! The pipeline syntax `query | stage1 | stage2` enables post-query analysis
4//! via aggregation stages like `count`, `group_by`, `top`, and `stats`.
5//!
6//! # Stages
7//!
8//! - `count` — Total number of matched symbols
9//! - `group_by <field>` — Group matches by field value with counts
10//! - `top <N> <field>` — Top N groups by count
11//! - `stats` — Comprehensive summary (count, by-kind, by-lang, by-visibility)
12
13use std::fmt;
14
15/// Result of a pipeline aggregation stage.
16pub enum AggregationResult {
17    /// Total count of matched symbols.
18    Count(CountResult),
19    /// Symbols grouped by a field value.
20    GroupBy(GroupByResult),
21    /// Top N groups by count.
22    Top(TopResult),
23    /// Comprehensive statistics.
24    Stats(StatsResult),
25}
26
27/// Count of matched symbols.
28pub struct CountResult {
29    /// Total number of matched symbols.
30    pub total: usize,
31}
32
33/// Symbols grouped by a field value, sorted descending by count.
34pub struct GroupByResult {
35    /// The field used for grouping.
36    pub field: String,
37    /// Groups as `(value, count)` pairs, sorted descending by count.
38    pub groups: Vec<(String, usize)>,
39}
40
41/// Top N groups by count.
42pub struct TopResult {
43    /// The field used for grouping.
44    pub field: String,
45    /// The requested number of top entries.
46    pub n: usize,
47    /// Top entries as `(value, count)` pairs, sorted descending by count.
48    pub entries: Vec<(String, usize)>,
49}
50
51/// Comprehensive statistics about matched symbols.
52pub struct StatsResult {
53    /// Total number of matched symbols.
54    pub total: usize,
55    /// Breakdown by node kind (e.g., function, class, method).
56    pub by_kind: Vec<(String, usize)>,
57    /// Breakdown by language.
58    pub by_lang: Vec<(String, usize)>,
59    /// Breakdown by visibility.
60    pub by_visibility: Vec<(String, usize)>,
61}
62
63impl fmt::Display for AggregationResult {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            Self::Count(r) => write!(f, "{r}"),
67            Self::GroupBy(r) => write!(f, "{r}"),
68            Self::Top(r) => write!(f, "{r}"),
69            Self::Stats(r) => write!(f, "{r}"),
70        }
71    }
72}
73
74impl fmt::Display for CountResult {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        write!(f, "{}", self.total)
77    }
78}
79
80impl fmt::Display for GroupByResult {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        for (value, count) in &self.groups {
83            writeln!(f, "{value}: {count}")?;
84        }
85        Ok(())
86    }
87}
88
89impl fmt::Display for TopResult {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        for (value, count) in &self.entries {
92            writeln!(f, "{value}: {count}")?;
93        }
94        Ok(())
95    }
96}
97
98impl fmt::Display for StatsResult {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        writeln!(f, "Total: {}", self.total)?;
101        writeln!(f, "\nBy kind:")?;
102        for (kind, count) in &self.by_kind {
103            writeln!(f, "  {kind}: {count}")?;
104        }
105        writeln!(f, "\nBy language:")?;
106        for (lang, count) in &self.by_lang {
107            writeln!(f, "  {lang}: {count}")?;
108        }
109        writeln!(f, "\nBy visibility:")?;
110        for (vis, count) in &self.by_visibility {
111            writeln!(f, "  {vis}: {count}")?;
112        }
113        Ok(())
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_count_result_display() {
123        let result = CountResult { total: 42 };
124        assert_eq!(format!("{result}"), "42");
125    }
126
127    #[test]
128    fn test_group_by_result_display() {
129        let result = GroupByResult {
130            field: "lang".to_string(),
131            groups: vec![("rust".to_string(), 10), ("python".to_string(), 5)],
132        };
133        let output = format!("{result}");
134        assert!(output.contains("rust: 10"));
135        assert!(output.contains("python: 5"));
136    }
137
138    #[test]
139    fn test_top_result_display() {
140        let result = TopResult {
141            field: "lang".to_string(),
142            n: 2,
143            entries: vec![("rust".to_string(), 10), ("python".to_string(), 5)],
144        };
145        let output = format!("{result}");
146        assert!(output.contains("rust: 10"));
147        assert!(output.contains("python: 5"));
148    }
149
150    #[test]
151    fn test_stats_result_display() {
152        let result = StatsResult {
153            total: 15,
154            by_kind: vec![("function".to_string(), 10)],
155            by_lang: vec![("rust".to_string(), 15)],
156            by_visibility: vec![("public".to_string(), 8)],
157        };
158        let output = format!("{result}");
159        assert!(output.contains("Total: 15"));
160        assert!(output.contains("function: 10"));
161        assert!(output.contains("rust: 15"));
162        assert!(output.contains("public: 8"));
163    }
164}