Skip to main content

velesdb_core/database/
stats.rs

1//! Collection statistics: analyze and cache collection stats.
2
3use crate::{Error, Result};
4
5use super::Database;
6
7impl Database {
8    /// Analyzes a collection, caches stats, and persists them to disk.
9    ///
10    /// # Errors
11    ///
12    /// Returns an error if the name is invalid, the collection does not exist,
13    /// analysis fails, or stats cannot be serialized and written to disk.
14    pub fn analyze_collection(
15        &self,
16        name: &str,
17    ) -> Result<crate::collection::stats::CollectionStats> {
18        crate::validation::validate_collection_name(name)?;
19
20        let collection = self.resolve_collection(name)?;
21        let stats = collection.analyze()?;
22
23        self.collection_stats
24            .write()
25            .insert(name.to_string(), stats.clone());
26
27        // Bug #51: route the write through Collection so that stats_io_mutex
28        // is held, preventing a race with incremental histogram updates.
29        collection.write_stats_guarded(&stats)?;
30
31        // Issue #608: bump analyze_generation after stats are persisted so
32        // the compiled plan cache key changes and stale plans are rebuilt
33        // with the fresh calibrated cost estimates.
34        collection.bump_analyze_generation();
35
36        Ok(stats)
37    }
38
39    /// Returns cached statistics when available, loading from disk if present.
40    ///
41    /// # Errors
42    ///
43    /// Returns an error if the name is invalid, or the on-disk stats file
44    /// exists but cannot be read or deserialized.
45    pub fn get_collection_stats(
46        &self,
47        name: &str,
48    ) -> Result<Option<crate::collection::stats::CollectionStats>> {
49        crate::validation::validate_collection_name(name)?;
50
51        if let Some(stats) = self.collection_stats.read().get(name).cloned() {
52            return Ok(Some(stats));
53        }
54
55        let stats_path = self.data_dir.join(name).join("collection.stats.json");
56        if !stats_path.exists() {
57            return Ok(None);
58        }
59
60        let bytes = std::fs::read(stats_path)?;
61        let stats: crate::collection::stats::CollectionStats = serde_json::from_slice(&bytes)
62            .map_err(|e| Error::Serialization(format!("failed to parse stats: {e}")))?;
63        self.collection_stats
64            .write()
65            .insert(name.to_string(), stats.clone());
66        Ok(Some(stats))
67    }
68}