1use std::collections::{hash_map::DefaultHasher, BTreeMap, BTreeSet};
13use std::hash::{Hash, Hasher};
14use std::sync::Arc;
15
16use super::index_catalog::{StoredIndexEntity, StoredIndexKind};
17
18pub type DistinctValues = BTreeMap<(String, String), usize>;
21
22#[derive(Debug, Default, Clone, PartialEq, Eq)]
25pub struct GraphStats {
26 pub node_count: usize,
28 pub relationship_count: usize,
30 pub nodes_by_label: BTreeMap<String, usize>,
32 pub relationships_by_type: BTreeMap<String, usize>,
34 pub node_distinct_values: Arc<DistinctValues>,
44 pub relationship_distinct_values: Arc<DistinctValues>,
46 pub node_range_indexes: BTreeSet<(String, String)>,
48 pub relationship_range_indexes: BTreeSet<(String, String)>,
49 pub node_text_indexes: BTreeSet<(String, String)>,
51 pub relationship_text_indexes: BTreeSet<(String, String)>,
52 pub node_point_indexes: BTreeSet<(String, String)>,
54 pub relationship_point_indexes: BTreeSet<(String, String)>,
55 pub node_vector_indexes: BTreeSet<(String, String)>,
60 pub relationship_vector_indexes: BTreeSet<(String, String)>,
61 pub node_list_elements: BTreeMap<(String, String), ListElementStats>,
65 pub relationship_list_elements: BTreeMap<(String, String), ListElementStats>,
66}
67
68#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
71pub struct ListElementStats {
72 pub distinct: usize,
74 pub postings: usize,
77}
78
79impl ListElementStats {
80 pub fn per_element(&self) -> u64 {
82 (self.postings as u64).div_ceil(self.distinct.max(1) as u64)
83 }
84}
85
86pub struct PlanSignature {
91 hasher: DefaultHasher,
92}
93
94impl Default for PlanSignature {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100impl PlanSignature {
101 pub fn new() -> Self {
102 Self {
103 hasher: DefaultHasher::new(),
104 }
105 }
106
107 pub fn bucket(count: usize) -> u32 {
111 usize::BITS - count.leading_zeros()
112 }
113
114 pub fn totals(&mut self, nodes: usize, relationships: usize) {
115 (0u8, Self::bucket(nodes), Self::bucket(relationships)).hash(&mut self.hasher);
116 }
117
118 pub fn label(&mut self, label: &str, nodes: usize) {
121 (1u8, label, Self::bucket(nodes)).hash(&mut self.hasher);
122 }
123
124 pub fn relationship_type(&mut self, rel_type: &str, relationships: usize) {
125 (2u8, rel_type, Self::bucket(relationships)).hash(&mut self.hasher);
126 }
127
128 pub fn node_distinct(&mut self, label: &str, property: &str, distinct: usize) {
129 (3u8, label, property, Self::bucket(distinct)).hash(&mut self.hasher);
130 }
131
132 pub fn relationship_distinct(&mut self, rel_type: &str, property: &str, distinct: usize) {
133 (4u8, rel_type, property, Self::bucket(distinct)).hash(&mut self.hasher);
134 }
135
136 pub fn index(
139 &mut self,
140 entity: StoredIndexEntity,
141 kind: StoredIndexKind,
142 label: &str,
143 property: &str,
144 ) {
145 (5u8, entity, kind.as_str(), label, property).hash(&mut self.hasher);
146 }
147
148 pub fn list_elements(
151 &mut self,
152 entity: StoredIndexEntity,
153 label: &str,
154 property: &str,
155 stats: ListElementStats,
156 ) {
157 (
158 6u8,
159 entity,
160 label,
161 property,
162 Self::bucket(stats.per_element() as usize),
163 )
164 .hash(&mut self.hasher);
165 }
166
167 pub fn finish(&self) -> u64 {
168 self.hasher.finish()
169 }
170}
171
172impl GraphStats {
173 pub fn estimate_node_property_equality(&self, label: &str, property: &str) -> Option<u64> {
177 let total = self.nodes_by_label.get(label).copied()? as u64;
178 let distinct = self
179 .node_distinct_values
180 .get(&(label.to_string(), property.to_string()))
181 .copied()
182 .unwrap_or(1)
183 .max(1) as u64;
184 Some(total.div_ceil(distinct))
187 }
188
189 pub fn estimate_node_list_element(&self, label: &str, property: &str) -> Option<u64> {
193 let scope = (label.to_owned(), property.to_owned());
194 if !self.node_range_indexes.contains(&scope) {
195 return None;
196 }
197 Some(
198 self.node_list_elements
199 .get(&scope)
200 .map_or(0, ListElementStats::per_element),
201 )
202 }
203
204 pub fn estimate_relationship_list_element(
206 &self,
207 rel_type: &str,
208 property: &str,
209 ) -> Option<u64> {
210 let scope = (rel_type.to_owned(), property.to_owned());
211 if !self.relationship_range_indexes.contains(&scope) {
212 return None;
213 }
214 Some(
215 self.relationship_list_elements
216 .get(&scope)
217 .map_or(0, ListElementStats::per_element),
218 )
219 }
220
221 pub fn label_count(&self, label: &str) -> Option<u64> {
222 self.nodes_by_label.get(label).copied().map(|c| c as u64)
223 }
224
225 pub fn relationship_type_count(&self, rel_type: &str) -> Option<u64> {
226 self.relationships_by_type
227 .get(rel_type)
228 .copied()
229 .map(|c| c as u64)
230 }
231
232 pub fn fingerprint(&self) -> u64 {
233 let mut hasher = DefaultHasher::new();
234 self.node_count.hash(&mut hasher);
235 self.relationship_count.hash(&mut hasher);
236 self.nodes_by_label.hash(&mut hasher);
237 self.relationships_by_type.hash(&mut hasher);
238 self.node_distinct_values.hash(&mut hasher);
239 self.relationship_distinct_values.hash(&mut hasher);
240 self.node_range_indexes.hash(&mut hasher);
241 self.relationship_range_indexes.hash(&mut hasher);
242 self.node_text_indexes.hash(&mut hasher);
243 self.relationship_text_indexes.hash(&mut hasher);
244 self.node_point_indexes.hash(&mut hasher);
245 self.relationship_point_indexes.hash(&mut hasher);
246 self.node_vector_indexes.hash(&mut hasher);
247 self.relationship_vector_indexes.hash(&mut hasher);
248 self.node_list_elements.hash(&mut hasher);
249 self.relationship_list_elements.hash(&mut hasher);
250 hasher.finish()
251 }
252
253 pub fn plan_signature(&self) -> u64 {
261 let mut sig = PlanSignature::new();
262 sig.totals(self.node_count, self.relationship_count);
263 for (label, count) in &self.nodes_by_label {
264 sig.label(label, *count);
265 }
266 for (rel_type, count) in &self.relationships_by_type {
267 sig.relationship_type(rel_type, *count);
268 }
269 for ((label, property), distinct) in self.node_distinct_values.iter() {
270 sig.node_distinct(label, property, *distinct);
271 }
272 for ((rel_type, property), distinct) in self.relationship_distinct_values.iter() {
273 sig.relationship_distinct(rel_type, property, *distinct);
274 }
275 let scopes = [
276 (
277 StoredIndexEntity::Node,
278 StoredIndexKind::Range,
279 &self.node_range_indexes,
280 ),
281 (
282 StoredIndexEntity::Node,
283 StoredIndexKind::Text,
284 &self.node_text_indexes,
285 ),
286 (
287 StoredIndexEntity::Node,
288 StoredIndexKind::Point,
289 &self.node_point_indexes,
290 ),
291 (
292 StoredIndexEntity::Node,
293 StoredIndexKind::Vector,
294 &self.node_vector_indexes,
295 ),
296 (
297 StoredIndexEntity::Relationship,
298 StoredIndexKind::Range,
299 &self.relationship_range_indexes,
300 ),
301 (
302 StoredIndexEntity::Relationship,
303 StoredIndexKind::Text,
304 &self.relationship_text_indexes,
305 ),
306 (
307 StoredIndexEntity::Relationship,
308 StoredIndexKind::Point,
309 &self.relationship_point_indexes,
310 ),
311 (
312 StoredIndexEntity::Relationship,
313 StoredIndexKind::Vector,
314 &self.relationship_vector_indexes,
315 ),
316 ];
317 for (entity, kind, set) in scopes {
318 for (label, property) in set {
319 sig.index(entity, kind, label, property);
320 }
321 }
322 for (entity, elements) in [
323 (StoredIndexEntity::Node, &self.node_list_elements),
324 (
325 StoredIndexEntity::Relationship,
326 &self.relationship_list_elements,
327 ),
328 ] {
329 for ((label, property), stats) in elements {
330 sig.list_elements(entity, label, property, *stats);
331 }
332 }
333 sig.finish()
334 }
335
336 pub fn has_node_range_index(&self, label: &str, property: &str) -> bool {
337 self.node_range_indexes
338 .contains(&(label.to_owned(), property.to_owned()))
339 }
340
341 pub fn has_node_text_index(&self, label: &str, property: &str) -> bool {
342 self.node_text_indexes
343 .contains(&(label.to_owned(), property.to_owned()))
344 }
345
346 pub fn has_node_point_index(&self, label: &str, property: &str) -> bool {
347 self.node_point_indexes
348 .contains(&(label.to_owned(), property.to_owned()))
349 }
350
351 pub fn has_relationship_range_index(&self, rel_type: &str, property: &str) -> bool {
352 self.relationship_range_indexes
353 .contains(&(rel_type.to_owned(), property.to_owned()))
354 }
355
356 pub fn has_relationship_text_index(&self, rel_type: &str, property: &str) -> bool {
357 self.relationship_text_indexes
358 .contains(&(rel_type.to_owned(), property.to_owned()))
359 }
360
361 pub fn has_relationship_point_index(&self, rel_type: &str, property: &str) -> bool {
362 self.relationship_point_indexes
363 .contains(&(rel_type.to_owned(), property.to_owned()))
364 }
365
366 pub fn has_node_vector_index(&self, label: &str, property: &str) -> bool {
367 self.node_vector_indexes
368 .contains(&(label.to_owned(), property.to_owned()))
369 }
370
371 pub fn has_relationship_vector_index(&self, rel_type: &str, property: &str) -> bool {
372 self.relationship_vector_indexes
373 .contains(&(rel_type.to_owned(), property.to_owned()))
374 }
375}