1use std::sync::Arc;
13
14use parking_lot::RwLock;
15use rustc_hash::FxHashMap;
16
17use crate::dsl::Field;
18use crate::segment::SegmentReader;
19
20pub struct LazyGlobalStats {
26 segments: Vec<Arc<SegmentReader>>,
28 total_docs: u64,
30 sparse_idf_cache: RwLock<FxHashMap<u32, FxHashMap<u32, f32>>>,
32 sparse_total_vectors_cache: RwLock<FxHashMap<u32, u64>>,
34 text_idf_cache: RwLock<FxHashMap<u32, FxHashMap<String, f32>>>,
36 avg_field_len_cache: RwLock<FxHashMap<u32, f32>>,
38}
39
40impl LazyGlobalStats {
41 pub fn new(segments: Vec<Arc<SegmentReader>>) -> Self {
43 let total_docs: u64 = segments.iter().map(|s| s.num_docs() as u64).sum();
44 Self {
45 segments,
46 total_docs,
47 sparse_idf_cache: RwLock::new(FxHashMap::default()),
48 sparse_total_vectors_cache: RwLock::new(FxHashMap::default()),
49 text_idf_cache: RwLock::new(FxHashMap::default()),
50 avg_field_len_cache: RwLock::new(FxHashMap::default()),
51 }
52 }
53
54 #[inline]
56 pub fn total_docs(&self) -> u64 {
57 self.total_docs
58 }
59
60 pub fn sparse_idf(&self, field: Field, dim_id: u32) -> f32 {
64 {
66 let cache = self.sparse_idf_cache.read();
67 if let Some(field_cache) = cache.get(&field.0)
68 && let Some(&idf) = field_cache.get(&dim_id)
69 {
70 return idf;
71 }
72 }
73
74 let df = self.compute_sparse_df(field, dim_id);
76 let n = self.cached_sparse_n(field);
77 let idf = if df > 0 && n > 0 {
78 (n as f32 / df as f32).ln().max(0.0)
79 } else {
80 0.0
81 };
82
83 {
85 let mut cache = self.sparse_idf_cache.write();
86 cache.entry(field.0).or_default().insert(dim_id, idf);
87 }
88
89 idf
90 }
91
92 pub fn sparse_idf_weights(&self, field: Field, dim_ids: &[u32]) -> Vec<f32> {
97 let mut result = vec![0.0f32; dim_ids.len()];
99 let mut misses: Vec<usize> = Vec::new();
100 {
101 let cache = self.sparse_idf_cache.read();
102 if let Some(field_cache) = cache.get(&field.0) {
103 for (i, &dim_id) in dim_ids.iter().enumerate() {
104 if let Some(&idf) = field_cache.get(&dim_id) {
105 result[i] = idf;
106 } else {
107 misses.push(i);
108 }
109 }
110 } else {
111 misses.extend(0..dim_ids.len());
112 }
113 }
114
115 if misses.is_empty() {
116 return result;
117 }
118
119 let n = self.cached_sparse_n(field);
121
122 let mut new_entries: Vec<(u32, f32)> = Vec::with_capacity(misses.len());
124 for &i in &misses {
125 let dim_id = dim_ids[i];
126 let df = self.compute_sparse_df(field, dim_id);
127 let idf = if df > 0 && n > 0 {
128 (n as f32 / df as f32).ln().max(0.0)
129 } else {
130 0.0
131 };
132 result[i] = idf;
133 new_entries.push((dim_id, idf));
134 }
135
136 {
138 let mut cache = self.sparse_idf_cache.write();
139 let field_cache = cache.entry(field.0).or_default();
140 for (dim_id, idf) in new_entries {
141 field_cache.insert(dim_id, idf);
142 }
143 }
144
145 result
146 }
147
148 fn cached_sparse_n(&self, field: Field) -> u64 {
151 {
153 let cache = self.sparse_total_vectors_cache.read();
154 if let Some(&tv) = cache.get(&field.0) {
155 return tv.max(self.total_docs);
156 }
157 }
158 let tv = self.compute_sparse_total_vectors(field);
160 self.sparse_total_vectors_cache.write().insert(field.0, tv);
161 tv.max(self.total_docs)
162 }
163
164 pub fn text_idf(&self, field: Field, term: &str) -> f32 {
168 {
170 let cache = self.text_idf_cache.read();
171 if let Some(field_cache) = cache.get(&field.0)
172 && let Some(&idf) = field_cache.get(term)
173 {
174 return idf;
175 }
176 }
177
178 let df = self.compute_text_df(field, term);
180 let n = self.total_docs as f32;
181 let df_f = df as f32;
182 let idf = if df > 0 {
183 ((n - df_f + 0.5) / (df_f + 0.5) + 1.0).ln()
184 } else {
185 0.0
186 };
187
188 {
190 let mut cache = self.text_idf_cache.write();
191 cache
192 .entry(field.0)
193 .or_default()
194 .insert(term.to_string(), idf);
195 }
196
197 idf
198 }
199
200 pub fn avg_field_len(&self, field: Field) -> f32 {
202 {
204 let cache = self.avg_field_len_cache.read();
205 if let Some(&avg) = cache.get(&field.0) {
206 return avg;
207 }
208 }
209
210 let mut weighted_sum = 0.0f64;
212 let mut total_weight = 0u64;
213
214 for segment in &self.segments {
215 let avg_len = segment.avg_field_len(field);
216 let doc_count = segment.text_corpus_size(field) as u64;
218 if avg_len > 0.0 && doc_count > 0 {
219 weighted_sum += avg_len as f64 * doc_count as f64;
220 total_weight += doc_count;
221 }
222 }
223
224 let avg = if total_weight > 0 {
225 (weighted_sum / total_weight as f64) as f32
226 } else {
227 1.0
228 };
229
230 {
232 let mut cache = self.avg_field_len_cache.write();
233 cache.insert(field.0, avg);
234 }
235
236 avg
237 }
238
239 fn compute_sparse_df(&self, field: Field, dim_id: u32) -> u64 {
242 let mut df = 0u64;
243 for segment in &self.segments {
244 if let Some(sparse_index) = segment.sparse_indexes().get(&field.0) {
245 df += sparse_index.doc_count(dim_id) as u64;
246 }
247 }
248 df
249 }
250
251 fn compute_sparse_total_vectors(&self, field: Field) -> u64 {
254 let mut total = 0u64;
255 for segment in &self.segments {
256 if let Some(sparse_index) = segment.sparse_indexes().get(&field.0) {
257 total += sparse_index.total_vectors as u64;
258 }
259 }
260 total
261 }
262
263 fn compute_text_df(&self, _field: Field, _term: &str) -> u64 {
268 0
272 }
273
274 pub fn num_segments(&self) -> usize {
276 self.segments.len()
277 }
278}
279
280impl std::fmt::Debug for LazyGlobalStats {
281 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282 f.debug_struct("LazyGlobalStats")
283 .field("total_docs", &self.total_docs)
284 .field("num_segments", &self.segments.len())
285 .field("sparse_cache_fields", &self.sparse_idf_cache.read().len())
286 .field("text_cache_fields", &self.text_idf_cache.read().len())
287 .finish()
288 }
289}
290
291#[derive(Debug)]
295pub struct GlobalStats {
296 total_docs: u64,
298 sparse_stats: FxHashMap<u32, SparseFieldStats>,
300 text_stats: FxHashMap<u32, TextFieldStats>,
302 generation: u64,
304}
305
306#[derive(Debug, Default)]
308pub struct SparseFieldStats {
309 pub doc_freqs: FxHashMap<u32, u64>,
311}
312
313#[derive(Debug, Default)]
315pub struct TextFieldStats {
316 pub doc_freqs: FxHashMap<String, u64>,
318 pub avg_field_len: f32,
320}
321
322impl GlobalStats {
323 pub fn new() -> Self {
325 Self {
326 total_docs: 0,
327 sparse_stats: FxHashMap::default(),
328 text_stats: FxHashMap::default(),
329 generation: 0,
330 }
331 }
332
333 #[inline]
335 pub fn total_docs(&self) -> u64 {
336 self.total_docs
337 }
338
339 #[inline]
341 pub fn sparse_idf(&self, field: Field, dim_id: u32) -> f32 {
342 if let Some(stats) = self.sparse_stats.get(&field.0)
343 && let Some(&df) = stats.doc_freqs.get(&dim_id)
344 && df > 0
345 {
346 return (self.total_docs as f32 / df as f32).ln();
347 }
348 0.0
349 }
350
351 pub fn sparse_idf_weights(&self, field: Field, dim_ids: &[u32]) -> Vec<f32> {
353 dim_ids.iter().map(|&d| self.sparse_idf(field, d)).collect()
354 }
355
356 #[inline]
358 pub fn text_idf(&self, field: Field, term: &str) -> f32 {
359 if let Some(stats) = self.text_stats.get(&field.0)
360 && let Some(&df) = stats.doc_freqs.get(term)
361 {
362 let n = self.total_docs as f32;
363 let df = df as f32;
364 return ((n - df + 0.5) / (df + 0.5) + 1.0).ln();
365 }
366 0.0
367 }
368
369 #[inline]
371 pub fn avg_field_len(&self, field: Field) -> f32 {
372 self.text_stats
373 .get(&field.0)
374 .map(|s| s.avg_field_len)
375 .unwrap_or(1.0)
376 }
377
378 #[inline]
380 pub fn generation(&self) -> u64 {
381 self.generation
382 }
383}
384
385impl Default for GlobalStats {
386 fn default() -> Self {
387 Self::new()
388 }
389}
390
391pub struct GlobalStatsBuilder {
393 pub total_docs: u64,
395 sparse_stats: FxHashMap<u32, SparseFieldStats>,
396 text_stats: FxHashMap<u32, TextFieldStats>,
397}
398
399impl GlobalStatsBuilder {
400 pub fn new() -> Self {
402 Self {
403 total_docs: 0,
404 sparse_stats: FxHashMap::default(),
405 text_stats: FxHashMap::default(),
406 }
407 }
408
409 pub fn add_segment(&mut self, reader: &SegmentReader) {
411 self.total_docs += reader.num_docs() as u64;
412
413 }
416
417 pub fn add_sparse_df(&mut self, field: Field, dim_id: u32, doc_count: u64) {
419 let stats = self.sparse_stats.entry(field.0).or_default();
420 *stats.doc_freqs.entry(dim_id).or_insert(0) += doc_count;
421 }
422
423 pub fn add_text_df(&mut self, field: Field, term: String, doc_count: u64) {
425 let stats = self.text_stats.entry(field.0).or_default();
426 *stats.doc_freqs.entry(term).or_insert(0) += doc_count;
427 }
428
429 pub fn set_avg_field_len(&mut self, field: Field, avg_len: f32) {
431 let stats = self.text_stats.entry(field.0).or_default();
432 stats.avg_field_len = avg_len;
433 }
434
435 pub fn build(self, generation: u64) -> GlobalStats {
437 GlobalStats {
438 total_docs: self.total_docs,
439 sparse_stats: self.sparse_stats,
440 text_stats: self.text_stats,
441 generation,
442 }
443 }
444}
445
446impl Default for GlobalStatsBuilder {
447 fn default() -> Self {
448 Self::new()
449 }
450}
451
452pub struct GlobalStatsCache {
457 stats: RwLock<Option<Arc<GlobalStats>>>,
459 generation: RwLock<u64>,
461}
462
463impl GlobalStatsCache {
464 pub fn new() -> Self {
466 Self {
467 stats: RwLock::new(None),
468 generation: RwLock::new(0),
469 }
470 }
471
472 pub fn invalidate(&self) {
474 let mut current_gen = self.generation.write();
475 *current_gen += 1;
476 let mut stats = self.stats.write();
477 *stats = None;
478 }
479
480 pub fn generation(&self) -> u64 {
482 *self.generation.read()
483 }
484
485 pub fn get(&self) -> Option<Arc<GlobalStats>> {
487 self.stats.read().clone()
488 }
489
490 pub fn set(&self, stats: GlobalStats) {
492 let mut cached = self.stats.write();
493 *cached = Some(Arc::new(stats));
494 }
495
496 pub fn get_or_compute<F>(&self, compute: F) -> Arc<GlobalStats>
500 where
501 F: FnOnce(&mut GlobalStatsBuilder),
502 {
503 if let Some(stats) = self.get() {
505 return stats;
506 }
507
508 let current_gen = self.generation();
510 let mut builder = GlobalStatsBuilder::new();
511 compute(&mut builder);
512 let stats = Arc::new(builder.build(current_gen));
513
514 let mut cached = self.stats.write();
516 *cached = Some(Arc::clone(&stats));
517
518 stats
519 }
520
521 pub fn needs_rebuild(&self) -> bool {
523 self.stats.read().is_none()
524 }
525
526 pub fn set_stats(&self, stats: GlobalStats) {
528 let mut cached = self.stats.write();
529 *cached = Some(Arc::new(stats));
530 }
531}
532
533impl Default for GlobalStatsCache {
534 fn default() -> Self {
535 Self::new()
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 #[test]
544 fn test_sparse_idf_computation() {
545 let mut builder = GlobalStatsBuilder::new();
546 builder.total_docs = 1000;
547 builder.add_sparse_df(Field(0), 42, 100); builder.add_sparse_df(Field(0), 43, 10); let stats = builder.build(1);
551
552 let idf_42 = stats.sparse_idf(Field(0), 42);
554 let idf_43 = stats.sparse_idf(Field(0), 43);
555
556 assert!(idf_43 > idf_42);
558 assert!((idf_42 - (1000.0_f32 / 100.0).ln()).abs() < 0.001);
559 assert!((idf_43 - (1000.0_f32 / 10.0).ln()).abs() < 0.001);
560 }
561
562 #[test]
563 fn test_text_idf_computation() {
564 let mut builder = GlobalStatsBuilder::new();
565 builder.total_docs = 10000;
566 builder.add_text_df(Field(0), "common".to_string(), 5000);
567 builder.add_text_df(Field(0), "rare".to_string(), 10);
568
569 let stats = builder.build(1);
570
571 let idf_common = stats.text_idf(Field(0), "common");
572 let idf_rare = stats.text_idf(Field(0), "rare");
573
574 assert!(idf_rare > idf_common);
576 }
577
578 #[test]
579 fn test_cache_invalidation() {
580 let cache = GlobalStatsCache::new();
581
582 assert!(cache.get().is_none());
584
585 let stats = cache.get_or_compute(|builder| {
587 builder.total_docs = 100;
588 });
589 assert_eq!(stats.total_docs(), 100);
590
591 assert!(cache.get().is_some());
593
594 cache.invalidate();
596 assert!(cache.get().is_none());
597 }
598}