fst_incremental 1.0.2

A thread-safe, updatable finite state set: dynamic insertions, deletions and queries over an immutable fst::Set fronted by a compact mutation buffer with amortized rebuilds.
Documentation
use std::sync::Arc;

use memmap2::Mmap;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::arena::CompactArenaSet;

/// What a mutation actually did to the set.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FstChangeType {
  /// Only the in-memory mutation buffer was touched. The persisted FST bytes are unchanged.
  BuffersModified,
  /// The persisted FST was rebuilt and the mutation buffer was cleared into it.
  FstRebuilt,
  /// The call was a no-op: the set already agreed with the requested state.
  NoChange,
}

/// Returned by every mutating call so the caller can decide what needs persisting.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct FstMutationResult {
  pub change_type: FstChangeType,
}

/// A point-in-time copy of the mutation buffer, for the caller to serialize.
///
/// Pair it with [`crate::IncrementalFstSet::persisted_fst_as_bytes`]: the two together are
/// the complete state of a set.
///
/// Produced by [`crate::IncrementalFstSet::buffers_snapshot`]. Build one from a buffer you
/// restored yourself with [`SerializableFstBuffers::new`] or `From<CompactArenaSet>`, and
/// take the buffer back out with [`SerializableFstBuffers::into_mutation_buffer`].
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SerializableFstBuffers {
  mutation_buffer: CompactArenaSet,
}

impl SerializableFstBuffers {
  pub fn new(mutation_buffer: CompactArenaSet) -> Self {
    Self { mutation_buffer }
  }

  pub fn mutation_buffer(&self) -> &CompactArenaSet {
    &self.mutation_buffer
  }

  /// Takes the buffer out, for handing straight to [`FstConfigOptions::with_initial_buffer`].
  pub fn into_mutation_buffer(self) -> CompactArenaSet {
    self.mutation_buffer
  }
}

impl From<CompactArenaSet> for SerializableFstBuffers {
  fn from(mutation_buffer: CompactArenaSet) -> Self {
    Self { mutation_buffer }
  }
}

/// Rebuild thresholds and the initial buffer contents, passed to every constructor.
///
/// `None` on a threshold means "use the default"; the builder methods are the intended
/// way to set them.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct FstConfigOptions {
  pub rebuild_threshold_adds_count: Option<usize>,
  pub rebuild_threshold_dels_ratio: Option<f32>,
  pub min_rebuild_interval_ms: Option<u64>,
  pub initial_mutation_buffer: Option<CompactArenaSet>,
}

impl FstConfigOptions {
  /// Live buffer entries that trigger a rebuild when no explicit threshold is set.
  pub const DEFAULT_REBUILD_ADDS_COUNT: usize = 10000;
  /// Tombstones as a fraction of persisted keys that trigger a rebuild when none is set.
  pub const DEFAULT_REBUILD_DELS_RATIO: f32 = 0.3;

  pub fn new() -> Self {
    Default::default()
  }

  pub fn with_rebuild_adds_count(mut self, count: usize) -> Self {
    self.rebuild_threshold_adds_count = Some(count);
    self
  }

  pub fn with_rebuild_dels_ratio(mut self, ratio: f32) -> Self {
    self.rebuild_threshold_dels_ratio = Some(ratio);
    self
  }

  /// Floor on the gap between rebuilds. A rebuild that comes due inside the window is
  /// deferred, reported as [`FstChangeType::BuffersModified`], and run on the next call
  /// past the window.
  pub fn with_min_rebuild_interval_ms(mut self, interval_ms: u64) -> Self {
    self.min_rebuild_interval_ms = Some(interval_ms);
    self
  }

  /// Seeds the set with a buffer previously taken from [`SerializableFstBuffers`].
  pub fn with_initial_buffer(mut self, buffer: CompactArenaSet) -> Self {
    self.initial_mutation_buffer = Some(buffer);
    self
  }
}

impl Default for FstConfigOptions {
  fn default() -> Self {
    Self {
      rebuild_threshold_adds_count: Some(Self::DEFAULT_REBUILD_ADDS_COUNT),
      rebuild_threshold_dels_ratio: Some(Self::DEFAULT_REBUILD_DELS_RATIO),
      min_rebuild_interval_ms: None,
      initial_mutation_buffer: None,
    }
  }
}

#[derive(Clone)]
pub(crate) enum FstDataHolder {
  Memory(Arc<Vec<u8>>),
  Mapped(Arc<Mmap>),
}

impl AsRef<[u8]> for FstDataHolder {
  fn as_ref(&self) -> &[u8] {
    match self {
      FstDataHolder::Memory(arc_vec) => arc_vec.as_ref(),
      FstDataHolder::Mapped(arc_mmap) => arc_mmap.as_ref(),
    }
  }
}