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 rustc_hash::FxHashMap;
10
11use super::types::StringId;
12
13/// String interning table for fast string comparisons.
14#[derive(Debug, Default)]
15pub struct StringTable {
16    /// String to ID mapping
17    string_to_id: FxHashMap<String, StringId>,
18    /// ID to string mapping (for retrieval)
19    id_to_string: Vec<String>,
20}
21
22impl StringTable {
23    /// Creates a new empty string table.
24    #[must_use]
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    /// Interns a string, returning its ID.
30    ///
31    /// If the string already exists, returns the existing ID.
32    pub fn intern(&mut self, s: &str) -> StringId {
33        if let Some(&id) = self.string_to_id.get(s) {
34            return id;
35        }
36
37        // EPIC-032/US-010: physically unreachable (4B strings = terabytes of RAM)
38        let len = self.id_to_string.len();
39        debug_assert!(
40            len < u32::MAX as usize,
41            "StringTable overflow: cannot intern more than {} strings",
42            u32::MAX
43        );
44        #[allow(clippy::cast_possible_truncation)] // Bounds checked above
45        let id = StringId(len as u32);
46        self.id_to_string.push(s.to_string());
47        self.string_to_id.insert(s.to_string(), id);
48        id
49    }
50
51    /// Gets the string for an ID.
52    #[must_use]
53    pub fn get(&self, id: StringId) -> Option<&str> {
54        self.id_to_string.get(id.0 as usize).map(String::as_str)
55    }
56
57    /// Gets the ID for a string without interning.
58    #[must_use]
59    pub fn get_id(&self, s: &str) -> Option<StringId> {
60        self.string_to_id.get(s).copied()
61    }
62
63    /// Returns the number of interned strings.
64    #[must_use]
65    pub fn len(&self) -> usize {
66        self.id_to_string.len()
67    }
68
69    /// Returns true if the table is empty.
70    #[must_use]
71    pub fn is_empty(&self) -> bool {
72        self.id_to_string.is_empty()
73    }
74}