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 Some(&distinct) = self
182 .node_distinct_values
183 .get(&(label.to_string(), property.to_string()))
184 else {
185 return Some(0);
186 };
187 Some(total.div_ceil(distinct.max(1) as u64))
190 }
191
192 pub fn estimate_node_list_element(&self, label: &str, property: &str) -> Option<u64> {
196 let scope = (label.to_owned(), property.to_owned());
197 if !self.node_range_indexes.contains(&scope) {
198 return None;
199 }
200 Some(
201 self.node_list_elements
202 .get(&scope)
203 .map_or(0, ListElementStats::per_element),
204 )
205 }
206
207 pub fn estimate_relationship_list_element(
209 &self,
210 rel_type: &str,
211 property: &str,
212 ) -> Option<u64> {
213 let scope = (rel_type.to_owned(), property.to_owned());
214 if !self.relationship_range_indexes.contains(&scope) {
215 return None;
216 }
217 Some(
218 self.relationship_list_elements
219 .get(&scope)
220 .map_or(0, ListElementStats::per_element),
221 )
222 }
223
224 pub fn label_count(&self, label: &str) -> Option<u64> {
225 self.nodes_by_label.get(label).copied().map(|c| c as u64)
226 }
227
228 pub fn relationship_type_count(&self, rel_type: &str) -> Option<u64> {
229 self.relationships_by_type
230 .get(rel_type)
231 .copied()
232 .map(|c| c as u64)
233 }
234
235 pub fn fingerprint(&self) -> u64 {
236 let mut hasher = DefaultHasher::new();
237 self.node_count.hash(&mut hasher);
238 self.relationship_count.hash(&mut hasher);
239 self.nodes_by_label.hash(&mut hasher);
240 self.relationships_by_type.hash(&mut hasher);
241 self.node_distinct_values.hash(&mut hasher);
242 self.relationship_distinct_values.hash(&mut hasher);
243 self.node_range_indexes.hash(&mut hasher);
244 self.relationship_range_indexes.hash(&mut hasher);
245 self.node_text_indexes.hash(&mut hasher);
246 self.relationship_text_indexes.hash(&mut hasher);
247 self.node_point_indexes.hash(&mut hasher);
248 self.relationship_point_indexes.hash(&mut hasher);
249 self.node_vector_indexes.hash(&mut hasher);
250 self.relationship_vector_indexes.hash(&mut hasher);
251 self.node_list_elements.hash(&mut hasher);
252 self.relationship_list_elements.hash(&mut hasher);
253 hasher.finish()
254 }
255
256 pub fn plan_signature(&self) -> u64 {
264 let mut sig = PlanSignature::new();
265 sig.totals(self.node_count, self.relationship_count);
266 for (label, count) in &self.nodes_by_label {
267 sig.label(label, *count);
268 }
269 for (rel_type, count) in &self.relationships_by_type {
270 sig.relationship_type(rel_type, *count);
271 }
272 for ((label, property), distinct) in self.node_distinct_values.iter() {
273 sig.node_distinct(label, property, *distinct);
274 }
275 for ((rel_type, property), distinct) in self.relationship_distinct_values.iter() {
276 sig.relationship_distinct(rel_type, property, *distinct);
277 }
278 let scopes = [
279 (
280 StoredIndexEntity::Node,
281 StoredIndexKind::Range,
282 &self.node_range_indexes,
283 ),
284 (
285 StoredIndexEntity::Node,
286 StoredIndexKind::Text,
287 &self.node_text_indexes,
288 ),
289 (
290 StoredIndexEntity::Node,
291 StoredIndexKind::Point,
292 &self.node_point_indexes,
293 ),
294 (
295 StoredIndexEntity::Node,
296 StoredIndexKind::Vector,
297 &self.node_vector_indexes,
298 ),
299 (
300 StoredIndexEntity::Relationship,
301 StoredIndexKind::Range,
302 &self.relationship_range_indexes,
303 ),
304 (
305 StoredIndexEntity::Relationship,
306 StoredIndexKind::Text,
307 &self.relationship_text_indexes,
308 ),
309 (
310 StoredIndexEntity::Relationship,
311 StoredIndexKind::Point,
312 &self.relationship_point_indexes,
313 ),
314 (
315 StoredIndexEntity::Relationship,
316 StoredIndexKind::Vector,
317 &self.relationship_vector_indexes,
318 ),
319 ];
320 for (entity, kind, set) in scopes {
321 for (label, property) in set {
322 sig.index(entity, kind, label, property);
323 }
324 }
325 for (entity, elements) in [
326 (StoredIndexEntity::Node, &self.node_list_elements),
327 (
328 StoredIndexEntity::Relationship,
329 &self.relationship_list_elements,
330 ),
331 ] {
332 for ((label, property), stats) in elements {
333 sig.list_elements(entity, label, property, *stats);
334 }
335 }
336 sig.finish()
337 }
338
339 pub fn has_node_range_index(&self, label: &str, property: &str) -> bool {
340 self.node_range_indexes
341 .contains(&(label.to_owned(), property.to_owned()))
342 }
343
344 pub fn has_node_text_index(&self, label: &str, property: &str) -> bool {
345 self.node_text_indexes
346 .contains(&(label.to_owned(), property.to_owned()))
347 }
348
349 pub fn has_node_point_index(&self, label: &str, property: &str) -> bool {
350 self.node_point_indexes
351 .contains(&(label.to_owned(), property.to_owned()))
352 }
353
354 pub fn has_relationship_range_index(&self, rel_type: &str, property: &str) -> bool {
355 self.relationship_range_indexes
356 .contains(&(rel_type.to_owned(), property.to_owned()))
357 }
358
359 pub fn has_relationship_text_index(&self, rel_type: &str, property: &str) -> bool {
360 self.relationship_text_indexes
361 .contains(&(rel_type.to_owned(), property.to_owned()))
362 }
363
364 pub fn has_relationship_point_index(&self, rel_type: &str, property: &str) -> bool {
365 self.relationship_point_indexes
366 .contains(&(rel_type.to_owned(), property.to_owned()))
367 }
368
369 pub fn has_node_vector_index(&self, label: &str, property: &str) -> bool {
370 self.node_vector_indexes
371 .contains(&(label.to_owned(), property.to_owned()))
372 }
373
374 pub fn has_relationship_vector_index(&self, rel_type: &str, property: &str) -> bool {
375 self.relationship_vector_indexes
376 .contains(&(rel_type.to_owned(), property.to_owned()))
377 }
378}