Skip to main content

velesdb_core/column_store/
string_table.rs

1//! String interning table for fast string comparisons.
2//!
3//! # Safety (EPIC-032/US-010)
4//!
5//! `StringId` uses u32 internally, limiting the table to ~4 billion strings.
6//! The `intern()` method debug-asserts this limit (physically unreachable:
7//! 4 billion interned strings would require terabytes of RAM).
8
9use std::sync::Arc;
10
11use rustc_hash::FxHashMap;
12
13use super::types::StringId;
14
15/// String interning table for fast string comparisons.
16///
17/// Both maps share one `Arc<str>` per interned string — an interning table
18/// that stored every string twice (map key + vec entry) would defeat half
19/// its own point. `Arc<str>` rather than `Rc<str>` because `ColumnStore`
20/// must stay `Send`/`Sync`.
21#[derive(Debug, Default)]
22pub struct StringTable {
23    /// String to ID mapping
24    string_to_id: FxHashMap<Arc<str>, StringId>,
25    /// ID to string mapping (for retrieval)
26    id_to_string: Vec<Arc<str>>,
27}
28
29impl StringTable {
30    /// Creates a new empty string table.
31    #[must_use]
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Interns a string, returning its ID.
37    ///
38    /// If the string already exists, returns the existing ID.
39    pub fn intern(&mut self, s: &str) -> StringId {
40        if let Some(&id) = self.string_to_id.get(s) {
41            return id;
42        }
43
44        // EPIC-032/US-010: physically unreachable (4B strings = terabytes of RAM)
45        let len = self.id_to_string.len();
46        debug_assert!(
47            len < u32::MAX as usize,
48            "StringTable overflow: cannot intern more than {} strings",
49            u32::MAX
50        );
51        #[allow(clippy::cast_possible_truncation)] // Bounds checked above
52        let id = StringId(len as u32);
53        let shared: Arc<str> = Arc::from(s);
54        self.id_to_string.push(Arc::clone(&shared));
55        self.string_to_id.insert(shared, id);
56        id
57    }
58
59    /// Gets the string for an ID.
60    #[must_use]
61    pub fn get(&self, id: StringId) -> Option<&str> {
62        self.id_to_string.get(id.0 as usize).map(AsRef::as_ref)
63    }
64
65    /// Gets the ID for a string without interning.
66    #[must_use]
67    pub fn get_id(&self, s: &str) -> Option<StringId> {
68        self.string_to_id.get(s).copied()
69    }
70
71    /// Returns the number of interned strings.
72    #[must_use]
73    pub fn len(&self) -> usize {
74        self.id_to_string.len()
75    }
76
77    /// Returns true if the table is empty.
78    #[must_use]
79    pub fn is_empty(&self) -> bool {
80        self.id_to_string.is_empty()
81    }
82}