use std::{collections::HashSet, sync::Arc};
pub const DEFAULT_CARDINALITY_THRESHOLD: usize = 32;
#[derive(Debug)]
pub struct StringInterner {
pool: HashSet<Arc<str>>,
threshold: usize,
active: bool,
}
impl Default for StringInterner {
fn default() -> Self {
Self::new(DEFAULT_CARDINALITY_THRESHOLD)
}
}
impl StringInterner {
pub fn new(threshold: usize) -> Self {
Self { pool: HashSet::with_capacity(threshold.min(64)), threshold, active: true }
}
pub fn intern(&mut self, s: &str) -> Arc<str> {
if !self.active {
return Arc::from(s);
}
if let Some(existing) = self.pool.get(s) {
return existing.clone();
}
if self.pool.len() >= self.threshold {
self.active = false;
return Arc::from(s);
}
let arc: Arc<str> = Arc::from(s);
self.pool.insert(arc.clone());
arc
}
pub fn intern_arc(&mut self, arc: Arc<str>) -> Arc<str> {
if !self.active {
return arc;
}
if let Some(existing) = self.pool.get(arc.as_ref()) {
return existing.clone();
}
if self.pool.len() >= self.threshold {
self.active = false;
return arc;
}
self.pool.insert(arc.clone());
arc
}
#[inline]
pub fn is_active(&self) -> bool {
self.active
}
#[inline]
pub fn cardinality(&self) -> usize {
self.pool.len()
}
#[inline]
pub fn threshold(&self) -> usize {
self.threshold
}
pub fn contains(&self, s: &str) -> bool {
self.pool.contains(s)
}
pub fn clear(&mut self) {
self.pool.clear();
self.active = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_interning() {
let mut interner = StringInterner::new(32);
let s1 = interner.intern("hello");
let s2 = interner.intern("hello");
let s3 = interner.intern("world");
assert!(Arc::ptr_eq(&s1, &s2));
assert!(!Arc::ptr_eq(&s1, &s3));
assert_eq!(s1.as_ref(), "hello");
assert_eq!(s3.as_ref(), "world");
}
#[test]
fn test_cardinality_tracking() {
let mut interner = StringInterner::new(32);
interner.intern("a");
interner.intern("b");
interner.intern("a");
assert_eq!(interner.cardinality(), 2);
assert!(interner.is_active());
}
#[test]
fn test_threshold_exceeded() {
let mut interner = StringInterner::new(3);
interner.intern("a");
interner.intern("b");
interner.intern("c");
assert!(interner.is_active());
let d = interner.intern("d");
assert!(!interner.is_active());
let e = interner.intern("e");
assert_eq!(d.as_ref(), "d");
assert_eq!(e.as_ref(), "e");
assert_eq!(interner.cardinality(), 3);
}
#[test]
fn test_intern_arc() {
let mut interner = StringInterner::new(32);
let s1 = interner.intern("test");
let arc = Arc::from("test");
let s2 = interner.intern_arc(arc);
assert!(Arc::ptr_eq(&s1, &s2));
}
#[test]
fn test_contains() {
let mut interner = StringInterner::new(32);
interner.intern("exists");
assert!(interner.contains("exists"));
assert!(!interner.contains("not_exists"));
}
#[test]
fn test_clear() {
let mut interner = StringInterner::new(2);
interner.intern("a");
interner.intern("b");
interner.intern("c"); assert!(!interner.is_active());
interner.clear();
assert!(interner.is_active());
assert_eq!(interner.cardinality(), 0);
}
#[test]
fn test_tpch_like_columns() {
let mut interner = StringInterner::new(32);
for _ in 0..10000 {
interner.intern("A");
interner.intern("N");
interner.intern("R");
}
assert!(interner.is_active());
assert_eq!(interner.cardinality(), 3);
let a1 = interner.intern("A");
let a2 = interner.intern("A");
assert!(Arc::ptr_eq(&a1, &a2));
}
#[test]
fn test_default() {
let interner = StringInterner::default();
assert_eq!(interner.threshold(), DEFAULT_CARDINALITY_THRESHOLD);
assert!(interner.is_active());
}
}