Skip to main content

graphrecords_query/optimizer/
stats.rs

1use graphrecords_core::{
2    GraphRecord,
3    graphrecord::{GraphRecordAttribute, Group},
4};
5use graphrecords_utils::aliases::{GrHashMap, GrHashSet};
6use std::{
7    any::{Any, TypeId},
8    cell::RefCell,
9    hash::Hash,
10};
11
12pub trait Statistic: 'static {
13    type Key: Hash + Eq + Clone + 'static;
14    type Value: Clone + 'static;
15
16    fn compute(graphrecord: &GraphRecord, key: &Self::Key) -> Self::Value;
17}
18
19pub struct Stats<'a> {
20    graphrecord: &'a GraphRecord,
21    cache: RefCell<GrHashMap<TypeId, Box<dyn Any>>>,
22}
23
24impl<'a> Stats<'a> {
25    #[must_use]
26    pub fn new(graphrecord: &'a GraphRecord) -> Self {
27        Self {
28            graphrecord,
29            cache: RefCell::new(GrHashMap::default()),
30        }
31    }
32
33    pub fn get<S: Statistic>(&self, key: &S::Key) -> S::Value {
34        let mut cache = self.cache.borrow_mut();
35
36        #[allow(clippy::missing_panics_doc)]
37        let map = cache
38            .entry(TypeId::of::<S>())
39            .or_insert_with(|| Box::new(GrHashMap::<S::Key, S::Value>::default()))
40            .downcast_mut::<GrHashMap<S::Key, S::Value>>()
41            .expect("Statistic cache type must match its TypeId key");
42
43        map.entry(key.clone())
44            .or_insert_with(|| S::compute(self.graphrecord, key))
45            .clone()
46    }
47}
48
49pub struct NodeGroupSize;
50
51impl Statistic for NodeGroupSize {
52    type Key = Group;
53    type Value = usize;
54
55    fn compute(graphrecord: &GraphRecord, key: &Self::Key) -> Self::Value {
56        graphrecord.nodes_in_group(key).map_or(0, Iterator::count)
57    }
58}
59
60pub struct EdgeGroupSize;
61
62impl Statistic for EdgeGroupSize {
63    type Key = Group;
64    type Value = usize;
65
66    fn compute(graphrecord: &GraphRecord, key: &Self::Key) -> Self::Value {
67        graphrecord.edges_in_group(key).map_or(0, Iterator::count)
68    }
69}
70
71pub struct NodeAttributeCardinality;
72
73impl Statistic for NodeAttributeCardinality {
74    type Key = GraphRecordAttribute;
75    type Value = usize;
76
77    fn compute(graphrecord: &GraphRecord, key: &Self::Key) -> Self::Value {
78        graphrecord
79            .node_indices()
80            .filter_map(|node_index| {
81                graphrecord
82                    .node_attributes(node_index)
83                    .expect("Node must exist")
84                    .get(key)
85                    .cloned()
86            })
87            .collect::<GrHashSet<_>>()
88            .len()
89    }
90}
91
92pub struct EdgeAttributeCardinality;
93
94impl Statistic for EdgeAttributeCardinality {
95    type Key = GraphRecordAttribute;
96    type Value = usize;
97
98    fn compute(graphrecord: &GraphRecord, key: &Self::Key) -> Self::Value {
99        graphrecord
100            .edge_indices()
101            .filter_map(|edge_index| {
102                graphrecord
103                    .edge_attributes(edge_index)
104                    .expect("Edge must exist")
105                    .get(key)
106                    .cloned()
107            })
108            .collect::<GrHashSet<_>>()
109            .len()
110    }
111}
112
113#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
114pub enum CountKind {
115    Nodes,
116    Edges,
117}
118
119pub struct Count;
120
121impl Statistic for Count {
122    type Key = CountKind;
123    type Value = usize;
124
125    fn compute(graphrecord: &GraphRecord, key: &Self::Key) -> Self::Value {
126        match key {
127            CountKind::Nodes => graphrecord.node_indices().count(),
128            CountKind::Edges => graphrecord.edge_indices().count(),
129        }
130    }
131}