libdictenstein 0.1.0

High-performance dictionary data structures (trie, DAWG, double-array trie, suffix automaton, lock-free durable persistent ART) behind one trait API; pairs with liblevenshtein for fuzzy matching
//! Public read-path API for `PersistentARTrieChar<V, S>`.
//!
//! Split out of char `dict_impl_char.rs` (lines ~278-468, ~191 LOC)
//! as a Phase-6 char sub-module. Methods covered:
//!
//! - `contains` / `try_contains` / `get` / `try_get` — fail-fast read path
//! - `contains_optimistic` / `try_contains_optimistic` /
//!   `get_optimistic` / `try_get_optimistic` — optimistic concurrency
//!   variants with bounded retry on epoch-version conflicts
//! - `enter_epoch` / `current_epoch` / `advance_epoch` / `active_readers`
//!   — epoch-based reclamation interface
//! - `retry_stats_snapshot` / `is_write_locked` / `current_version`
//!   — observability accessors

use crate::persistent_artrie::block_storage::BlockStorage;
use crate::persistent_artrie::concurrency::{EpochGuard, OptimisticReadGuard};
use crate::persistent_artrie::error::Result;
use crate::value::DictionaryValue;

impl<V: DictionaryValue, S: BlockStorage> super::PersistentARTrieChar<V, S> {
    ///
    /// For persistent tries with lazy loading, this will load nodes on-demand.
    /// I/O errors during lazy loading fail closed as `false`. Use
    /// `try_contains()` for explicit error handling.
    pub fn contains(&self, term: &str) -> bool {
        match self.try_contains(term) {
            Ok(result) => result,
            Err(error) => {
                log::warn!("I/O error during lazy loading in contains(): {:?}", error);
                false
            }
        }
    }

    /// Check if a term exists in the trie with explicit error handling.
    ///
    /// This version returns a `Result` for lazy loading I/O errors.
    /// For disk-backed tries, prefetches children at each level for improved I/O performance.
    pub fn try_contains(&self, term: &str) -> Result<bool> {
        // L3.3: the overlay is the sole representation; route membership to the
        // non-faulting lock-free read.
        Ok(self.contains_lockfree(term))
    }

    /// Get a value by term.
    ///
    /// For persistent tries with lazy loading, this will load nodes on-demand.
    /// I/O errors during lazy loading fail closed as `None`. Use `try_get()`
    /// for explicit error handling.
    ///
    /// **F4:** returns an OWNED `Option<V>` (was `Option<&V>`). The owned tree is
    /// now behind the OR `RwLock`, so a `&V` borrow into it can't outlive the read
    /// guard; cloning the value out is the lock-correct (and unsafe-free) shape.
    /// (`get`/`try_get` are deprecation-track readers — `get_value` is canonical;
    /// no in-repo or sibling caller relies on the borrow form.)
    pub fn get(&self, term: &str) -> Option<V>
    where
        V: Clone,
    {
        match self.try_get(term) {
            Ok(result) => result,
            Err(error) => {
                log::warn!("I/O error during lazy loading in get(): {:?}", error);
                None
            }
        }
    }

    /// Term → value as an owned `Option<V>` (unlike `get`'s borrow). The overlay is
    /// the sole representation, so it value-routes to the overlay (the `u64` counter
    /// via `get_lockfree`, or `()` membership) through the SAFE `Any` dispatch in
    /// `overlay_get_value`. This is the canonical value getter the
    /// `MappedDictionary`/`ARTrie` trait bodies delegate to — the
    /// inherent method shadows the trait method of the same name in `.get_value()`
    /// call syntax, so `self.get_value(..)` from a trait body calls THIS (no recursion).
    pub fn get_value(&self, term: &str) -> Option<V> {
        // L3.3c: the overlay is the sole representation; route to the overlay value read
        // (`overlay_get_value` → the shared LockFreeOverlay driver handling the u64
        // counter, `()` membership, AND arbitrary `V`). `Some(inner)` is the answer; an outer
        // `None` (overlay-ineligible `V`) is impossible since the overlay is the sole
        // representation for all `V`, so `.flatten()` (treating the impossible `None` as
        // absent) is exact.
        self.overlay_get_value(term).flatten()
    }

