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;