Skip to main content

linkrs_query/optimizer/stats/
view.rs

1//! Per-query statistics accessor scoped to a single space.
2//!
3//! The [`StatsView`] wraps the shared [`StatisticsManager`] together with the
4//! space of the query being optimized. A `None` space yields no statistics
5//! (returns `0` / `None`), which matches the semantics of space-less queries.
6
7use super::manager::StatisticsManager;
8use super::{EdgeTypeStatistics, TagStatistics};
9
10/// A borrowed, space-scoped view over the statistics manager.
11#[derive(Debug)]
12pub struct StatsView<'a> {
13    manager: &'a StatisticsManager,
14    space: Option<&'a str>,
15}
16
17impl<'a> StatsView<'a> {
18    /// Create a new statistics view for the given space.
19    pub fn new(manager: &'a StatisticsManager, space: Option<&'a str>) -> Self {
20        Self { manager, space }
21    }
22
23    /// The space this view is scoped to, if any.
24    pub fn space(&self) -> Option<&'a str> {
25        self.space
26    }
27
28    /// Number of vertices for a tag, or 0 when no space is set.
29    pub fn vertex_count(&self, tag_name: &str) -> u64 {
30        match self.space {
31            Some(space) => self.manager.get_vertex_count(space, tag_name),
32            None => 0,
33        }
34    }
35
36    /// Number of edges for an edge type, or 0 when no space is set.
37    pub fn edge_count(&self, edge_type: &str) -> u64 {
38        match self.space {
39            Some(space) => self.manager.get_edge_count(space, edge_type),
40            None => 0,
41        }
42    }
43
44    /// Tag statistics for the space, if any.
45    pub fn tag_stats(&self, tag_name: &str) -> Option<TagStatistics> {
46        match self.space {
47            Some(space) => self.manager.get_tag_stats(space, tag_name),
48            None => None,
49        }
50    }
51
52    /// Edge type statistics for the space, if any.
53    pub fn edge_stats(&self, edge_type: &str) -> Option<EdgeTypeStatistics> {
54        match self.space {
55            Some(space) => self.manager.get_edge_stats(space, edge_type),
56            None => None,
57        }
58    }
59
60    /// Property statistics for `property` under `tag_name` (or global), if any.
61    pub fn property_stats(
62        &self,
63        tag_name: Option<&str>,
64        property: &str,
65    ) -> Option<crate::optimizer::stats::PropertyStatistics> {
66        match self.space {
67            Some(space) => self.manager.get_property_stats(space, tag_name, property),
68            None => None,
69        }
70    }
71
72    /// Distinct-value count (NDV) for a property column, if collected.
73    pub fn property_ndv(&self, tag_name: Option<&str>, property: &str) -> Option<u64> {
74        self.property_stats(tag_name, property)
75            .map(|s| s.distinct_values)
76            .filter(|&n| n > 0)
77    }
78
79    /// Combined cardinality for a set of grouping properties.
80    pub fn combined_cardinality(
81        &self,
82        tag_name: Option<&str>,
83        properties: &[String],
84    ) -> Option<u64> {
85        match self.space {
86            Some(space) => self
87                .manager
88                .get_combined_cardinality(space, tag_name, properties),
89            None => None,
90        }
91    }
92
93    /// Reference to the underlying statistics manager.
94    pub fn manager(&self) -> &StatisticsManager {
95        self.manager
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::optimizer::stats::TagStatistics;
103
104    #[test]
105    fn test_view_without_space_returns_no_stats() {
106        let manager = StatisticsManager::new();
107        let view = StatsView::new(&manager, None);
108        assert_eq!(view.space(), None);
109        assert_eq!(view.vertex_count("person"), 0);
110        assert_eq!(view.edge_count("follows"), 0);
111        assert!(view.tag_stats("person").is_none());
112        assert!(view.edge_stats("follows").is_none());
113    }
114
115    #[test]
116    fn test_view_with_space_reads_scoped_stats() {
117        let manager = StatisticsManager::new();
118        let mut tag = TagStatistics::new("person".to_string());
119        tag.vertex_count = 1000;
120        manager.update_tag_stats("basketball", tag);
121
122        let view = StatsView::new(&manager, Some("basketball"));
123        assert_eq!(view.vertex_count("person"), 1000);
124        assert_eq!(view.edge_count("follows"), 0);
125        assert_eq!(view.tag_stats("person").map(|s| s.vertex_count), Some(1000));
126
127        let other = StatsView::new(&manager, Some("tennis"));
128        assert_eq!(other.vertex_count("person"), 0);
129    }
130}