    /// Get a value by term with explicit error handling.
    ///
    /// This version returns a `Result` for lazy loading I/O errors.
    /// For disk-backed tries, prefetches children at each level for improved I/O performance.
    ///
    /// **F4:** returns an OWNED `Result<Option<V>>` (was `Result<Option<&V>>`); see
    /// [`Self::get`].
    pub fn try_get(&self, term: &str) -> Result<Option<V>>
    where
        V: Clone,
    {
        // L3.3: the overlay is the sole representation; route to the overlay value read
        // via the canonical `get_value` (→ `overlay_route_get_value`, the shared
        // `LockFreeOverlay` driver handling the i64/u64 counter, `()` membership, AND
        // arbitrary `V`). The overlay read is non-faulting/infallible, hence `Ok(..)`.
        Ok(self.get_value(term))
    }

    // ==================== Optimistic Concurrency Methods ====================

    /// Try an optimistic read for contains.
    ///
    /// Returns `Some(result)` if the read was consistent, `None` if a concurrent
    /// write occurred and the read should be retried.
    pub fn try_contains_optimistic(&self, term: &str) -> Option<bool> {
        // Record the version before reading
        let guard = OptimisticReadGuard::new(&self.version);

        // Perform the read
        let result = self.contains(term);

        // Validate the version - if it changed, return None to signal retry
        if guard.validate() {
            Some(result)
        } else {
            None
        }
    }

    /// Optimistic contains with automatic retry.
    ///
    /// Retries up to `max_retries` times if concurrent writes occur.
    /// Returns the result if successful within retry limit.
    pub fn contains_optimistic(&self, term: &str, max_retries: usize) -> Option<bool> {
        let mut retries = 0u64;
        for _ in 0..max_retries {
            if let Some(result) = self.try_contains_optimistic(term) {
                self.retry_stats.record_success(retries);
                return Some(result);
            }
            retries += 1;
            std::hint::spin_loop();
        }
        None
    }

    /// Try an optimistic read for get.
    ///
    /// Returns `Some(result)` if the read was consistent, `None` if retry needed.
    /// Note: Returns Option<Option<V>> - outer Option for consistency, inner for value.
    pub fn try_get_optimistic(&self, term: &str) -> Option<Option<V>> {
        let guard = OptimisticReadGuard::new(&self.version);

        // Clone the value if found (to avoid holding reference during validation).
        // D4: value-route via `get_value` (owned `Option<V>`, no borrow) so the
        // optimistic getter reflects the overlay, instead of `get` (which returns
        // `None` under the overlay — the borrow limitation).
        let result = self.get_value(term);

        if guard.validate() {
            Some(result)
        } else {
            None
        }
    }

    /// Optimistic get with automatic retry.
    pub fn get_optimistic(&self, term: &str, max_retries: usize) -> Option<Option<V>> {
        let mut retries = 0u64;
        for _ in 0..max_retries {
            if let Some(result) = self.try_get_optimistic(term) {
                self.retry_stats.record_success(retries);
                return Some(result);
            }
            retries += 1;
            std::hint::spin_loop();
        }
        None
    }

    /// Enter an epoch-protected read section.
    ///
    /// Returns an EpochGuard that must be held while reading. This ensures
    /// memory accessed during the read won't be reclaimed until the guard is dropped.
    pub fn enter_epoch(&self) -> EpochGuard<'_> {
        EpochGuard::new(&self.epoch_manager)
    }

    /// Get the current read epoch.
    pub fn current_epoch(&self) -> u64 {
        self.epoch_manager.current_epoch()
    }

    /// Advance the epoch (should be called periodically by a background task).
    pub fn advance_epoch(&self) -> u64 {
        self.epoch_manager.advance()
    }

    /// Get the number of active readers.
    pub fn active_readers(&self) -> usize {
        self.epoch_manager.active_reader_count()
    }

    /// Get retry statistics snapshot.
    pub fn retry_stats_snapshot(
        &self,
    ) -> crate::persistent_artrie::concurrency::RetryStatsSnapshot {
        self.retry_stats.snapshot()
    }

    /// Check if the trie is currently being written to.
    pub fn is_write_locked(&self) -> bool {
        !self.version.is_stable()
    }

    /// Get the current version (for debugging/monitoring).
    pub fn current_version(&self) -> u64 {
        self.version.get()
    }

    // ==================== End Optimistic Concurrency Methods ====================
}