graphrecords_query/optimizer/
stats.rs1use 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}