Skip to main content

munin_msbuild/
string_table.rs

1// Copyright (c) Michael Grier
2
3//! Deduplicated string table for binlog reading.
4//!
5//! Strings in the binlog are assigned sequential indices starting at 10.
6//! Index 0 means null (absent), index 1 means empty string, and indices 2–9
7//! are reserved. Changing these constants is a breaking change.
8
9use crate::error::MuninError;
10
11/// First index assigned to a stored string record.
12const STRING_START_INDEX: i32 = 10;
13
14/// Index value representing a null/absent string.
15const NULL_INDEX: i32 = 0;
16
17/// Index value representing an empty string.
18const EMPTY_INDEX: i32 = 1;
19
20/// A table of deduplicated strings read from String records in the binlog stream.
21///
22/// Strings are appended in the order they appear (each String record gets the
23/// next sequential index starting at [`STRING_START_INDEX`]). Event payloads
24/// reference strings by index.
25#[derive(Debug, Default)]
26pub struct StringTable {
27    entries: Vec<String>,
28}
29
30impl StringTable {
31    /// Create an empty string table.
32    pub fn new() -> Self {
33        Self {
34            entries: Vec::new(),
35        }
36    }
37
38    /// Add a string record. Returns the index assigned to it.
39    pub fn add(&mut self, value: String) -> i32 {
40        let index = STRING_START_INDEX + self.entries.len() as i32;
41        self.entries.push(value);
42        index
43    }
44
45    /// Look up a string by its on-disk index.
46    ///
47    /// - Index 0 → `Ok(None)` (null)
48    /// - Index 1 → `Ok(Some(""))` (empty)
49    /// - Index 2–9 → `Err(InvalidIndex)` (reserved, not yet assigned meaning)
50    /// - Index ≥ 10 → lookup in the table
51    pub fn get(&self, index: i32) -> Result<Option<&str>, MuninError> {
52        if index == NULL_INDEX {
53            return Ok(None);
54        }
55        if index == EMPTY_INDEX {
56            return Ok(Some(""));
57        }
58        let table_index = index - STRING_START_INDEX;
59        if table_index < 0 || table_index as usize >= self.entries.len() {
60            return Err(MuninError::InvalidIndex {
61                kind: "string",
62                index,
63            });
64        }
65        Ok(Some(&self.entries[table_index as usize]))
66    }
67
68    /// Number of string records ingested so far.
69    pub fn len(&self) -> usize {
70        self.entries.len()
71    }
72
73    /// Whether the table is empty (no string records ingested).
74    pub fn is_empty(&self) -> bool {
75        self.entries.is_empty()
76    }
77
78    /// Borrow the stored strings in insertion (index) order.
79    pub fn entries(&self) -> &[String] {
80        &self.entries
81    }
82
83    /// Mutably borrow the stored strings in insertion (index) order.
84    ///
85    /// Provided for in-place rewrites that must preserve indices (e.g.
86    /// [`crate::redact`]). Replacing an entry's contents keeps every
87    /// existing string-index reference valid, since indices are positional.
88    pub fn entries_mut(&mut self) -> &mut [String] {
89        &mut self.entries
90    }
91}
92
93#[cfg(test)]
94mod tests;