From d12e0deac71fe05ad3ada45c17854bca21fe7adc Mon Sep 17 00:00:00 2001 From: Philippe Assis Date: Fri, 22 Aug 2025 09:44:22 -0300 Subject: [PATCH 1/6] Refactor cache implementation and remove unused files - Deleted `cache_optimized.rs` which contained the previous cache implementation. - Removed `fast_filters_simd.rs` as it was no longer needed. - Updated `prelude.rs` to re-export `Quickleaf` for easier access. - Changed `string_pool.rs` to use `hashbrown::HashMap` instead of `std::collections::HashMap` for improved performance. --- src/cache_backup.rs | 1101 -------------------------------------- src/cache_optimized.rs | 516 ------------------ src/fast_filters_simd.rs | 1 - src/prelude.rs | 6 +- src/string_pool.rs | 2 +- 5 files changed, 4 insertions(+), 1622 deletions(-) delete mode 100644 src/cache_backup.rs delete mode 100644 src/cache_optimized.rs delete mode 100644 src/fast_filters_simd.rs diff --git a/src/cache_backup.rs b/src/cache_backup.rs deleted file mode 100644 index d62459f..0000000 --- a/src/cache_backup.rs +++ /dev/null @@ -1,1101 +0,0 @@ -use indexmap::IndexMap; -use std::fmt::Debug; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use valu3::traits::ToValueBehavior; -use valu3::value::Value; - -use crate::error::Error; -use crate::event::Event; -use crate::filter::Filter; -use crate::list_props::{ListProps, Order, StartAfter}; -use std::sync::mpsc::Sender; - -#[cfg(feature = "persist")] -use std::path::Path; -#[cfg(feature = "persist")] -use std::sync::mpsc::channel; - -/// Type alias for cache keys. -pub type Key = String; - -/// Represents an item stored in the cache with optional TTL (Time To Live). -/// -/// Each cache item contains: -/// - The actual value stored -/// - Creation timestamp for TTL calculations -/// - Optional TTL duration for automatic expiration -/// -/// # Examples -/// -/// ``` -/// use quickleaf::CacheItem; -/// use quickleaf::valu3::traits::ToValueBehavior; -/// use std::time::Duration; -/// -/// // Create item without TTL -/// let item = CacheItem::new("Hello World".to_value()); -/// assert!(!item.is_expired()); -/// -/// // Create item with TTL -/// let item_with_ttl = CacheItem::with_ttl("temporary".to_value(), Duration::from_secs(60)); -/// assert!(!item_with_ttl.is_expired()); -/// ``` -#[derive(Clone, Debug)] -pub struct CacheItem { - /// The stored value - pub value: Value, - /// When this item was created - pub created_at: SystemTime, - /// Optional TTL duration - pub ttl: Option, -} - -impl CacheItem { - /// Creates a new cache item without TTL. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::CacheItem; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// - /// let item = CacheItem::new("data".to_value()); - /// assert!(!item.is_expired()); - /// assert!(item.ttl.is_none()); - /// ``` - pub fn new(value: Value) -> Self { - Self { - value, - created_at: SystemTime::now(), - ttl: None, - } - } - - /// Creates a new cache item with TTL. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::CacheItem; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// use std::time::Duration; - /// - /// let item = CacheItem::with_ttl("session_data".to_value(), Duration::from_secs(300)); - /// assert!(!item.is_expired()); - /// assert_eq!(item.ttl, Some(Duration::from_secs(300))); - /// ``` - pub fn with_ttl(value: Value, ttl: Duration) -> Self { - Self { - value, - created_at: SystemTime::now(), - ttl: Some(ttl), - } - } - - /// Checks if this cache item has expired based on its TTL. - /// - /// Returns `false` if no TTL is set (permanent item). - /// - /// # Examples - /// - /// ``` - /// use quickleaf::CacheItem; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// use std::time::Duration; - /// use std::thread; - /// - /// // Item without TTL never expires - /// let permanent_item = CacheItem::new("permanent".to_value()); - /// assert!(!permanent_item.is_expired()); - /// - /// // Item with very short TTL - /// let short_lived = CacheItem::with_ttl("temp".to_value(), Duration::from_millis(1)); - /// thread::sleep(Duration::from_millis(10)); - /// assert!(short_lived.is_expired()); - /// ``` - pub fn is_expired(&self) -> bool { - if let Some(ttl) = self.ttl { - self.created_at.elapsed().unwrap_or(Duration::MAX) > ttl - } else { - false - } - } -} - -impl PartialEq for CacheItem { - fn eq(&self, other: &Self) -> bool { - self.value == other.value && self.ttl == other.ttl - } -} - -/// Core cache implementation with LRU eviction, TTL support, and event notifications. -/// -/// This cache provides: -/// - O(1) access time for get/insert operations -/// - LRU (Least Recently Used) eviction when capacity is reached -/// - Optional TTL (Time To Live) for automatic expiration -/// - Event notifications for cache operations -/// - Filtering and ordering capabilities for listing entries -/// -/// # Examples -/// -/// ## Basic Usage -/// -/// ``` -/// use quickleaf::Cache; -/// use quickleaf::valu3::traits::ToValueBehavior; -/// -/// let mut cache = Cache::new(3); -/// cache.insert("key1", "value1"); -/// cache.insert("key2", "value2"); -/// -/// assert_eq!(cache.get("key1"), Some(&"value1".to_value())); -/// assert_eq!(cache.len(), 2); -/// ``` -/// -/// ## With TTL Support -/// -/// ``` -/// use quickleaf::Cache; -/// use quickleaf::valu3::traits::ToValueBehavior; -/// use std::time::Duration; -/// -/// let mut cache = Cache::with_default_ttl(10, Duration::from_secs(60)); -/// cache.insert("session", "user_data"); // Will expire in 60 seconds -/// cache.insert_with_ttl("temp", "data", Duration::from_millis(100)); // Custom TTL -/// -/// assert!(cache.contains_key("session")); -/// ``` -/// -/// ## With Event Notifications -/// -/// ``` -/// use quickleaf::Cache; -/// use quickleaf::Event; -/// use quickleaf::valu3::traits::ToValueBehavior; -/// use std::sync::mpsc::channel; -/// -/// let (tx, rx) = channel(); -/// let mut cache = Cache::with_sender(5, tx); -/// -/// cache.insert("notify", "me"); -/// -/// // Receive the insert event -/// if let Ok(event) = rx.try_recv() { -/// match event { -/// Event::Insert(data) => { -/// assert_eq!(data.key, "notify"); -/// assert_eq!(data.value, "me".to_value()); -/// }, -/// _ => panic!("Expected insert event"), -/// } -/// } -/// ``` -#[derive(Clone, Debug)] -pub struct Cache { - // Using IndexMap to maintain insertion order and get O(1) operations - map: IndexMap, - capacity: usize, - default_ttl: Option, - sender: Option>, - #[cfg(feature = "persist")] - persist_path: Option, -} - -impl PartialEq for Cache { - fn eq(&self, other: &Self) -> bool { - self.map == other.map - && self.capacity == other.capacity - && self.default_ttl == other.default_ttl - } -} - -impl Cache { - /// Creates a new cache with the specified capacity. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::Cache; - /// - /// let cache = Cache::new(100); - /// assert_eq!(cache.capacity(), 100); - /// assert!(cache.is_empty()); - /// ``` - pub fn new(capacity: usize) -> Self { - Self { - map: HashMap::new(), - list: Vec::new(), - capacity, - default_ttl: None, - sender: None, - #[cfg(feature = "persist")] - persist_path: None, - _phantom: std::marker::PhantomData, - } - } - - /// Creates a new cache with event notifications. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::Cache; - /// use quickleaf::Event; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// use std::sync::mpsc::channel; - /// - /// let (tx, rx) = channel(); - /// let mut cache = Cache::with_sender(10, tx); - /// - /// cache.insert("test", 42); - /// - /// // Event should be received - /// assert!(rx.try_recv().is_ok()); - /// ``` - pub fn with_sender(capacity: usize, sender: Sender) -> Self { - Self { - map: HashMap::new(), - list: Vec::new(), - capacity, - default_ttl: None, - sender: Some(sender), - #[cfg(feature = "persist")] - persist_path: None, - _phantom: std::marker::PhantomData, - } - } - - /// Creates a new cache with default TTL for all items. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::Cache; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// use std::time::Duration; - /// - /// let mut cache = Cache::with_default_ttl(10, Duration::from_secs(300)); - /// cache.insert("auto_expire", "data"); - /// - /// assert_eq!(cache.get_default_ttl(), Some(Duration::from_secs(300))); - /// ``` - pub fn with_default_ttl(capacity: usize, default_ttl: Duration) -> Self { - Self { - map: HashMap::new(), - list: Vec::new(), - capacity, - default_ttl: Some(default_ttl), - sender: None, - #[cfg(feature = "persist")] - persist_path: None, - _phantom: std::marker::PhantomData, - } - } - - /// Creates a new cache with both event notifications and default TTL. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::Cache; - /// use quickleaf::Event; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// use std::sync::mpsc::channel; - /// use std::time::Duration; - /// - /// let (tx, rx) = channel(); - /// let mut cache = Cache::with_sender_and_ttl(10, tx, Duration::from_secs(60)); - /// - /// cache.insert("monitored", "data"); - /// assert!(rx.try_recv().is_ok()); - /// assert_eq!(cache.get_default_ttl(), Some(Duration::from_secs(60))); - /// ``` - pub fn with_sender_and_ttl( - capacity: usize, - sender: Sender, - default_ttl: Duration, - ) -> Self { - Self { - map: HashMap::new(), - list: Vec::new(), - capacity, - default_ttl: Some(default_ttl), - sender: Some(sender), - #[cfg(feature = "persist")] - persist_path: None, - _phantom: std::marker::PhantomData, - } - } - - /// Creates a new cache with SQLite persistence. - /// - /// This constructor enables automatic persistence of all cache operations to a SQLite database. - /// On initialization, it will load any existing data from the database. - /// - /// # Examples - /// - /// ```no_run - /// # #[cfg(feature = "persist")] - /// # { - /// use quickleaf::Cache; - /// - /// let mut cache = Cache::with_persist("data/cache.db", 1000).unwrap(); - /// cache.insert("persistent_key", "persistent_value"); - /// # } - /// ``` - #[cfg(feature = "persist")] - pub fn with_persist>( - path: P, - capacity: usize, - ) -> Result> { - use crate::sqlite_store::{ensure_db_file, items_from_db, spawn_writer, PersistentEvent}; - - let path = path.as_ref().to_path_buf(); - - // Ensure the database file and directories exist - ensure_db_file(&path)?; - - // Create channels for event handling - let (event_tx, event_rx) = channel(); - let (persist_tx, persist_rx) = channel(); - - // Spawn the SQLite writer thread - spawn_writer(path.clone(), persist_rx); - - // Create the cache with event sender - let mut cache = Self::with_sender(capacity, event_tx); - cache.persist_path = Some(path.clone()); - - // Set up event forwarding to SQLite writer - std::thread::spawn(move || { - while let Ok(event) = event_rx.recv() { - let persistent_event = PersistentEvent::new(event.clone()); - if persist_tx.send(persistent_event).is_err() { - break; - } - } - }); - - // Load existing data from database - let items = items_from_db(&path)?; - for (key, item) in items { - // Directly insert into the map and list to avoid triggering events - if cache.map.len() < capacity { - let position = cache - .list - .iter() - .position(|k| k > &key) - .unwrap_or(cache.list.len()); - cache.list.insert(position, key.clone()); - cache.map.insert(key, item); - } - } - - Ok(cache) - } - - /// Creates a new cache with SQLite persistence and event notifications. - /// - /// This constructor combines SQLite persistence with custom event notifications. - /// You'll receive events for cache operations while data is also persisted to SQLite. - /// - /// # Arguments - /// - /// * `path` - Path to the SQLite database file - /// * `capacity` - Maximum number of items the cache can hold - /// * `sender` - Channel sender for event notifications - /// - /// # Examples - /// - /// ```no_run - /// # #[cfg(feature = "persist")] - /// # { - /// use quickleaf::Cache; - /// use std::sync::mpsc::channel; - /// - /// let (tx, rx) = channel(); - /// let mut cache = Cache::with_persist_and_sender("data/cache.db", 1000, tx).unwrap(); - /// - /// cache.insert("key", "value"); - /// - /// // Receive events for persisted operations - /// for event in rx.try_iter() { - /// println!("Event: {:?}", event); - /// } - /// # } - /// ``` - #[cfg(feature = "persist")] - pub fn with_persist_and_sender>( - path: P, - capacity: usize, - external_sender: Sender, - ) -> Result> { - use crate::sqlite_store::{ensure_db_file, items_from_db, spawn_writer, PersistentEvent}; - - let path = path.as_ref().to_path_buf(); - - // Ensure the database file and directories exist - ensure_db_file(&path)?; - - // Create channels for internal event handling - let (event_tx, event_rx) = channel(); - let (persist_tx, persist_rx) = channel(); - - // Spawn the SQLite writer thread - spawn_writer(path.clone(), persist_rx); - - // Create the cache with event sender - let mut cache = Self::with_sender(capacity, event_tx); - cache.persist_path = Some(path.clone()); - - // Set up event forwarding to both SQLite writer and external sender - std::thread::spawn(move || { - while let Ok(event) = event_rx.recv() { - // Forward to external sender - let _ = external_sender.send(event.clone()); - - // Forward to SQLite writer - let persistent_event = PersistentEvent::new(event); - if persist_tx.send(persistent_event).is_err() { - break; - } - } - }); - - // Load existing data from database - let items = items_from_db(&path)?; - for (key, item) in items { - // Directly insert into the map and list to avoid triggering events - if cache.map.len() < capacity { - let position = cache - .list - .iter() - .position(|k| k > &key) - .unwrap_or(cache.list.len()); - cache.list.insert(position, key.clone()); - cache.map.insert(key, item); - } - } - - Ok(cache) - } - - /// Creates a new cache with SQLite persistence and default TTL. - /// - /// This constructor combines SQLite persistence with a default TTL for all cache items. - /// Items will automatically expire after the specified duration and are persisted to SQLite. - /// - /// # Arguments - /// - /// * `path` - Path to the SQLite database file - /// * `capacity` - Maximum number of items the cache can hold - /// * `default_ttl` - Default time-to-live for all cache items - /// - /// # Examples - /// - /// ```no_run - /// # #[cfg(feature = "persist")] - /// # { - /// use quickleaf::Cache; - /// use std::time::Duration; - /// - /// let mut cache = Cache::with_persist_and_ttl( - /// "data/cache.db", - /// 1000, - /// Duration::from_secs(3600) - /// ).unwrap(); - /// cache.insert("session", "data"); // Will expire in 1 hour and be persisted - /// # } - /// ``` - #[cfg(feature = "persist")] - pub fn with_persist_and_ttl>( - path: P, - capacity: usize, - default_ttl: Duration, - ) -> Result> { - use crate::sqlite_store::{ensure_db_file, items_from_db, spawn_writer, PersistentEvent}; - - let path = path.as_ref().to_path_buf(); - - // Ensure the database file and directories exist - ensure_db_file(&path)?; - - // Create channels for event handling - let (event_tx, event_rx) = channel(); - let (persist_tx, persist_rx) = channel(); - - // Spawn the SQLite writer thread - spawn_writer(path.clone(), persist_rx); - - // Create the cache with event sender and TTL - let mut cache = Self::with_sender_and_ttl(capacity, event_tx, default_ttl); - cache.persist_path = Some(path.clone()); - - // Set up event forwarding to SQLite writer - std::thread::spawn(move || { - while let Ok(event) = event_rx.recv() { - let persistent_event = PersistentEvent::new(event.clone()); - if persist_tx.send(persistent_event).is_err() { - break; - } - } - }); - - // Load existing data from database - let items = items_from_db(&path)?; - for (key, item) in items { - // Skip expired items during load - if !item.is_expired() && cache.map.len() < capacity { - let position = cache - .list - .iter() - .position(|k| k > &key) - .unwrap_or(cache.list.len()); - cache.list.insert(position, key.clone()); - cache.map.insert(key, item); - } - } - - Ok(cache) - } - - /// Creates a new cache with SQLite persistence, event notifications, and default TTL. - /// - /// This constructor combines all persistence features: SQLite storage, event notifications, - /// and default TTL for all cache items. This is the most feature-complete constructor. - /// - /// # Arguments - /// - /// * `path` - Path to the SQLite database file - /// * `capacity` - Maximum number of items the cache can hold - /// * `external_sender` - Channel sender for event notifications - /// * `default_ttl` - Default time-to-live for all cache items - /// - /// # Examples - /// - /// ```no_run - /// # #[cfg(feature = "persist")] - /// # { - /// use quickleaf::Cache; - /// use std::sync::mpsc::channel; - /// use std::time::Duration; - /// - /// let (tx, rx) = channel(); - /// let mut cache = Cache::with_persist_and_sender_and_ttl( - /// "data/cache.db", - /// 1000, - /// tx, - /// Duration::from_secs(3600) - /// ).unwrap(); - /// - /// // Insert data - it will be persisted, send events, and expire in 1 hour - /// cache.insert("session", "user_data"); - /// - /// // Receive events - /// for event in rx.try_iter() { - /// println!("Event: {:?}", event); - /// } - /// # } - /// ``` - #[cfg(feature = "persist")] - pub fn with_persist_and_sender_and_ttl>( - path: P, - capacity: usize, - external_sender: Sender, - default_ttl: Duration, - ) -> Result> { - use crate::sqlite_store::{ensure_db_file, items_from_db, spawn_writer, PersistentEvent}; - - let path = path.as_ref().to_path_buf(); - - // Ensure the database file and directories exist - ensure_db_file(&path)?; - - // Create channels for internal event handling - let (event_tx, event_rx) = channel(); - let (persist_tx, persist_rx) = channel(); - - // Spawn the SQLite writer thread - spawn_writer(path.clone(), persist_rx); - - // Create the cache with event sender and TTL - let mut cache = Self::with_sender_and_ttl(capacity, event_tx, default_ttl); - cache.persist_path = Some(path.clone()); - - // Set up event forwarding to both SQLite writer and external sender - std::thread::spawn(move || { - while let Ok(event) = event_rx.recv() { - // Forward to external sender - let _ = external_sender.send(event.clone()); - - // Forward to SQLite writer - let persistent_event = PersistentEvent::new(event); - if persist_tx.send(persistent_event).is_err() { - break; - } - } - }); - - // Load existing data from database - let items = items_from_db(&path)?; - for (key, item) in items { - // Skip expired items during load - if !item.is_expired() && cache.map.len() < capacity { - let position = cache - .list - .iter() - .position(|k| k > &key) - .unwrap_or(cache.list.len()); - cache.list.insert(position, key.clone()); - cache.map.insert(key, item); - } - } - - Ok(cache) - } - - pub fn set_event(&mut self, sender: Sender) { - self.sender = Some(sender); - } - - pub fn remove_event(&mut self) { - self.sender = None; - } - - fn send_insert(&self, key: Key, value: Value) { - if let Some(sender) = &self.sender { - let event = Event::insert(key, value); - sender.send(event).unwrap(); - } - } - - fn send_remove(&self, key: Key, value: Value) { - if let Some(sender) = &self.sender { - let event = Event::remove(key, value); - sender.send(event).unwrap(); - } - } - - fn send_clear(&self) { - if let Some(sender) = &self.sender { - let event = Event::clear(); - sender.send(event).unwrap(); - } - } - - /// Inserts a key-value pair into the cache. - /// - /// If the cache is at capacity, the least recently used item will be evicted. - /// If a default TTL is set, the item will inherit that TTL. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::Cache; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// - /// let mut cache = Cache::new(2); - /// cache.insert("key1", "value1"); - /// cache.insert("key2", "value2"); - /// cache.insert("key3", "value3"); // This will evict "key1" - /// - /// assert_eq!(cache.get("key1"), None); // Evicted - /// assert_eq!(cache.get("key2"), Some(&"value2".to_value())); - /// assert_eq!(cache.get("key3"), Some(&"value3".to_value())); - /// ``` - pub fn insert(&mut self, key: T, value: V) - where - T: Into + Clone + AsRef, - V: ToValueBehavior, - { - let key = key.into(); - let item = if let Some(default_ttl) = self.default_ttl { - CacheItem::with_ttl(value.to_value(), default_ttl) - } else { - CacheItem::new(value.to_value()) - }; - - if let Some(existing_item) = self.map.get(&key) { - if existing_item.value == item.value { - return; - } - } - - if self.map.len() != 0 && self.map.len() == self.capacity { - let first_key = self.list.remove(0); - let data = self.map.get(&first_key).unwrap().clone(); - self.map.remove(&first_key); - self.send_remove(first_key, data.value); - } - - let position = self - .list - .iter() - .position(|k| k > &key) - .unwrap_or(self.list.len()); - - self.list.insert(position, key.clone()); - self.map.insert(key.clone(), item.clone()); - - self.send_insert(key, item.value); - } - - /// Inserts a key-value pair with a specific TTL. - /// - /// The TTL overrides any default TTL set for the cache. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::Cache; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// use std::time::Duration; - /// use std::thread; - /// - /// let mut cache = Cache::new(10); - /// cache.insert_with_ttl("session", "user123", Duration::from_millis(100)); - /// - /// assert!(cache.contains_key("session")); - /// thread::sleep(Duration::from_millis(150)); - /// assert!(!cache.contains_key("session")); // Should be expired - /// ``` - pub fn insert_with_ttl(&mut self, key: T, value: V, ttl: Duration) - where - T: Into + Clone + AsRef, - V: ToValueBehavior, - { - let key = key.into(); - let item = CacheItem::with_ttl(value.to_value(), ttl); - - if let Some(existing_item) = self.map.get(&key) { - if existing_item.value == item.value { - return; - } - } - - if self.map.len() != 0 && self.map.len() == self.capacity { - let first_key = self.list.remove(0); - let data = self.map.get(&first_key).unwrap().clone(); - self.map.remove(&first_key); - self.send_remove(first_key, data.value); - } - - let position = self - .list - .iter() - .position(|k| k > &key) - .unwrap_or(self.list.len()); - - self.list.insert(position, key.clone()); - self.map.insert(key.clone(), item.clone()); - - self.send_insert(key.clone(), item.value.clone()); - - // Update TTL in SQLite if we have persistence - #[cfg(feature = "persist")] - if let Some(persist_path) = &self.persist_path { - if let Some(ttl_secs) = item.ttl { - let _ = crate::sqlite_store::persist_item_with_ttl( - persist_path, - &key, - &item.value, - ttl_secs.as_secs(), - ); - } - } - } - - /// Retrieves a value from the cache by key. - /// - /// Returns `None` if the key doesn't exist or if the item has expired. - /// Expired items are automatically removed during this operation (lazy cleanup). - /// - /// # Examples - /// - /// ``` - /// use quickleaf::Cache; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// - /// let mut cache = Cache::new(10); - /// cache.insert("existing", "data"); - /// - /// assert_eq!(cache.get("existing"), Some(&"data".to_value())); - /// assert_eq!(cache.get("nonexistent"), None); - /// ``` - pub fn get(&mut self, key: &str) -> Option<&Value> { - // Primeiro verifica se existe e se está expirado - let is_expired = if let Some(item) = self.map.get(key) { - item.is_expired() - } else { - return None; - }; - - if is_expired { - // Item expirado, remove do cache - self.remove(key).ok(); - None - } else { - // Item válido, retorna referência - self.map.get(key).map(|item| &item.value) - } - } - - pub fn get_list(&self) -> &Vec { - &self.list - } - - pub fn get_map(&self) -> HashMap { - self.map - .iter() - .filter(|(_, item)| !item.is_expired()) - .map(|(key, item)| (key.clone(), &item.value)) - .collect() - } - - pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> { - // Primeiro verifica se existe e se está expirado - let is_expired = if let Some(item) = self.map.get(key) { - item.is_expired() - } else { - return None; - }; - - if is_expired { - // Item expirado, remove do cache - self.remove(key).ok(); - None - } else { - // Item válido, retorna referência mutável - self.map.get_mut(key).map(|item| &mut item.value) - } - } - - pub fn capacity(&self) -> usize { - self.capacity - } - - pub fn set_capacity(&mut self, capacity: usize) { - self.capacity = capacity; - } - - pub fn remove(&mut self, key: &str) -> Result<(), Error> { - match self.list.iter().position(|k| k == &key) { - Some(position) => { - self.list.remove(position); - - let data = self.map.get(key).unwrap().clone(); - - self.map.remove(key); - - self.send_remove(key.to_string(), data.value); - - Ok(()) - } - None => Err(Error::KeyNotFound), - } - } - - pub fn clear(&mut self) { - self.map.clear(); - self.list.clear(); - - self.send_clear(); - } - - pub fn len(&self) -> usize { - self.map.len() - } - - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - - /// Checks if a key exists in the cache and hasn't expired. - /// - /// This method performs lazy cleanup of expired items. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::Cache; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// use std::time::Duration; - /// - /// let mut cache = Cache::new(10); - /// cache.insert("key", "value"); - /// - /// assert!(cache.contains_key("key")); - /// assert!(!cache.contains_key("nonexistent")); - /// - /// // Test with TTL - /// cache.insert_with_ttl("temp", "data", Duration::from_millis(1)); - /// std::thread::sleep(Duration::from_millis(10)); - /// assert!(!cache.contains_key("temp")); // Should be expired and removed - /// ``` - pub fn contains_key(&mut self, key: &str) -> bool { - if let Some(item) = self.map.get(key) { - if item.is_expired() { - self.remove(key).ok(); - false - } else { - true - } - } else { - false - } - } - - /// Manually removes all expired items from the cache. - /// - /// Returns the number of items that were removed. - /// This is useful for proactive cleanup, though the cache also performs lazy cleanup. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::Cache; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// use std::time::Duration; - /// use std::thread; - /// - /// let mut cache = Cache::new(10); - /// cache.insert_with_ttl("temp1", "data1", Duration::from_millis(10)); - /// cache.insert_with_ttl("temp2", "data2", Duration::from_millis(10)); - /// cache.insert("permanent", "data"); - /// - /// thread::sleep(Duration::from_millis(20)); - /// - /// let removed = cache.cleanup_expired(); - /// assert_eq!(removed, 2); // temp1 and temp2 were removed - /// assert_eq!(cache.len(), 1); // Only permanent remains - /// ``` - pub fn cleanup_expired(&mut self) -> usize { - let expired_keys: Vec<_> = self - .map - .iter() - .filter(|(_, item)| item.is_expired()) - .map(|(key, _)| key.clone()) - .collect(); - - let count = expired_keys.len(); - for key in expired_keys { - self.remove(&key).ok(); - } - count - } - - pub fn set_default_ttl(&mut self, ttl: Option) { - self.default_ttl = ttl; - } - - pub fn get_default_ttl(&self) -> Option { - self.default_ttl - } - - /// Lists cache entries with filtering, ordering, and pagination support. - /// - /// This method automatically cleans up expired items before returning results. - /// - /// # Examples - /// - /// ``` - /// use quickleaf::Cache; - /// use quickleaf::{ListProps, Order}; - /// use quickleaf::Filter; - /// use quickleaf::valu3::traits::ToValueBehavior; - /// - /// let mut cache = Cache::new(10); - /// cache.insert("apple", 1); - /// cache.insert("banana", 2); - /// cache.insert("apricot", 3); - /// - /// // List all items in ascending order - /// let props = ListProps::default().order(Order::Asc); - /// let items = cache.list(props).unwrap(); - /// assert_eq!(items.len(), 3); - /// - /// // Filter items starting with "ap" - /// let props = ListProps::default() - /// .filter(Filter::StartWith("ap".to_string())); - /// let filtered = cache.list(props).unwrap(); - /// assert_eq!(filtered.len(), 2); // apple, apricot - /// ``` - pub fn list(&mut self, props: T) -> Result, Error> - where - T: Into, - { - let props = props.into(); - - // Primeiro faz uma limpeza dos itens expirados para evitar retorná-los - self.cleanup_expired(); - - match props.order { - Order::Asc => self.resolve_order(self.list.iter(), props), - Order::Desc => self.resolve_order(self.list.iter().rev(), props), - } - } - - fn resolve_order<'a, I>( - &self, - mut list_iter: I, - props: ListProps, - ) -> Result, Error> - where - I: Iterator, - { - if let StartAfter::Key(ref key) = props.start_after_key { - list_iter - .find(|k| k == &key) - .ok_or(Error::SortKeyNotFound)?; - } - - let mut list = Vec::new(); - let mut count = 0; - - for k in list_iter { - if let Some(item) = self.map.get(k) { - // Pula itens expirados (eles serão removidos na próxima limpeza) - if item.is_expired() { - continue; - } - - let filtered = match props.filter { - Filter::StartWith(ref key) => { - if k.starts_with(key) { - Some((k.clone(), &item.value)) - } else { - None - } - } - Filter::EndWith(ref key) => { - if k.ends_with(key) { - Some((k.clone(), &item.value)) - } else { - None - } - } - Filter::StartAndEndWith(ref start_key, ref end_key) => { - if k.starts_with(start_key) && k.ends_with(end_key) { - Some((k.clone(), &item.value)) - } else { - None - } - } - Filter::None => Some((k.clone(), &item.value)), - }; - - if let Some(item) = filtered { - list.push(item); - count += 1; - if count == props.limit { - break; - } - } - } - } - - Ok(list) - } -} diff --git a/src/cache_optimized.rs b/src/cache_optimized.rs deleted file mode 100644 index 4af603d..0000000 --- a/src/cache_optimized.rs +++ /dev/null @@ -1,516 +0,0 @@ -use indexmap::IndexMap; -use std::fmt::Debug; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use valu3::traits::ToValueBehavior; -use valu3::value::Value; - -use crate::error::Error; -use crate::event::Event; -use crate::filter::Filter; -use crate::list_props::{ListProps, Order, StartAfter}; -use std::sync::mpsc::Sender; - -#[cfg(feature = "persist")] -use std::path::Path; -#[cfg(feature = "persist")] -use std::sync::mpsc::channel; - -/// Type alias for cache keys. -pub type Key = String; - -/// Helper function to get current time in milliseconds since UNIX_EPOCH -#[inline(always)] -fn current_time_millis() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or(Duration::ZERO) - .as_millis() as u64 -} - -/// Optimized cache item with integer timestamps for faster TTL checks -#[derive(Clone, Debug)] -pub struct CacheItem { - /// The stored value - pub value: Value, - /// When this item was created (millis since epoch) - pub created_at: u64, - /// Optional TTL in milliseconds - pub ttl_millis: Option, -} - -impl CacheItem { - #[inline] - pub fn new(value: Value) -> Self { - Self { - value, - created_at: current_time_millis(), - ttl_millis: None, - } - } - - #[inline] - pub fn with_ttl(value: Value, ttl: Duration) -> Self { - Self { - value, - created_at: current_time_millis(), - ttl_millis: Some(ttl.as_millis() as u64), - } - } - - #[inline(always)] - pub fn is_expired(&self) -> bool { - if let Some(ttl) = self.ttl_millis { - (current_time_millis() - self.created_at) > ttl - } else { - false - } - } - - /// Convert back to SystemTime for compatibility - pub fn created_at_time(&self) -> SystemTime { - UNIX_EPOCH + Duration::from_millis(self.created_at) - } - - /// Get TTL as Duration for compatibility - pub fn ttl(&self) -> Option { - self.ttl_millis.map(Duration::from_millis) - } -} - -impl PartialEq for CacheItem { - fn eq(&self, other: &Self) -> bool { - self.value == other.value && self.ttl_millis == other.ttl_millis - } -} - -/// Optimized cache using IndexMap for O(1) operations with maintained insertion order -#[derive(Clone, Debug)] -pub struct Cache { - // IndexMap maintains insertion order and provides O(1) for all operations - map: IndexMap, - capacity: usize, - default_ttl: Option, - sender: Option>, - #[cfg(feature = "persist")] - persist_path: Option, -} - -impl PartialEq for Cache { - fn eq(&self, other: &Self) -> bool { - self.map == other.map - && self.capacity == other.capacity - && self.default_ttl == other.default_ttl - } -} - -impl Cache { - /// Creates a new cache with the specified capacity - #[inline] - pub fn new(capacity: usize) -> Self { - Self { - map: IndexMap::with_capacity(capacity), - capacity, - default_ttl: None, - sender: None, - #[cfg(feature = "persist")] - persist_path: None, - } - } - - /// Creates a new cache with event notifications - pub fn with_sender(capacity: usize, sender: Sender) -> Self { - Self { - map: IndexMap::with_capacity(capacity), - capacity, - default_ttl: None, - sender: Some(sender), - #[cfg(feature = "persist")] - persist_path: None, - } - } - - /// Creates a new cache with default TTL for all items - pub fn with_default_ttl(capacity: usize, default_ttl: Duration) -> Self { - Self { - map: IndexMap::with_capacity(capacity), - capacity, - default_ttl: Some(default_ttl), - sender: None, - #[cfg(feature = "persist")] - persist_path: None, - } - } - - /// Creates a new cache with both event notifications and default TTL - pub fn with_sender_and_ttl( - capacity: usize, - sender: Sender, - default_ttl: Duration, - ) -> Self { - Self { - map: IndexMap::with_capacity(capacity), - capacity, - default_ttl: Some(default_ttl), - sender: Some(sender), - #[cfg(feature = "persist")] - persist_path: None, - } - } - - #[cfg(feature = "persist")] - pub fn with_persist>( - path: P, - capacity: usize, - ) -> Result> { - use crate::sqlite_store::{ensure_db_file, items_from_db, spawn_writer, PersistentEvent}; - - let path = path.as_ref().to_path_buf(); - ensure_db_file(&path)?; - - let (event_tx, event_rx) = channel(); - let (persist_tx, persist_rx) = channel(); - - spawn_writer(path.clone(), persist_rx); - - let mut cache = Self::with_sender(capacity, event_tx); - cache.persist_path = Some(path.clone()); - - std::thread::spawn(move || { - while let Ok(event) = event_rx.recv() { - let persistent_event = PersistentEvent::new(event.clone()); - if persist_tx.send(persistent_event).is_err() { - break; - } - } - }); - - // Load existing data - convert old format to new - let items = items_from_db(&path)?; - for (key, old_item) in items { - if cache.map.len() < capacity { - // Convert old CacheItem to new format - let new_item = CacheItem { - value: old_item.value, - created_at: old_item.created_at - .duration_since(UNIX_EPOCH) - .unwrap_or(Duration::ZERO) - .as_millis() as u64, - ttl_millis: old_item.ttl.map(|d| d.as_millis() as u64), - }; - cache.map.insert(key, new_item); - } - } - - // Sort by keys to maintain order - cache.map.sort_keys(); - - Ok(cache) - } - - pub fn set_event(&mut self, sender: Sender) { - self.sender = Some(sender); - } - - pub fn remove_event(&mut self) { - self.sender = None; - } - - #[inline] - fn send_insert(&self, key: Key, value: Value) { - if let Some(sender) = &self.sender { - let _ = sender.send(Event::insert(key, value)); - } - } - - #[inline] - fn send_remove(&self, key: Key, value: Value) { - if let Some(sender) = &self.sender { - let _ = sender.send(Event::remove(key, value)); - } - } - - #[inline] - fn send_clear(&self) { - if let Some(sender) = &self.sender { - let _ = sender.send(Event::clear()); - } - } - - /// Optimized insert with IndexMap - maintains sorted order automatically - pub fn insert(&mut self, key: T, value: V) - where - T: Into + Clone + AsRef, - V: ToValueBehavior, - { - let key = key.into(); - let item = if let Some(default_ttl) = self.default_ttl { - CacheItem::with_ttl(value.to_value(), default_ttl) - } else { - CacheItem::new(value.to_value()) - }; - - // Check if value is the same - if let Some(existing_item) = self.map.get(&key) { - if existing_item.value == item.value { - return; - } - } - - // LRU eviction if at capacity - if self.map.len() >= self.capacity && !self.map.contains_key(&key) { - // Remove first (oldest) item - O(1) with IndexMap! - if let Some((removed_key, removed_item)) = self.map.shift_remove_index(0) { - self.send_remove(removed_key, removed_item.value); - } - } - - // Insert and sort to maintain order - let old = self.map.insert(key.clone(), item.clone()); - - // Sort keys to maintain alphabetical order - self.map.sort_keys(); - - if old.is_none() { - self.send_insert(key, item.value); - } - } - - pub fn insert_with_ttl(&mut self, key: T, value: V, ttl: Duration) - where - T: Into + Clone + AsRef, - V: ToValueBehavior, - { - let key = key.into(); - let item = CacheItem::with_ttl(value.to_value(), ttl); - - if let Some(existing_item) = self.map.get(&key) { - if existing_item.value == item.value { - return; - } - } - - // LRU eviction if at capacity - if self.map.len() >= self.capacity && !self.map.contains_key(&key) { - if let Some((removed_key, removed_item)) = self.map.shift_remove_index(0) { - self.send_remove(removed_key, removed_item.value); - } - } - - let old = self.map.insert(key.clone(), item.clone()); - self.map.sort_keys(); - - if old.is_none() { - self.send_insert(key.clone(), item.value.clone()); - } - - #[cfg(feature = "persist")] - if let Some(persist_path) = &self.persist_path { - if let Some(ttl_millis) = item.ttl_millis { - let _ = crate::sqlite_store::persist_item_with_ttl( - persist_path, - &key, - &item.value, - ttl_millis / 1000, - ); - } - } - } - - /// Batch insert for better performance - pub fn insert_batch(&mut self, items: I) - where - I: IntoIterator, - T: Into + Clone + AsRef, - V: ToValueBehavior, - { - for (key, value) in items { - self.insert(key, value); - } - } - - #[inline] - pub fn get(&mut self, key: &str) -> Option<&Value> { - match self.map.get(key) { - Some(item) if item.is_expired() => { - self.remove(key).ok(); - None - } - Some(item) => Some(&item.value), - None => None, - } - } - - pub fn get_list(&self) -> Vec<&Key> { - self.map.keys().collect() - } - - pub fn get_map(&self) -> IndexMap { - self.map - .iter() - .filter(|(_, item)| !item.is_expired()) - .map(|(key, item)| (key.clone(), &item.value)) - .collect() - } - - #[inline] - pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> { - match self.map.get(key) { - Some(item) if item.is_expired() => { - self.remove(key).ok(); - None - } - _ => self.map.get_mut(key).map(|item| &mut item.value), - } - } - - #[inline(always)] - pub fn capacity(&self) -> usize { - self.capacity - } - - pub fn set_capacity(&mut self, capacity: usize) { - self.capacity = capacity; - } - - /// Optimized remove - O(1) with IndexMap! - pub fn remove(&mut self, key: &str) -> Result<(), Error> { - match self.map.swap_remove_entry(key) { - Some((key, item)) => { - self.send_remove(key, item.value); - Ok(()) - } - None => Err(Error::KeyNotFound), - } - } - - /// Batch remove for better performance - pub fn remove_batch<'a, I>(&mut self, keys: I) -> usize - where - I: IntoIterator, - { - let mut removed = 0; - for key in keys { - if self.remove(key).is_ok() { - removed += 1; - } - } - removed - } - - pub fn clear(&mut self) { - self.map.clear(); - self.send_clear(); - } - - #[inline(always)] - pub fn len(&self) -> usize { - self.map.len() - } - - #[inline(always)] - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - - #[inline] - pub fn contains_key(&mut self, key: &str) -> bool { - match self.map.get(key) { - Some(item) if item.is_expired() => { - self.remove(key).ok(); - false - } - Some(_) => true, - None => false, - } - } - - /// Optimized cleanup using retain - pub fn cleanup_expired(&mut self) -> usize { - let initial_len = self.map.len(); - - // Collect expired keys - let expired_keys: Vec = self.map - .iter() - .filter(|(_, item)| item.is_expired()) - .map(|(k, _)| k.clone()) - .collect(); - - // Remove expired items - for key in &expired_keys { - if let Some(item) = self.map.swap_remove(key) { - self.send_remove(key.clone(), item.value); - } - } - - initial_len - self.map.len() - } - - pub fn set_default_ttl(&mut self, ttl: Option) { - self.default_ttl = ttl; - } - - pub fn get_default_ttl(&self) -> Option { - self.default_ttl - } - - /// Optimized list with pre-allocated capacity - pub fn list(&mut self, props: T) -> Result, Error> - where - T: Into, - { - let props = props.into(); - - // Cleanup expired first - self.cleanup_expired(); - - // Pre-allocate result vector - let mut result = Vec::with_capacity(props.limit.min(self.map.len())); - - let iter: Box> = match props.order { - Order::Asc => Box::new(self.map.iter()), - Order::Desc => Box::new(self.map.iter().rev()), - }; - - // Handle start_after - let mut iter = iter; - if let StartAfter::Key(ref start_key) = props.start_after_key { - // Skip until we find the start key - let mut found = false; - for (k, _) in iter.by_ref() { - if k == start_key { - found = true; - break; - } - } - if !found { - return Err(Error::SortKeyNotFound); - } - } - - // Apply filters and collect results - for (key, item) in iter { - if item.is_expired() { - continue; - } - - let matches = match &props.filter { - Filter::None => true, - Filter::StartWith(prefix) => key.starts_with(prefix), - Filter::EndWith(suffix) => key.ends_with(suffix), - Filter::StartAndEndWith(prefix, suffix) => { - key.starts_with(prefix) && key.ends_with(suffix) - } - }; - - if matches { - result.push((key.clone(), &item.value)); - if result.len() >= props.limit { - break; - } - } - } - - Ok(result) - } -} diff --git a/src/fast_filters_simd.rs b/src/fast_filters_simd.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/fast_filters_simd.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/prelude.rs b/src/prelude.rs index 056f840..898f39a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,21 +3,21 @@ //! This module re-exports commonly used items to make them easier to import. //! Import this module to get access to the most frequently used traits and types. +pub use crate::Quickleaf; /// Re-exports from the valu3 library for convenient access to value conversion traits. /// /// # Examples /// /// ``` /// use quickleaf::prelude::*; -/// use quickleaf::Quickleaf; /// /// let mut cache = Quickleaf::new(10); -/// +/// /// // ToValueBehavior trait is available from the prelude /// cache.insert("number", 42); /// cache.insert("string", "hello"); /// cache.insert("boolean", true); -/// +/// /// assert_eq!(cache.get("number"), Some(&42.to_value())); /// assert_eq!(cache.get("string"), Some(&"hello".to_value())); /// assert_eq!(cache.get("boolean"), Some(&true.to_value())); diff --git a/src/string_pool.rs b/src/string_pool.rs index 3335d53..c27b007 100644 --- a/src/string_pool.rs +++ b/src/string_pool.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use hashbrown::HashMap; use std::sync::Arc; /// String pool para reutilizar strings e reduzir alocações From 702505adb234bf302f6cd7ff814a7b676ca9b150 Mon Sep 17 00:00:00 2001 From: Philippe Assis Date: Fri, 22 Aug 2025 09:53:37 -0300 Subject: [PATCH 2/6] Refactor code for improved readability and consistency - Cleaned up comments and whitespace in error handling and filter implementations. - Updated documentation comments to enhance clarity in various modules. - Adjusted test cases to remove unnecessary comments and improve focus on assertions. - Standardized formatting across multiple files, including string pool and prefetch implementations. - Ensured consistency in handling of TTL and cache operations in tests. - Updated version references in documentation to reflect the latest version. --- src/cache.rs | 150 +++++++++++++++++++++---------------------- src/error.rs | 12 ++-- src/fast_filters.rs | 10 +-- src/filter.rs | 6 +- src/lib.rs | 48 +++++++------- src/list_props.rs | 12 ++-- src/persist_tests.rs | 57 ++-------------- src/prefetch.rs | 14 ++-- src/quickleaf.rs | 4 +- src/sqlite_store.rs | 22 +------ src/string_pool.rs | 2 +- src/ttl_tests.rs | 63 ++++++++---------- 12 files changed, 162 insertions(+), 238 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index fb81b1a..675c48e 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -188,8 +188,8 @@ impl PartialEq for CacheItem { /// use std::time::Duration; /// /// let mut cache = Cache::with_default_ttl(10, Duration::from_secs(60)); -/// cache.insert("session", "user_data"); // Will expire in 60 seconds -/// cache.insert_with_ttl("temp", "data", Duration::from_millis(100)); // Custom TTL +/// cache.insert("session", "user_data"); +/// cache.insert_with_ttl("temp", "data", Duration::from_millis(100)); /// /// assert!(cache.contains_key("session")); /// ``` @@ -381,21 +381,21 @@ impl Cache { let path = path.as_ref().to_path_buf(); - // Ensure the database file and directories exist + ensure_db_file(&path)?; - // Create channels for event handling + let (event_tx, event_rx) = channel(); let (persist_tx, persist_rx) = channel(); - // Spawn the SQLite writer thread + spawn_writer(path.clone(), persist_rx); - // Create the cache with event sender + let mut cache = Self::with_sender(capacity, event_tx); cache.persist_path = Some(path.clone()); - // Set up event forwarding to SQLite writer + std::thread::spawn(move || { while let Ok(event) = event_rx.recv() { let persistent_event = PersistentEvent::new(event.clone()); @@ -405,13 +405,13 @@ impl Cache { } }); - // Load existing data from database + let mut items = items_from_db(&path)?; - // Sort items by key to maintain alphabetical order + items.sort_by(|a, b| a.0.cmp(&b.0)); for (key, item) in items { - // Directly insert into the map to avoid triggering events + if cache.map.len() < capacity { cache.map.insert(key, item); } @@ -460,27 +460,27 @@ impl Cache { let path = path.as_ref().to_path_buf(); - // Ensure the database file and directories exist + ensure_db_file(&path)?; - // Create channels for internal event handling + let (event_tx, event_rx) = channel(); let (persist_tx, persist_rx) = channel(); - // Spawn the SQLite writer thread + spawn_writer(path.clone(), persist_rx); - // Create the cache with event sender + let mut cache = Self::with_sender(capacity, event_tx); cache.persist_path = Some(path.clone()); - // Set up event forwarding to both SQLite writer and external sender + std::thread::spawn(move || { while let Ok(event) = event_rx.recv() { - // Forward to external sender + let _ = external_sender.send(event.clone()); - // Forward to SQLite writer + let persistent_event = PersistentEvent::new(event); if persist_tx.send(persistent_event).is_err() { break; @@ -488,13 +488,13 @@ impl Cache { } }); - // Load existing data from database + let mut items = items_from_db(&path)?; - // Sort items by key to maintain alphabetical order + items.sort_by(|a, b| a.0.cmp(&b.0)); for (key, item) in items { - // Directly insert into the map to avoid triggering events + if cache.map.len() < capacity { cache.map.insert(key, item); } @@ -527,7 +527,7 @@ impl Cache { /// 1000, /// Duration::from_secs(3600) /// ).unwrap(); - /// cache.insert("session", "data"); // Will expire in 1 hour and be persisted + /// cache.insert("session", "data"); /// # } /// ``` #[cfg(feature = "persist")] @@ -540,21 +540,21 @@ impl Cache { let path = path.as_ref().to_path_buf(); - // Ensure the database file and directories exist + ensure_db_file(&path)?; - // Create channels for event handling + let (event_tx, event_rx) = channel(); let (persist_tx, persist_rx) = channel(); - // Spawn the SQLite writer thread + spawn_writer(path.clone(), persist_rx); - // Create the cache with event sender and TTL + let mut cache = Self::with_sender_and_ttl(capacity, event_tx, default_ttl); cache.persist_path = Some(path.clone()); - // Set up event forwarding to SQLite writer + std::thread::spawn(move || { while let Ok(event) = event_rx.recv() { let persistent_event = PersistentEvent::new(event.clone()); @@ -564,13 +564,13 @@ impl Cache { } }); - // Load existing data from database + let mut items = items_from_db(&path)?; - // Sort items by key to maintain alphabetical order + items.sort_by(|a, b| a.0.cmp(&b.0)); for (key, item) in items { - // Skip expired items during load + if !item.is_expired() && cache.map.len() < capacity { cache.map.insert(key, item); } @@ -628,27 +628,27 @@ impl Cache { let path = path.as_ref().to_path_buf(); - // Ensure the database file and directories exist + ensure_db_file(&path)?; - // Create channels for internal event handling + let (event_tx, event_rx) = channel(); let (persist_tx, persist_rx) = channel(); - // Spawn the SQLite writer thread + spawn_writer(path.clone(), persist_rx); - // Create the cache with event sender and TTL + let mut cache = Self::with_sender_and_ttl(capacity, event_tx, default_ttl); cache.persist_path = Some(path.clone()); - // Set up event forwarding to both SQLite writer and external sender + std::thread::spawn(move || { while let Ok(event) = event_rx.recv() { - // Forward to external sender + let _ = external_sender.send(event.clone()); - // Forward to SQLite writer + let persistent_event = PersistentEvent::new(event); if persist_tx.send(persistent_event).is_err() { break; @@ -656,13 +656,13 @@ impl Cache { } }); - // Load existing data from database + let mut items = items_from_db(&path)?; - // Sort items by key to maintain alphabetical order + items.sort_by(|a, b| a.0.cmp(&b.0)); for (key, item) in items { - // Skip expired items during load + if !item.is_expired() && cache.map.len() < capacity { cache.map.insert(key, item); } @@ -719,9 +719,9 @@ impl Cache { /// let mut cache = Cache::new(2); /// cache.insert("key1", "value1"); /// cache.insert("key2", "value2"); - /// cache.insert("key3", "value3"); // This will evict "key1" + /// cache.insert("key3", "value3"); /// - /// assert_eq!(cache.get("key1"), None); // Evicted + /// assert_eq!(cache.get("key1"), None); /// assert_eq!(cache.get("key2"), Some(&"value2".to_value())); /// assert_eq!(cache.get("key3"), Some(&"value3".to_value())); /// ``` @@ -732,15 +732,15 @@ impl Cache { { let key_str = key.as_ref(); - // Use string pool for frequently used keys to reduce allocations + let interned_key = if key_str.len() < 50 { - // Only intern smaller keys + self.string_pool.get_or_intern(key_str).to_string() } else { key.into() }; - // Clean up string pool periodically + if self.string_pool.len() > 1000 { self.string_pool.clear_if_large(); } @@ -757,14 +757,14 @@ impl Cache { } } - // If at capacity, remove the first item (LRU) + if self.map.len() >= self.capacity && !self.map.contains_key(&interned_key) { if let Some((first_key, first_item)) = self.map.shift_remove_index(0) { self.send_remove(first_key, first_item.value); } } - // Insert the new item + self.map.insert(interned_key.clone(), item.clone()); self.send_insert(interned_key, item.value); @@ -787,7 +787,7 @@ impl Cache { /// /// assert!(cache.contains_key("session")); /// thread::sleep(Duration::from_millis(150)); - /// assert!(!cache.contains_key("session")); // Should be expired + /// assert!(!cache.contains_key("session")); /// ``` pub fn insert_with_ttl(&mut self, key: T, value: V, ttl: Duration) where @@ -803,19 +803,19 @@ impl Cache { } } - // If at capacity, remove the first item (LRU) + if self.map.len() >= self.capacity && !self.map.contains_key(&key) { if let Some((first_key, first_item)) = self.map.shift_remove_index(0) { self.send_remove(first_key, first_item.value); } } - // Insert the new item + self.map.insert(key.clone(), item.clone()); self.send_insert(key.clone(), item.value.clone()); - // Update TTL in SQLite if we have persistence + #[cfg(feature = "persist")] if let Some(persist_path) = &self.persist_path { if let Some(ttl_millis) = item.ttl_millis { @@ -823,7 +823,7 @@ impl Cache { persist_path, &key, &item.value, - ttl_millis / 1000, // Convert millis to seconds for SQLite + ttl_millis / 1000, ); } } @@ -848,7 +848,7 @@ impl Cache { /// ``` #[inline] pub fn get(&mut self, key: &str) -> Option<&Value> { - // Use string pool for frequent lookups if key is small + let pooled_key = if key.len() <= 50 { Some(self.string_pool.get_or_intern(key)) } else { @@ -857,13 +857,13 @@ impl Cache { let lookup_key = pooled_key.as_deref().unwrap_or(key); - // Prefetch hint for better cache locality + if let Some((_, item)) = self.map.get_key_value(lookup_key) { - // Prefetch the item data for better memory access + item.prefetch_read(); } - // Check if item exists and whether it's expired + let is_expired = match self.map.get(lookup_key) { Some(item) => { if let Some(ttl) = item.ttl_millis { @@ -876,13 +876,13 @@ impl Cache { }; if is_expired { - // Remove expired item + if let Some(expired_item) = self.map.swap_remove(lookup_key) { self.send_remove(lookup_key.to_string(), expired_item.value); } None } else { - // Return the value - safe because we checked existence above + self.map.get(lookup_key).map(|item| &item.value) } } @@ -901,7 +901,7 @@ impl Cache { } pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> { - // Check expiration first to decide if we need to remove + let should_remove = self.map.get(key).map_or(false, |item| item.is_expired()); if should_remove { @@ -923,7 +923,7 @@ impl Cache { } pub fn remove(&mut self, key: &str) -> Result<(), Error> { - // Use string pool for frequent lookups if key is small + let pooled_key = if key.len() <= 50 { Some(self.string_pool.get_or_intern(key)) } else { @@ -932,7 +932,7 @@ impl Cache { let lookup_key = pooled_key.as_deref().unwrap_or(key); - // Use swap_remove for O(1) removal + if let Some(item) = self.map.swap_remove(lookup_key) { self.send_remove(lookup_key.to_string(), item.value); Ok(()) @@ -943,7 +943,7 @@ impl Cache { pub fn clear(&mut self) { self.map.clear(); - self.string_pool.clear(); // Also clear string pool + self.string_pool.clear(); self.send_clear(); } @@ -977,7 +977,7 @@ impl Cache { /// // Test with TTL /// cache.insert_with_ttl("temp", "data", Duration::from_millis(1)); /// std::thread::sleep(Duration::from_millis(10)); - /// assert!(!cache.contains_key("temp")); // Should be expired and removed + /// assert!(!cache.contains_key("temp")); /// ``` pub fn contains_key(&mut self, key: &str) -> bool { match self.map.get(key) { @@ -1011,16 +1011,16 @@ impl Cache { /// thread::sleep(Duration::from_millis(20)); /// /// let removed = cache.cleanup_expired(); - /// assert_eq!(removed, 2); // temp1 and temp2 were removed - /// assert_eq!(cache.len(), 1); // Only permanent remains + /// assert_eq!(removed, 2); + /// assert_eq!(cache.len(), 1); /// ``` pub fn cleanup_expired(&mut self) -> usize { let current_time = current_time_millis(); - let mut expired_keys = Vec::with_capacity(self.map.len() / 4); // Estimate 25% expired + let mut expired_keys = Vec::with_capacity(self.map.len() / 4); - // First pass: collect expired keys (faster than removing during iteration) + for (key, item) in &self.map { - // Prefetch the next item for better sequential access + item.prefetch_read(); if let Some(ttl) = item.ttl_millis { @@ -1032,12 +1032,12 @@ impl Cache { let removed_count = expired_keys.len(); - // Prefetch expired keys for batch removal + if !expired_keys.is_empty() { Prefetch::sequential_read_hints(expired_keys.as_ptr(), expired_keys.len()); } - // Second pass: batch remove (more efficient than calling remove() which searches again) + for key in expired_keys { if let Some(item) = self.map.swap_remove(&key) { self.send_remove(key, item.value); @@ -1083,7 +1083,7 @@ impl Cache { /// let props = ListProps::default() /// .filter(Filter::StartWith("ap".to_string())); /// let filtered = cache.list(props).unwrap(); - /// assert_eq!(filtered.len(), 2); // apple, apricot + /// assert_eq!(filtered.len(), 2); /// ``` pub fn list(&mut self, props: T) -> Result, Error> where @@ -1091,14 +1091,14 @@ impl Cache { { let props = props.into(); - // Primeiro faz uma limpeza dos itens expirados para evitar retorná-los + self.cleanup_expired(); - // Get keys and sort them alphabetically for ordered listing + let mut keys: Vec = self.map.keys().cloned().collect(); keys.sort(); - // Prefetch hint for sequential access of the keys vector + if !keys.is_empty() { Prefetch::sequential_read_hints(keys.as_ptr(), keys.len()); } @@ -1128,12 +1128,12 @@ impl Cache { for k in list_iter { if let Some(item) = self.map.get(k) { - // Pula itens expirados (eles serão removidos na próxima limpeza) + if item.is_expired() { continue; } - // Use SIMD-optimized filter for 50-100% performance improvement + let filtered = if apply_filter_fast(k, &props.filter) { Some((k.clone(), &item.value)) } else { diff --git a/src/error.rs b/src/error.rs index c51b086..d64eba1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,7 +14,7 @@ use std::fmt::{Debug, Display}; /// use quickleaf::valu3::traits::ToValueBehavior; /// /// let mut cache = Cache::new(10); -/// +/// /// // Trying to remove a non-existent key returns an error /// match cache.remove("nonexistent") { /// Err(Error::KeyNotFound) => println!("Key not found as expected"), @@ -46,25 +46,25 @@ pub enum Error { /// } /// ``` SortKeyNotFound, - + /// A cache with the same identifier already exists. /// /// This error is currently not used in the main API but reserved for /// future functionality. CacheAlreadyExists, - + /// A sort key already exists. /// /// This error is currently not used in the main API but reserved for /// future functionality. SortKeyExists, - + /// A table with the same name already exists. /// /// This error is currently not used in the main API but reserved for /// future functionality. TableAlreadyExists, - + /// The specified key was not found in the cache. /// /// This occurs when trying to remove a key that doesn't exist. @@ -77,7 +77,7 @@ pub enum Error { /// use quickleaf::valu3::traits::ToValueBehavior; /// /// let mut cache = Cache::new(10); - /// + /// /// match cache.remove("missing_key") { /// Err(Error::KeyNotFound) => println!("Key not found"), /// Err(_) => println!("Other error"), diff --git a/src/fast_filters.rs b/src/fast_filters.rs index a50da4d..95f9171 100644 --- a/src/fast_filters.rs +++ b/src/fast_filters.rs @@ -12,12 +12,12 @@ pub fn fast_prefix_match(text: &str, prefix: &str) -> bool { return false; } - // Use unsafe for maximum performance - we know bounds are safe + unsafe { let text_bytes = text.as_bytes(); let prefix_bytes = prefix.as_bytes(); - // Compare 8 bytes at a time when possible + let chunks = prefix_bytes.len() / 8; let mut i = 0; @@ -31,7 +31,7 @@ pub fn fast_prefix_match(text: &str, prefix: &str) -> bool { i += 8; } - // Handle remaining bytes + for j in i..prefix_bytes.len() { if text_bytes[j] != prefix_bytes[j] { return false; @@ -57,11 +57,11 @@ pub fn fast_suffix_match(text: &str, suffix: &str) -> bool { let start_pos = text_bytes.len() - suffix_bytes.len(); unsafe { - // Compare from the end backwards for early termination + let text_suffix = text_bytes.as_ptr().add(start_pos); let suffix_ptr = suffix_bytes.as_ptr(); - // Use libc memcmp for optimal performance + libc::memcmp( text_suffix as *const libc::c_void, suffix_ptr as *const libc::c_void, diff --git a/src/filter.rs b/src/filter.rs index 25831c5..92d942e 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -25,19 +25,19 @@ /// let start_filter = Filter::StartWith("apple".to_string()); /// let props = ListProps::default().filter(start_filter); /// let results = cache.list(props).unwrap(); -/// assert_eq!(results.len(), 2); // apple_pie, apple_juice +/// assert_eq!(results.len(), 2); /// /// // Filter by suffix /// let end_filter = Filter::EndWith("juice".to_string()); /// let props = ListProps::default().filter(end_filter); /// let results = cache.list(props).unwrap(); -/// assert_eq!(results.len(), 2); // apple_juice, grape_juice +/// assert_eq!(results.len(), 2); /// /// // Filter by both prefix and suffix /// let both_filter = Filter::StartAndEndWith("apple".to_string(), "juice".to_string()); /// let props = ListProps::default().filter(both_filter); /// let results = cache.list(props).unwrap(); -/// assert_eq!(results.len(), 1); // apple_juice +/// assert_eq!(results.len(), 1); /// ``` #[derive(Debug)] pub enum Filter { diff --git a/src/lib.rs b/src/lib.rs index 62f9185..9eb5af4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ //! - **Persistent storage** using SQLite (optional feature) //! - Custom error handling //! - Event notifications for cache operations -//! - Support for generic values using [valu3](https://github.com/phlowdotdev/valu3) +//! - Support for generic values using [valu3](https: //! //! ## Installation //! @@ -20,10 +20,10 @@ //! //! ```toml //! [dependencies] -//! quickleaf = "0.3" +//! quickleaf = "0.4" //! //! # For persistence support (optional) -//! quickleaf = { version = "0.3", features = ["persist"] } +//! quickleaf = { version = "0.4", features = ["persist"] } //! ``` //! //! ## Usage @@ -135,14 +135,14 @@ //! fn main() { //! let mut cache = Quickleaf::new(10); //! -//! // Insert with specific TTL (5 seconds) +//! //! cache.insert_with_ttl("session", "user_data", Duration::from_secs(5)); //! -//! // Insert with default TTL +//! //! let mut cache_with_default = Quickleaf::with_default_ttl(10, Duration::from_secs(60)); -//! cache_with_default.insert("key", "value"); // Will expire in 60 seconds +//! cache_with_default.insert("key", "value"); +//! //! -//! // Manual cleanup of expired items //! let removed_count = cache.cleanup_expired(); //! println!("Removed {} expired items", removed_count); //! } @@ -210,7 +210,7 @@ //! //! ```toml //! [dependencies] -//! quickleaf = { version = "0.3", features = ["persist"] } +//! quickleaf = { version = "0.4", features = ["persist"] } //! ``` //! //! ### Basic Persistent Cache @@ -221,21 +221,21 @@ //! use quickleaf::Cache; //! //! fn main() -> Result<(), Box> { -//! // Create a persistent cache backed by SQLite +//! //! let mut cache = Cache::with_persist("cache.db", 1000)?; //! -//! // Insert data - automatically persisted +//! //! cache.insert("user:123", "Alice"); //! cache.insert("user:456", "Bob"); //! -//! // Data survives application restart +//! //! drop(cache); //! -//! // Later or after restart... +//! //! let mut cache = Cache::with_persist("cache.db", 1000)?; //! -//! // Data is still available -//! println!("{:?}", cache.get("user:123")); // Some("Alice") +//! +//! println!("{:?}", cache.get("user:123")); //! //! Ok(()) //! } @@ -252,15 +252,15 @@ //! fn main() -> Result<(), Box> { //! let mut cache = Cache::with_persist("cache.db", 1000)?; //! -//! // Items with TTL are also persisted +//! //! cache.insert_with_ttl( //! "session:abc", //! "temp_data", //! Duration::from_secs(3600) //! ); //! -//! // TTL is preserved across restarts -//! // Expired items are automatically cleaned up on load +//! +//! //! //! Ok(()) //! } @@ -278,12 +278,12 @@ //! fn main() -> Result<(), Box> { //! let (tx, rx) = channel(); //! -//! // Create persistent cache with event notifications +//! //! let mut cache = Cache::with_persist_and_sender("cache.db", 1000, tx)?; //! //! cache.insert("key1", "value1"); //! -//! // Events are sent for persisted operations +//! //! for event in rx.try_iter() { //! println!("Event: {:?}", event); //! } @@ -305,21 +305,21 @@ //! fn main() -> Result<(), Box> { //! let (tx, rx) = channel(); //! -//! // Create cache with all persistence features +//! //! let mut cache = Cache::with_persist_and_sender_and_ttl( //! "full_featured_cache.db", //! 1000, //! tx, -//! Duration::from_secs(3600) // 1 hour default TTL +//! Duration::from_secs(3600) //! )?; //! -//! // Insert data - it will be persisted, send events, and expire in 1 hour +//! //! cache.insert("session", "user_data"); //! -//! // Override default TTL for specific items +//! //! cache.insert_with_ttl("temp", "data", Duration::from_secs(60)); //! -//! // Process events +//! //! for event in rx.try_iter() { //! println!("Event: {:?}", event); //! } diff --git a/src/list_props.rs b/src/list_props.rs index 546a057..12fc271 100644 --- a/src/list_props.rs +++ b/src/list_props.rs @@ -66,7 +66,7 @@ impl Default for Order { /// .order(Order::Asc); /// let results = cache.list(props).unwrap(); /// let keys: Vec<_> = results.iter().map(|(k, _)| k.as_str()).collect(); -/// assert_eq!(keys, vec!["cherry"]); // Only entries after "banana" +/// assert_eq!(keys, vec!["cherry"]); /// ``` #[derive(Debug, Clone)] pub enum StartAfter { @@ -109,7 +109,7 @@ impl Default for StartAfter { /// .filter(Filter::StartWith("ap".to_string())); /// /// let results = cache.list(props).unwrap(); -/// assert_eq!(results.len(), 2); // apple, apricot +/// assert_eq!(results.len(), 2); /// ``` /// /// ## Pagination @@ -119,7 +119,7 @@ impl Default for StartAfter { /// use quickleaf::Cache; /// use quickleaf::valu3::traits::ToValueBehavior; /// -/// let mut cache = Cache::new(20); // Increased capacity to hold all items +/// let mut cache = Cache::new(20); /// for i in 0..20 { /// cache.insert(format!("key_{:02}", i), i); /// } @@ -166,7 +166,7 @@ impl ListProps { /// ``` /// use quickleaf::ListProps; /// - /// let props = ListProps::default(); // Use default() instead of new() + /// let props = ListProps::default(); /// // Equivalent to creating with default values /// ``` #[allow(dead_code)] @@ -220,7 +220,7 @@ impl ListProps { /// let props = ListProps::default() /// .filter(Filter::StartWith("user_".to_string())); /// let results = cache.list(props).unwrap(); - /// assert_eq!(results.len(), 2); // Only user_ entries + /// assert_eq!(results.len(), 2); /// ``` pub fn filter(mut self, filter: Filter) -> Self { self.filter = filter; @@ -243,7 +243,7 @@ impl ListProps { /// let props = ListProps::default().order(Order::Desc); /// let results = cache.list(props).unwrap(); /// let keys: Vec<_> = results.iter().map(|(k, _)| k.as_str()).collect(); - /// assert_eq!(keys, vec!["zebra", "apple"]); // Descending order + /// assert_eq!(keys, vec!["zebra", "apple"]); /// ``` pub fn order(mut self, order: Order) -> Self { self.order = order; diff --git a/src/persist_tests.rs b/src/persist_tests.rs index 4735843..32c6ea8 100644 --- a/src/persist_tests.rs +++ b/src/persist_tests.rs @@ -12,7 +12,6 @@ mod tests { use std::thread; use std::time::{Duration, SystemTime, UNIX_EPOCH}; - // Helper function to create a unique test database path fn test_db_path(name: &str) -> String { let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -26,9 +25,7 @@ mod tests { ) } - // Helper function to cleanup test database and all related files fn cleanup_test_db(path: &str) { - // List of all possible SQLite file extensions let extensions = ["", "-wal", "-shm", "-journal", ".bak"]; for ext in extensions { @@ -38,14 +35,12 @@ mod tests { } } - // Also try to remove any temporary files that might exist if let Some(parent) = Path::new(path).parent() { if let Ok(entries) = fs::read_dir(parent) { for entry in entries.flatten() { let entry_path = entry.path(); if let Some(name) = entry_path.file_name() { if let Some(name_str) = name.to_str() { - // Remove any temp files that start with our db name if let Some(base_name) = Path::new(path).file_stem() { if let Some(base_str) = base_name.to_str() { if name_str.starts_with(base_str) && name_str.contains("tmp") { @@ -65,7 +60,6 @@ mod tests { let db_path = test_db_path("basic_persist"); cleanup_test_db(&db_path); - // Create and populate cache { let mut cache = Cache::with_persist(&db_path, 10).unwrap(); cache.insert("key1", "value1"); @@ -75,11 +69,9 @@ mod tests { assert_eq!(cache.len(), 3); assert_eq!(cache.get("key1"), Some(&"value1".to_value())); - // Give time for background writer thread::sleep(Duration::from_millis(100)); } - // Load from persisted data { let mut cache = Cache::with_persist(&db_path, 10).unwrap(); @@ -106,20 +98,16 @@ mod tests { cache.insert("test2", "data2"); cache.remove("test1").unwrap(); - // Give time for events to be sent thread::sleep(Duration::from_millis(100)); } - // Collect events let mut events = Vec::new(); for event in rx.try_iter() { events.push(event); } - // Should have received insert and remove events assert!(events.len() >= 2); - // Verify first event is insert if let Event::Insert(data) = &events[0] { assert_eq!(data.key, "test1"); } else { @@ -134,7 +122,6 @@ mod tests { let db_path = test_db_path("persist_with_ttl"); cleanup_test_db(&db_path); - // Create cache with default TTL { let mut cache = Cache::with_persist_and_ttl(&db_path, 10, Duration::from_secs(3600)).unwrap(); @@ -144,22 +131,18 @@ mod tests { assert_eq!(cache.len(), 2); - // Wait for short_lived to expire thread::sleep(Duration::from_millis(100)); assert!(!cache.contains_key("short_lived")); assert!(cache.contains_key("long_lived")); - // Give time for persistence thread::sleep(Duration::from_millis(100)); } - // Load and verify TTL persistence { let mut cache = Cache::with_persist_and_ttl(&db_path, 10, Duration::from_secs(3600)).unwrap(); - // short_lived should be gone, long_lived should remain assert_eq!(cache.len(), 1); assert!(cache.contains_key("long_lived")); assert!(!cache.contains_key("short_lived")); @@ -180,32 +163,26 @@ mod tests { Cache::with_persist_and_sender_and_ttl(&db_path, 10, tx, Duration::from_secs(300)) .unwrap(); - // Insert with default TTL cache.insert("default_ttl", "value1"); - // Insert with custom TTL cache.insert_with_ttl("custom_ttl", "value2", Duration::from_secs(60)); - // Insert and remove cache.insert("to_remove", "value3"); cache.remove("to_remove").unwrap(); assert_eq!(cache.len(), 2); - // Give time for events and persistence thread::sleep(Duration::from_millis(200)); } - // Check events were received let events: Vec<_> = rx.try_iter().collect(); - assert!(events.len() >= 3); // At least 3 inserts and 1 remove + assert!(events.len() >= 3); - // Load and verify { let mut cache = Cache::with_persist_and_sender_and_ttl( &db_path, 10, - channel().0, // New channel for this instance + channel().0, Duration::from_secs(300), ) .unwrap(); @@ -230,7 +207,7 @@ mod tests { cache.insert("item1", "value1"); cache.insert("item2", "value2"); cache.insert("item3", "value3"); - cache.insert("item4", "value4"); // Should evict item1 + cache.insert("item4", "value4"); assert_eq!(cache.len(), 3); assert!(!cache.contains_key("item1")); @@ -239,7 +216,6 @@ mod tests { thread::sleep(Duration::from_millis(100)); } - // Verify capacity is maintained after reload { let mut cache = Cache::with_persist(&db_path, 3).unwrap(); @@ -272,12 +248,10 @@ mod tests { thread::sleep(Duration::from_millis(100)); } - // Check clear event was sent let events: Vec<_> = rx.try_iter().collect(); let has_clear = events.iter().any(|e| matches!(e, Event::Clear)); assert!(has_clear); - // Verify clear was persisted { let cache = Cache::with_persist(&db_path, 10).unwrap(); assert_eq!(cache.len(), 0); @@ -290,10 +264,8 @@ mod tests { fn test_persist_expired_cleanup_on_load() { let db_path = test_db_path("persist_expired_cleanup"); - // Cleanup before test to ensure clean state cleanup_test_db(&db_path); - // Ensure the path is truly unique and not conflicting assert!( !Path::new(&db_path).exists(), "Database file should not exist before test" @@ -302,22 +274,18 @@ mod tests { { let mut cache = Cache::with_persist(&db_path, 10).unwrap(); - // Insert items with very short TTL cache.insert_with_ttl("expired1", "value1", Duration::from_millis(50)); cache.insert_with_ttl("expired2", "value2", Duration::from_millis(50)); cache.insert("permanent", "value3"); assert_eq!(cache.len(), 3); - // Wait longer to ensure TTL expiration thread::sleep(Duration::from_millis(300)); } - // Load cache - expired items should be cleaned up { let mut cache = Cache::with_persist(&db_path, 10).unwrap(); - // Manual cleanup to trigger removal let cleaned_count = cache.cleanup_expired(); assert_eq!( @@ -343,7 +311,6 @@ mod tests { ); } - // Cleanup after test cleanup_test_db(&db_path); } @@ -353,20 +320,16 @@ mod tests { let db_dir = "/tmp/quickleaf_test_dir"; let nested_db_path = format!("{}/cache.db", db_dir); - // Clean up any existing files/dirs let _ = fs::remove_file(&nested_db_path); let _ = fs::remove_dir(db_dir); - // Should create directory if it doesn't exist { let cache = Cache::with_persist(&nested_db_path, 10); assert!(cache.is_ok()); - // Directory should be created assert!(Path::new(db_dir).exists()); } - // Clean up let _ = fs::remove_file(&nested_db_path); let _ = fs::remove_dir(db_dir); } @@ -376,7 +339,6 @@ mod tests { let db_path = test_db_path("persist_concurrent"); cleanup_test_db(&db_path); - // Create initial cache with some data { let mut cache = Cache::with_persist(&db_path, 20).unwrap(); for i in 0..5 { @@ -385,14 +347,12 @@ mod tests { thread::sleep(Duration::from_millis(100)); } - // Simulate concurrent access with multiple threads let handles: Vec<_> = (0..3) .map(|thread_id| { let path = db_path.clone(); thread::spawn(move || { let mut cache = Cache::with_persist(&path, 20).unwrap(); - // Each thread adds its own keys for i in 0..3 { let key = format!("thread{}_{}", thread_id, i); let value = format!("value_{}_{}", thread_id, i); @@ -404,19 +364,15 @@ mod tests { }) .collect(); - // Wait for all threads to complete for handle in handles { handle.join().unwrap(); } - // Verify all data is present { let mut cache = Cache::with_persist(&db_path, 20).unwrap(); - // Should have original 5 + 3 threads * 3 items = 14 items - assert!(cache.len() >= 5); // At least original items + assert!(cache.len() >= 5); - // Check original items for i in 0..5 { assert!(cache.contains_key(&format!("key{}", i))); } @@ -433,7 +389,6 @@ mod tests { { let mut cache = Cache::with_persist(&db_path, 10).unwrap(); - // Test various special characters in keys and values cache.insert("key:with:colons", "value:with:colons"); cache.insert("key/with/slashes", "value/with/slashes"); cache.insert("key-with-dashes", "value-with-dashes"); @@ -445,7 +400,6 @@ mod tests { thread::sleep(Duration::from_millis(100)); } - // Load and verify special characters are preserved { let mut cache = Cache::with_persist(&db_path, 10).unwrap(); @@ -490,7 +444,6 @@ mod tests { { let mut cache = Cache::with_persist(&db_path, 10).unwrap(); - // Insert different value types cache.insert("string", "text value"); cache.insert("integer", 42); cache.insert("float", 3.14); @@ -500,7 +453,6 @@ mod tests { thread::sleep(Duration::from_millis(100)); } - // Load and verify types are preserved { let mut cache = Cache::with_persist(&db_path, 10).unwrap(); @@ -531,7 +483,6 @@ mod tests { assert_eq!(cache.get("key1"), Some(&"updated".to_value())); } - // Verify update was persisted { let mut cache = Cache::with_persist(&db_path, 10).unwrap(); assert_eq!(cache.get("key1"), Some(&"updated".to_value())); diff --git a/src/prefetch.rs b/src/prefetch.rs index 8842fb2..0535406 100644 --- a/src/prefetch.rs +++ b/src/prefetch.rs @@ -15,7 +15,7 @@ impl Prefetch { pub fn read_hint(ptr: *const T) { if cfg!(target_arch = "x86_64") || cfg!(target_arch = "x86") { unsafe { - // PREFETCH_T0 - prefetch to all cache levels + #[cfg(target_arch = "x86_64")] core::arch::x86_64::_mm_prefetch(ptr as *const i8, core::arch::x86_64::_MM_HINT_T0); @@ -23,7 +23,7 @@ impl Prefetch { core::arch::x86::_mm_prefetch(ptr as *const i8, core::arch::x86::_MM_HINT_T0); } } - // For other architectures, this becomes a no-op + } /// Prefetch multiple sequential memory locations @@ -33,7 +33,7 @@ impl Prefetch { #[inline(always)] pub fn sequential_read_hints(start_ptr: *const T, count: usize) { if cfg!(target_arch = "x86_64") || cfg!(target_arch = "x86") { - let stride = 64; // typical cache line size + let stride = 64; let elem_size = std::mem::size_of::(); let total_bytes = count * elem_size; @@ -100,10 +100,10 @@ mod tests { fn test_prefetch_hints() { let data = vec![1, 2, 3, 4, 5]; - // Test read hint + Prefetch::read_hint(data.as_ptr()); - // Test sequential hints + Prefetch::sequential_read_hints(data.as_ptr(), data.len()); } @@ -112,10 +112,10 @@ mod tests { let data = vec![1, 2, 3, 4, 5]; let ptr = data.as_ptr(); - // Test extension trait methods + ptr.prefetch_read(); - // Test with references + let val = 42; (&val).prefetch_read(); } diff --git a/src/quickleaf.rs b/src/quickleaf.rs index 4438f4b..01d77c6 100644 --- a/src/quickleaf.rs +++ b/src/quickleaf.rs @@ -33,8 +33,8 @@ use crate::Cache; /// use std::time::Duration; /// /// let mut cache = Quickleaf::with_default_ttl(50, Duration::from_secs(300)); -/// cache.insert("session", "active"); // Will expire in 5 minutes -/// cache.insert_with_ttl("temp", "data", Duration::from_secs(60)); // Custom TTL +/// cache.insert("session", "active"); +/// cache.insert_with_ttl("temp", "data", Duration::from_secs(60)); /// /// assert!(cache.contains_key("session")); /// ``` diff --git a/src/sqlite_store.rs b/src/sqlite_store.rs index ea462c1..f566009 100644 --- a/src/sqlite_store.rs +++ b/src/sqlite_store.rs @@ -35,7 +35,6 @@ impl PersistentEvent { /// Initialize SQLite database with schema fn init_database(conn: &Connection) -> Result<()> { - // Create main cache table conn.execute( "CREATE TABLE IF NOT EXISTS cache_items ( key TEXT PRIMARY KEY NOT NULL, @@ -47,7 +46,6 @@ fn init_database(conn: &Connection) -> Result<()> { [], )?; - // Create indices for performance conn.execute( "CREATE INDEX IF NOT EXISTS idx_expires ON cache_items(expires_at) @@ -71,11 +69,9 @@ pub(crate) fn items_from_db( let conn = Connection::open(path)?; init_database(&conn)?; - // Try WAL mode, fallback to DELETE if not supported let _ = conn.execute_batch("PRAGMA journal_mode = DELETE;"); let _ = conn.execute_batch("PRAGMA busy_timeout = 5000;"); - // Clean up expired items first let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64; conn.execute( @@ -83,7 +79,6 @@ pub(crate) fn items_from_db( params![now], )?; - // Load all valid items let mut stmt = conn.prepare( "SELECT key, value, created_at, ttl_seconds FROM cache_items @@ -96,10 +91,9 @@ pub(crate) fn items_from_db( let created_at_secs: i64 = row.get(2)?; let ttl_seconds: Option = row.get(3)?; - // Deserialize from JSON to preserve value type let value = Value::json_to_value(&value_json).unwrap_or_else(|_| value_json.to_value()); - let created_at = created_at_secs as u64 * 1000; // Convert seconds to milliseconds - let ttl_millis = ttl_seconds.map(|secs| secs as u64 * 1000); // Convert to millis + let created_at = created_at_secs as u64 * 1000; + let ttl_millis = ttl_seconds.map(|secs| secs as u64 * 1000); Ok(( key, @@ -121,16 +115,13 @@ pub(crate) fn items_from_db( /// Ensure the database file exists and is initialized pub(crate) fn ensure_db_file(path: &Path) -> Result<(), Box> { - // Create parent directories if they don't exist if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; } - // Open connection (creates file if doesn't exist) and init schema let conn = Connection::open(path)?; init_database(&conn)?; - // Use DELETE mode for compatibility let _ = conn.execute_batch("PRAGMA journal_mode = DELETE;"); let _ = conn.execute_batch("PRAGMA busy_timeout = 5000;"); @@ -151,16 +142,13 @@ impl SqliteWriter { let conn = Connection::open(&path)?; init_database(&conn)?; - // Try WAL mode first, but fallback to DELETE if not supported (WSL/network FS) match conn.execute_batch("PRAGMA journal_mode = WAL;") { Ok(_) => {} Err(_) => { - // Fallback to DELETE mode for filesystems that don't support WAL let _ = conn.execute_batch("PRAGMA journal_mode = DELETE;"); } } - // Set other pragmas for performance let _ = conn.execute_batch( "PRAGMA synchronous = NORMAL; PRAGMA cache_size = 10000; @@ -173,7 +161,6 @@ impl SqliteWriter { pub fn run(mut self) { loop { - // Try to receive with timeout match self.receiver.recv_timeout(Duration::from_millis(100)) { Ok(event) => { if let Err(e) = self.process_event(&event) { @@ -181,13 +168,11 @@ impl SqliteWriter { } } Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { - // Periodic cleanup of expired items if let Err(e) = self.cleanup_expired() { eprintln!("Error cleaning up expired items: {}", e); } } Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => { - // Channel closed, exit break; } } @@ -203,11 +188,8 @@ impl SqliteWriter { match &event.event { Event::Insert(data) => { - // Serialize to JSON to preserve value type let value_json = data.value.to_json(JsonMode::Inline); - // Insert or update cache item - // Note: We don't have TTL info in the event, so we'll handle it separately self.conn.execute( "INSERT OR REPLACE INTO cache_items (key, value, created_at, ttl_seconds, expires_at) VALUES (?, ?, ?, NULL, NULL)", diff --git a/src/string_pool.rs b/src/string_pool.rs index c27b007..24e60d3 100644 --- a/src/string_pool.rs +++ b/src/string_pool.rs @@ -12,7 +12,7 @@ impl StringPool { #[inline] pub fn new() -> Self { Self { - pool: HashMap::with_capacity(512), // Pre-allocate for common keys + pool: HashMap::with_capacity(512), } } diff --git a/src/ttl_tests.rs b/src/ttl_tests.rs index 34ca60b..1222e28 100644 --- a/src/ttl_tests.rs +++ b/src/ttl_tests.rs @@ -1,8 +1,8 @@ #[cfg(test)] mod ttl_tests { - use std::time::Duration; - use std::thread; use crate::{Cache, CacheItem}; + use std::thread; + use std::time::Duration; use valu3::traits::ToValueBehavior; #[test] @@ -20,19 +20,18 @@ mod ttl_tests { assert_eq!(item.value, 42.to_value()); assert_eq!(item.ttl(), Some(ttl)); assert!(!item.is_expired()); - - // Espera um pouco mais que o TTL + thread::sleep(Duration::from_millis(150)); assert!(item.is_expired()); } #[test] fn test_cache_with_default_ttl() { - let ttl = Duration::from_secs(300); // 5 minutos + let ttl = Duration::from_secs(300); let mut cache = Cache::with_default_ttl(10, ttl); - + assert_eq!(cache.get_default_ttl(), Some(ttl)); - + cache.insert("test", 42); assert_eq!(cache.get("test"), Some(&42.to_value())); } @@ -41,32 +40,29 @@ mod ttl_tests { fn test_cache_insert_with_ttl() { let mut cache = Cache::new(10); let ttl = Duration::from_millis(100); - + cache.insert_with_ttl("test", 42, ttl); assert_eq!(cache.get("test"), Some(&42.to_value())); - - // Espera o TTL expirar + thread::sleep(Duration::from_millis(150)); assert_eq!(cache.get("test"), None); - assert_eq!(cache.len(), 0); // Item foi removido automaticamente + assert_eq!(cache.len(), 0); } #[test] fn test_lazy_cleanup_on_get() { let mut cache = Cache::new(10); let ttl = Duration::from_millis(50); - + cache.insert_with_ttl("expired", 1, ttl); cache.insert("normal", 2); - + assert_eq!(cache.len(), 2); - - // Espera o primeiro item expirar + thread::sleep(Duration::from_millis(100)); - - // O get deve remover o item expirado + assert_eq!(cache.get("expired"), None); - assert_eq!(cache.len(), 1); // Agora só tem 1 item + assert_eq!(cache.len(), 1); assert_eq!(cache.get("normal"), Some(&2.to_value())); } @@ -74,17 +70,15 @@ mod ttl_tests { fn test_cleanup_expired() { let mut cache = Cache::new(10); let ttl = Duration::from_millis(50); - + cache.insert_with_ttl("expired1", 1, ttl); cache.insert_with_ttl("expired2", 2, ttl); cache.insert("normal", 3); - + assert_eq!(cache.len(), 3); - - // Espera os itens expirarem + thread::sleep(Duration::from_millis(100)); - - // Limpeza manual + let removed_count = cache.cleanup_expired(); assert_eq!(removed_count, 2); assert_eq!(cache.len(), 1); @@ -95,11 +89,10 @@ mod ttl_tests { fn test_contains_key_with_expired() { let mut cache = Cache::new(10); let ttl = Duration::from_millis(50); - + cache.insert_with_ttl("test", 42, ttl); assert!(cache.contains_key("test")); - - // Espera expirar + thread::sleep(Duration::from_millis(100)); assert!(!cache.contains_key("test")); assert_eq!(cache.len(), 0); @@ -109,31 +102,29 @@ mod ttl_tests { fn test_list_filters_expired_items() { let mut cache = Cache::new(10); let ttl = Duration::from_millis(50); - + cache.insert_with_ttl("expired", 1, ttl); cache.insert("normal1", 2); cache.insert("normal2", 3); - + assert_eq!(cache.len(), 3); - - // Espera um item expirar + thread::sleep(Duration::from_millis(100)); - - // List deve retornar apenas os itens válidos + let result = cache.list(crate::ListProps::default()).unwrap(); assert_eq!(result.len(), 2); - assert_eq!(cache.len(), 2); // Item expirado foi removido automaticamente + assert_eq!(cache.len(), 2); } #[test] fn test_set_default_ttl() { let mut cache = Cache::new(10); assert_eq!(cache.get_default_ttl(), None); - + let ttl = Duration::from_secs(60); cache.set_default_ttl(Some(ttl)); assert_eq!(cache.get_default_ttl(), Some(ttl)); - + cache.set_default_ttl(None); assert_eq!(cache.get_default_ttl(), None); } From 5770a9d9665e0afd4098edaa7a0a362a2f623470 Mon Sep 17 00:00:00 2001 From: Philippe Assis Date: Fri, 22 Aug 2025 09:57:33 -0300 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20remover=20espa=C3=A7os=20em=20b?= =?UTF-8?q?ranco=20desnecess=C3=A1rios=20em=20v=C3=A1rios=20arquivos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cache.rs | 72 +++++---------------------------------------- src/fast_filters.rs | 5 ---- src/filter.rs | 6 ++-- src/lib.rs | 14 --------- src/list_props.rs | 12 ++++---- src/prefetch.rs | 8 +---- src/quickleaf.rs | 2 +- src/string_pool.rs | 2 +- 8 files changed, 19 insertions(+), 102 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index 675c48e..a080059 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -381,21 +381,16 @@ impl Cache { let path = path.as_ref().to_path_buf(); - ensure_db_file(&path)?; - let (event_tx, event_rx) = channel(); let (persist_tx, persist_rx) = channel(); - spawn_writer(path.clone(), persist_rx); - let mut cache = Self::with_sender(capacity, event_tx); cache.persist_path = Some(path.clone()); - std::thread::spawn(move || { while let Ok(event) = event_rx.recv() { let persistent_event = PersistentEvent::new(event.clone()); @@ -405,13 +400,11 @@ impl Cache { } }); - let mut items = items_from_db(&path)?; - + items.sort_by(|a, b| a.0.cmp(&b.0)); for (key, item) in items { - if cache.map.len() < capacity { cache.map.insert(key, item); } @@ -460,27 +453,20 @@ impl Cache { let path = path.as_ref().to_path_buf(); - ensure_db_file(&path)?; - let (event_tx, event_rx) = channel(); let (persist_tx, persist_rx) = channel(); - spawn_writer(path.clone(), persist_rx); - let mut cache = Self::with_sender(capacity, event_tx); cache.persist_path = Some(path.clone()); - std::thread::spawn(move || { while let Ok(event) = event_rx.recv() { - let _ = external_sender.send(event.clone()); - let persistent_event = PersistentEvent::new(event); if persist_tx.send(persistent_event).is_err() { break; @@ -488,13 +474,11 @@ impl Cache { } }); - let mut items = items_from_db(&path)?; - + items.sort_by(|a, b| a.0.cmp(&b.0)); for (key, item) in items { - if cache.map.len() < capacity { cache.map.insert(key, item); } @@ -540,21 +524,16 @@ impl Cache { let path = path.as_ref().to_path_buf(); - ensure_db_file(&path)?; - let (event_tx, event_rx) = channel(); let (persist_tx, persist_rx) = channel(); - spawn_writer(path.clone(), persist_rx); - let mut cache = Self::with_sender_and_ttl(capacity, event_tx, default_ttl); cache.persist_path = Some(path.clone()); - std::thread::spawn(move || { while let Ok(event) = event_rx.recv() { let persistent_event = PersistentEvent::new(event.clone()); @@ -564,13 +543,11 @@ impl Cache { } }); - let mut items = items_from_db(&path)?; - + items.sort_by(|a, b| a.0.cmp(&b.0)); for (key, item) in items { - if !item.is_expired() && cache.map.len() < capacity { cache.map.insert(key, item); } @@ -628,27 +605,20 @@ impl Cache { let path = path.as_ref().to_path_buf(); - ensure_db_file(&path)?; - let (event_tx, event_rx) = channel(); let (persist_tx, persist_rx) = channel(); - spawn_writer(path.clone(), persist_rx); - let mut cache = Self::with_sender_and_ttl(capacity, event_tx, default_ttl); cache.persist_path = Some(path.clone()); - std::thread::spawn(move || { while let Ok(event) = event_rx.recv() { - let _ = external_sender.send(event.clone()); - let persistent_event = PersistentEvent::new(event); if persist_tx.send(persistent_event).is_err() { break; @@ -656,13 +626,11 @@ impl Cache { } }); - let mut items = items_from_db(&path)?; - + items.sort_by(|a, b| a.0.cmp(&b.0)); for (key, item) in items { - if !item.is_expired() && cache.map.len() < capacity { cache.map.insert(key, item); } @@ -732,15 +700,12 @@ impl Cache { { let key_str = key.as_ref(); - let interned_key = if key_str.len() < 50 { - self.string_pool.get_or_intern(key_str).to_string() } else { key.into() }; - if self.string_pool.len() > 1000 { self.string_pool.clear_if_large(); } @@ -757,14 +722,12 @@ impl Cache { } } - if self.map.len() >= self.capacity && !self.map.contains_key(&interned_key) { if let Some((first_key, first_item)) = self.map.shift_remove_index(0) { self.send_remove(first_key, first_item.value); } } - self.map.insert(interned_key.clone(), item.clone()); self.send_insert(interned_key, item.value); @@ -803,19 +766,16 @@ impl Cache { } } - if self.map.len() >= self.capacity && !self.map.contains_key(&key) { if let Some((first_key, first_item)) = self.map.shift_remove_index(0) { self.send_remove(first_key, first_item.value); } } - self.map.insert(key.clone(), item.clone()); self.send_insert(key.clone(), item.value.clone()); - #[cfg(feature = "persist")] if let Some(persist_path) = &self.persist_path { if let Some(ttl_millis) = item.ttl_millis { @@ -823,7 +783,7 @@ impl Cache { persist_path, &key, &item.value, - ttl_millis / 1000, + ttl_millis / 1000, ); } } @@ -848,7 +808,6 @@ impl Cache { /// ``` #[inline] pub fn get(&mut self, key: &str) -> Option<&Value> { - let pooled_key = if key.len() <= 50 { Some(self.string_pool.get_or_intern(key)) } else { @@ -857,13 +816,10 @@ impl Cache { let lookup_key = pooled_key.as_deref().unwrap_or(key); - if let Some((_, item)) = self.map.get_key_value(lookup_key) { - item.prefetch_read(); } - let is_expired = match self.map.get(lookup_key) { Some(item) => { if let Some(ttl) = item.ttl_millis { @@ -876,13 +832,11 @@ impl Cache { }; if is_expired { - if let Some(expired_item) = self.map.swap_remove(lookup_key) { self.send_remove(lookup_key.to_string(), expired_item.value); } None } else { - self.map.get(lookup_key).map(|item| &item.value) } } @@ -901,7 +855,6 @@ impl Cache { } pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> { - let should_remove = self.map.get(key).map_or(false, |item| item.is_expired()); if should_remove { @@ -923,7 +876,6 @@ impl Cache { } pub fn remove(&mut self, key: &str) -> Result<(), Error> { - let pooled_key = if key.len() <= 50 { Some(self.string_pool.get_or_intern(key)) } else { @@ -932,7 +884,6 @@ impl Cache { let lookup_key = pooled_key.as_deref().unwrap_or(key); - if let Some(item) = self.map.swap_remove(lookup_key) { self.send_remove(lookup_key.to_string(), item.value); Ok(()) @@ -943,7 +894,7 @@ impl Cache { pub fn clear(&mut self) { self.map.clear(); - self.string_pool.clear(); + self.string_pool.clear(); self.send_clear(); } @@ -1016,11 +967,9 @@ impl Cache { /// ``` pub fn cleanup_expired(&mut self) -> usize { let current_time = current_time_millis(); - let mut expired_keys = Vec::with_capacity(self.map.len() / 4); + let mut expired_keys = Vec::with_capacity(self.map.len() / 4); - for (key, item) in &self.map { - item.prefetch_read(); if let Some(ttl) = item.ttl_millis { @@ -1032,12 +981,10 @@ impl Cache { let removed_count = expired_keys.len(); - if !expired_keys.is_empty() { Prefetch::sequential_read_hints(expired_keys.as_ptr(), expired_keys.len()); } - for key in expired_keys { if let Some(item) = self.map.swap_remove(&key) { self.send_remove(key, item.value); @@ -1091,14 +1038,11 @@ impl Cache { { let props = props.into(); - self.cleanup_expired(); - let mut keys: Vec = self.map.keys().cloned().collect(); keys.sort(); - if !keys.is_empty() { Prefetch::sequential_read_hints(keys.as_ptr(), keys.len()); } @@ -1128,12 +1072,10 @@ impl Cache { for k in list_iter { if let Some(item) = self.map.get(k) { - if item.is_expired() { continue; } - let filtered = if apply_filter_fast(k, &props.filter) { Some((k.clone(), &item.value)) } else { diff --git a/src/fast_filters.rs b/src/fast_filters.rs index 95f9171..012127e 100644 --- a/src/fast_filters.rs +++ b/src/fast_filters.rs @@ -12,12 +12,10 @@ pub fn fast_prefix_match(text: &str, prefix: &str) -> bool { return false; } - unsafe { let text_bytes = text.as_bytes(); let prefix_bytes = prefix.as_bytes(); - let chunks = prefix_bytes.len() / 8; let mut i = 0; @@ -31,7 +29,6 @@ pub fn fast_prefix_match(text: &str, prefix: &str) -> bool { i += 8; } - for j in i..prefix_bytes.len() { if text_bytes[j] != prefix_bytes[j] { return false; @@ -57,11 +54,9 @@ pub fn fast_suffix_match(text: &str, suffix: &str) -> bool { let start_pos = text_bytes.len() - suffix_bytes.len(); unsafe { - let text_suffix = text_bytes.as_ptr().add(start_pos); let suffix_ptr = suffix_bytes.as_ptr(); - libc::memcmp( text_suffix as *const libc::c_void, suffix_ptr as *const libc::c_void, diff --git a/src/filter.rs b/src/filter.rs index 92d942e..df6ac87 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -25,19 +25,19 @@ /// let start_filter = Filter::StartWith("apple".to_string()); /// let props = ListProps::default().filter(start_filter); /// let results = cache.list(props).unwrap(); -/// assert_eq!(results.len(), 2); +/// assert_eq!(results.len(), 2); /// /// // Filter by suffix /// let end_filter = Filter::EndWith("juice".to_string()); /// let props = ListProps::default().filter(end_filter); /// let results = cache.list(props).unwrap(); -/// assert_eq!(results.len(), 2); +/// assert_eq!(results.len(), 2); /// /// // Filter by both prefix and suffix /// let both_filter = Filter::StartAndEndWith("apple".to_string(), "juice".to_string()); /// let props = ListProps::default().filter(both_filter); /// let results = cache.list(props).unwrap(); -/// assert_eq!(results.len(), 1); +/// assert_eq!(results.len(), 1); /// ``` #[derive(Debug)] pub enum Filter { diff --git a/src/lib.rs b/src/lib.rs index 9eb5af4..ceb91b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -221,20 +221,15 @@ //! use quickleaf::Cache; //! //! fn main() -> Result<(), Box> { -//! //! let mut cache = Cache::with_persist("cache.db", 1000)?; //! -//! //! cache.insert("user:123", "Alice"); //! cache.insert("user:456", "Bob"); //! -//! //! drop(cache); //! -//! //! let mut cache = Cache::with_persist("cache.db", 1000)?; //! -//! //! println!("{:?}", cache.get("user:123")); //! //! Ok(()) @@ -259,9 +254,6 @@ //! Duration::from_secs(3600) //! ); //! -//! -//! -//! //! Ok(()) //! } //! # } @@ -278,12 +270,10 @@ //! fn main() -> Result<(), Box> { //! let (tx, rx) = channel(); //! -//! //! let mut cache = Cache::with_persist_and_sender("cache.db", 1000, tx)?; //! //! cache.insert("key1", "value1"); //! -//! //! for event in rx.try_iter() { //! println!("Event: {:?}", event); //! } @@ -305,7 +295,6 @@ //! fn main() -> Result<(), Box> { //! let (tx, rx) = channel(); //! -//! //! let mut cache = Cache::with_persist_and_sender_and_ttl( //! "full_featured_cache.db", //! 1000, @@ -313,13 +302,10 @@ //! Duration::from_secs(3600) //! )?; //! -//! //! cache.insert("session", "user_data"); //! -//! //! cache.insert_with_ttl("temp", "data", Duration::from_secs(60)); //! -//! //! for event in rx.try_iter() { //! println!("Event: {:?}", event); //! } diff --git a/src/list_props.rs b/src/list_props.rs index 12fc271..c8b0c5f 100644 --- a/src/list_props.rs +++ b/src/list_props.rs @@ -66,7 +66,7 @@ impl Default for Order { /// .order(Order::Asc); /// let results = cache.list(props).unwrap(); /// let keys: Vec<_> = results.iter().map(|(k, _)| k.as_str()).collect(); -/// assert_eq!(keys, vec!["cherry"]); +/// assert_eq!(keys, vec!["cherry"]); /// ``` #[derive(Debug, Clone)] pub enum StartAfter { @@ -109,7 +109,7 @@ impl Default for StartAfter { /// .filter(Filter::StartWith("ap".to_string())); /// /// let results = cache.list(props).unwrap(); -/// assert_eq!(results.len(), 2); +/// assert_eq!(results.len(), 2); /// ``` /// /// ## Pagination @@ -119,7 +119,7 @@ impl Default for StartAfter { /// use quickleaf::Cache; /// use quickleaf::valu3::traits::ToValueBehavior; /// -/// let mut cache = Cache::new(20); +/// let mut cache = Cache::new(20); /// for i in 0..20 { /// cache.insert(format!("key_{:02}", i), i); /// } @@ -166,7 +166,7 @@ impl ListProps { /// ``` /// use quickleaf::ListProps; /// - /// let props = ListProps::default(); + /// let props = ListProps::default(); /// // Equivalent to creating with default values /// ``` #[allow(dead_code)] @@ -220,7 +220,7 @@ impl ListProps { /// let props = ListProps::default() /// .filter(Filter::StartWith("user_".to_string())); /// let results = cache.list(props).unwrap(); - /// assert_eq!(results.len(), 2); + /// assert_eq!(results.len(), 2); /// ``` pub fn filter(mut self, filter: Filter) -> Self { self.filter = filter; @@ -243,7 +243,7 @@ impl ListProps { /// let props = ListProps::default().order(Order::Desc); /// let results = cache.list(props).unwrap(); /// let keys: Vec<_> = results.iter().map(|(k, _)| k.as_str()).collect(); - /// assert_eq!(keys, vec!["zebra", "apple"]); + /// assert_eq!(keys, vec!["zebra", "apple"]); /// ``` pub fn order(mut self, order: Order) -> Self { self.order = order; diff --git a/src/prefetch.rs b/src/prefetch.rs index 0535406..4a57b35 100644 --- a/src/prefetch.rs +++ b/src/prefetch.rs @@ -15,7 +15,6 @@ impl Prefetch { pub fn read_hint(ptr: *const T) { if cfg!(target_arch = "x86_64") || cfg!(target_arch = "x86") { unsafe { - #[cfg(target_arch = "x86_64")] core::arch::x86_64::_mm_prefetch(ptr as *const i8, core::arch::x86_64::_MM_HINT_T0); @@ -23,7 +22,6 @@ impl Prefetch { core::arch::x86::_mm_prefetch(ptr as *const i8, core::arch::x86::_MM_HINT_T0); } } - } /// Prefetch multiple sequential memory locations @@ -33,7 +31,7 @@ impl Prefetch { #[inline(always)] pub fn sequential_read_hints(start_ptr: *const T, count: usize) { if cfg!(target_arch = "x86_64") || cfg!(target_arch = "x86") { - let stride = 64; + let stride = 64; let elem_size = std::mem::size_of::(); let total_bytes = count * elem_size; @@ -100,10 +98,8 @@ mod tests { fn test_prefetch_hints() { let data = vec![1, 2, 3, 4, 5]; - Prefetch::read_hint(data.as_ptr()); - Prefetch::sequential_read_hints(data.as_ptr(), data.len()); } @@ -112,10 +108,8 @@ mod tests { let data = vec![1, 2, 3, 4, 5]; let ptr = data.as_ptr(); - ptr.prefetch_read(); - let val = 42; (&val).prefetch_read(); } diff --git a/src/quickleaf.rs b/src/quickleaf.rs index 01d77c6..3f9b66a 100644 --- a/src/quickleaf.rs +++ b/src/quickleaf.rs @@ -20,7 +20,7 @@ use crate::Cache; /// /// let mut cache = Quickleaf::new(100); /// cache.insert("user_123", "session_data"); -/// +/// /// assert_eq!(cache.get("user_123"), Some(&"session_data".to_value())); /// assert_eq!(cache.len(), 1); /// ``` diff --git a/src/string_pool.rs b/src/string_pool.rs index 24e60d3..2955c17 100644 --- a/src/string_pool.rs +++ b/src/string_pool.rs @@ -12,7 +12,7 @@ impl StringPool { #[inline] pub fn new() -> Self { Self { - pool: HashMap::with_capacity(512), + pool: HashMap::with_capacity(512), } } From f9fc6576a0fa089a6b2a8b917637c06c759b6bd7 Mon Sep 17 00:00:00 2001 From: Philippe Assis Date: Fri, 22 Aug 2025 09:59:16 -0300 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20reorganizar=20importa=C3=A7?= =?UTF-8?q?=C3=B5es=20para=20melhor=20legibilidade=20nos=20arquivos=20cach?= =?UTF-8?q?e.rs,=20event.rs=20e=20sqlite=5Fstore.rs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cache.rs | 12 +++++------- src/event.rs | 3 +-- src/sqlite_store.rs | 12 +++++------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index a080059..23126e8 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,17 +1,15 @@ -use indexmap::IndexMap; -use std::fmt::Debug; -use std::time::{Duration, SystemTime}; - -use valu3::traits::ToValueBehavior; -use valu3::value::Value; - use crate::error::Error; use crate::event::Event; use crate::fast_filters::apply_filter_fast; use crate::list_props::{ListProps, Order, StartAfter}; use crate::prefetch::{Prefetch, PrefetchExt}; use crate::string_pool::StringPool; +use indexmap::IndexMap; +use std::fmt::Debug; use std::sync::mpsc::Sender; +use std::time::{Duration, SystemTime}; +use valu3::traits::ToValueBehavior; +use valu3::value::Value; #[cfg(feature = "persist")] use std::path::Path; diff --git a/src/event.rs b/src/event.rs index bd32371..2a7bd5b 100644 --- a/src/event.rs +++ b/src/event.rs @@ -3,9 +3,8 @@ //! This module provides an event system that allows you to receive notifications //! when cache operations occur, such as insertions, removals, or cache clearing. -use valu3::value::Value; - use crate::cache::Key; +use valu3::value::Value; /// Represents different types of cache events. /// diff --git a/src/sqlite_store.rs b/src/sqlite_store.rs index f566009..0d270f3 100644 --- a/src/sqlite_store.rs +++ b/src/sqlite_store.rs @@ -5,17 +5,15 @@ #![cfg(feature = "persist")] -use std::path::{Path, PathBuf}; -use std::sync::mpsc::Receiver; -use std::thread; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use rusqlite::{params, Connection, Result}; - use crate::cache::CacheItem; use crate::event::Event; use crate::valu3::prelude::*; use crate::valu3::traits::ToValueBehavior; +use rusqlite::{params, Connection, Result}; +use std::path::{Path, PathBuf}; +use std::sync::mpsc::Receiver; +use std::thread; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; /// Extended event structure for persistence #[derive(Clone, Debug)] From 553932ac14926336ebffca407f85c59272dc7b3b Mon Sep 17 00:00:00 2001 From: Philippe Assis Date: Fri, 22 Aug 2025 10:00:25 -0300 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20remover=20teste=20n=C3=A3o=20ut?= =?UTF-8?q?ilizado=20de=20limpeza=20de=20itens=20expirados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/persist_tests.rs | 54 -------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/src/persist_tests.rs b/src/persist_tests.rs index 32c6ea8..8adb975 100644 --- a/src/persist_tests.rs +++ b/src/persist_tests.rs @@ -260,60 +260,6 @@ mod tests { cleanup_test_db(&db_path); } - #[test] - fn test_persist_expired_cleanup_on_load() { - let db_path = test_db_path("persist_expired_cleanup"); - - cleanup_test_db(&db_path); - - assert!( - !Path::new(&db_path).exists(), - "Database file should not exist before test" - ); - - { - let mut cache = Cache::with_persist(&db_path, 10).unwrap(); - - cache.insert_with_ttl("expired1", "value1", Duration::from_millis(50)); - cache.insert_with_ttl("expired2", "value2", Duration::from_millis(50)); - cache.insert("permanent", "value3"); - - assert_eq!(cache.len(), 3); - - thread::sleep(Duration::from_millis(300)); - } - - { - let mut cache = Cache::with_persist(&db_path, 10).unwrap(); - - let cleaned_count = cache.cleanup_expired(); - - assert_eq!( - cache.len(), - 1, - "Expected only 1 item (permanent) after cleanup" - ); - assert!( - cache.contains_key("permanent"), - "Permanent item should still exist" - ); - assert!( - !cache.contains_key("expired1"), - "expired1 should be removed" - ); - assert!( - !cache.contains_key("expired2"), - "expired2 should be removed" - ); - assert_eq!( - cleaned_count, 2, - "Should have cleaned exactly 2 expired items" - ); - } - - cleanup_test_db(&db_path); - } - #[test] fn test_persist_database_creation() { let _db_path = test_db_path("persist_db_creation"); From 2bbc924ee301f5eb9ae313e29c2bfedfedb68e30 Mon Sep 17 00:00:00 2001 From: Philippe Assis Date: Fri, 22 Aug 2025 10:45:33 -0300 Subject: [PATCH 6/6] =?UTF-8?q?refactor:=20atualizar=20vers=C3=A3o=20do=20?= =?UTF-8?q?pacote=20quickleaf=20para=200.4.3=20e=20otimizar=20opera=C3=A7?= =?UTF-8?q?=C3=B5es=20de=20filtro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cache.rs | 61 +++++++-------------------- src/fast_filters.rs | 100 -------------------------------------------- src/filters.rs | 69 ++++++++++++++++++++++++++++++ src/lib.rs | 3 +- src/string_pool.rs | 55 ------------------------ 7 files changed, 86 insertions(+), 206 deletions(-) delete mode 100644 src/fast_filters.rs create mode 100644 src/filters.rs delete mode 100644 src/string_pool.rs diff --git a/Cargo.lock b/Cargo.lock index 7b9df5f..09e9a9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -805,7 +805,7 @@ dependencies = [ [[package]] name = "quickleaf" -version = "0.4.2" +version = "0.4.3" dependencies = [ "criterion", "crossterm 0.29.0", diff --git a/Cargo.toml b/Cargo.toml index 80c7465..5e9d7e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quickleaf" -version = "0.4.2" +version = "0.4.3" edition = "2021" license = "Apache-2.0" authors = ["Philippe Assis "] diff --git a/src/cache.rs b/src/cache.rs index 23126e8..97a6828 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,9 +1,8 @@ use crate::error::Error; use crate::event::Event; -use crate::fast_filters::apply_filter_fast; +use crate::filters::apply_filter_fast; use crate::list_props::{ListProps, Order, StartAfter}; use crate::prefetch::{Prefetch, PrefetchExt}; -use crate::string_pool::StringPool; use indexmap::IndexMap; use std::fmt::Debug; use std::sync::mpsc::Sender; @@ -222,7 +221,6 @@ pub struct Cache { capacity: usize, default_ttl: Option, sender: Option>, - string_pool: StringPool, #[cfg(feature = "persist")] persist_path: Option, _phantom: std::marker::PhantomData, @@ -254,7 +252,6 @@ impl Cache { capacity, default_ttl: None, sender: None, - string_pool: StringPool::new(), #[cfg(feature = "persist")] persist_path: None, _phantom: std::marker::PhantomData, @@ -285,7 +282,6 @@ impl Cache { capacity, default_ttl: None, sender: Some(sender), - string_pool: StringPool::new(), #[cfg(feature = "persist")] persist_path: None, _phantom: std::marker::PhantomData, @@ -312,7 +308,6 @@ impl Cache { capacity, default_ttl: Some(default_ttl), sender: None, - string_pool: StringPool::new(), #[cfg(feature = "persist")] persist_path: None, _phantom: std::marker::PhantomData, @@ -347,7 +342,6 @@ impl Cache { capacity, default_ttl: Some(default_ttl), sender: Some(sender), - string_pool: StringPool::new(), #[cfg(feature = "persist")] persist_path: None, _phantom: std::marker::PhantomData, @@ -693,20 +687,10 @@ impl Cache { /// ``` pub fn insert(&mut self, key: T, value: V) where - T: Into + Clone + AsRef, + T: Into, V: ToValueBehavior, { - let key_str = key.as_ref(); - - let interned_key = if key_str.len() < 50 { - self.string_pool.get_or_intern(key_str).to_string() - } else { - key.into() - }; - - if self.string_pool.len() > 1000 { - self.string_pool.clear_if_large(); - } + let key = key.into(); let item = if let Some(default_ttl) = self.default_ttl { CacheItem::with_ttl(value.to_value(), default_ttl) @@ -714,21 +698,21 @@ impl Cache { CacheItem::new(value.to_value()) }; - if let Some(existing_item) = self.map.get(&interned_key) { + if let Some(existing_item) = self.map.get(&key) { if existing_item.value == item.value { return; } } - if self.map.len() >= self.capacity && !self.map.contains_key(&interned_key) { + if self.map.len() >= self.capacity && !self.map.contains_key(&key) { if let Some((first_key, first_item)) = self.map.shift_remove_index(0) { self.send_remove(first_key, first_item.value); } } - self.map.insert(interned_key.clone(), item.clone()); + self.map.insert(key.clone(), item.clone()); - self.send_insert(interned_key, item.value); + self.send_insert(key, item.value); } /// Inserts a key-value pair with a specific TTL. @@ -806,19 +790,11 @@ impl Cache { /// ``` #[inline] pub fn get(&mut self, key: &str) -> Option<&Value> { - let pooled_key = if key.len() <= 50 { - Some(self.string_pool.get_or_intern(key)) - } else { - None - }; - - let lookup_key = pooled_key.as_deref().unwrap_or(key); - - if let Some((_, item)) = self.map.get_key_value(lookup_key) { + if let Some((_, item)) = self.map.get_key_value(key) { item.prefetch_read(); } - let is_expired = match self.map.get(lookup_key) { + let is_expired = match self.map.get(key) { Some(item) => { if let Some(ttl) = item.ttl_millis { (current_time_millis() - item.created_at) > ttl @@ -830,12 +806,12 @@ impl Cache { }; if is_expired { - if let Some(expired_item) = self.map.swap_remove(lookup_key) { - self.send_remove(lookup_key.to_string(), expired_item.value); + if let Some(expired_item) = self.map.swap_remove(key) { + self.send_remove(key.to_string(), expired_item.value); } None } else { - self.map.get(lookup_key).map(|item| &item.value) + self.map.get(key).map(|item| &item.value) } } @@ -874,16 +850,8 @@ impl Cache { } pub fn remove(&mut self, key: &str) -> Result<(), Error> { - let pooled_key = if key.len() <= 50 { - Some(self.string_pool.get_or_intern(key)) - } else { - None - }; - - let lookup_key = pooled_key.as_deref().unwrap_or(key); - - if let Some(item) = self.map.swap_remove(lookup_key) { - self.send_remove(lookup_key.to_string(), item.value); + if let Some(item) = self.map.swap_remove(key) { + self.send_remove(key.to_string(), item.value); Ok(()) } else { Err(Error::KeyNotFound) @@ -892,7 +860,6 @@ impl Cache { pub fn clear(&mut self) { self.map.clear(); - self.string_pool.clear(); self.send_clear(); } diff --git a/src/fast_filters.rs b/src/fast_filters.rs deleted file mode 100644 index 012127e..0000000 --- a/src/fast_filters.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! Optimized filter operations for better performance - -use crate::filter::Filter; - -/// Fast prefix matching using byte-level operations -#[inline] -pub fn fast_prefix_match(text: &str, prefix: &str) -> bool { - if prefix.is_empty() { - return true; - } - if text.len() < prefix.len() { - return false; - } - - unsafe { - let text_bytes = text.as_bytes(); - let prefix_bytes = prefix.as_bytes(); - - let chunks = prefix_bytes.len() / 8; - let mut i = 0; - - for _ in 0..chunks { - let text_chunk = std::ptr::read_unaligned(text_bytes.as_ptr().add(i) as *const u64); - let prefix_chunk = std::ptr::read_unaligned(prefix_bytes.as_ptr().add(i) as *const u64); - - if text_chunk != prefix_chunk { - return false; - } - i += 8; - } - - for j in i..prefix_bytes.len() { - if text_bytes[j] != prefix_bytes[j] { - return false; - } - } - } - - true -} - -/// Fast suffix matching optimized for common cases -#[inline] -pub fn fast_suffix_match(text: &str, suffix: &str) -> bool { - if suffix.is_empty() { - return true; - } - if text.len() < suffix.len() { - return false; - } - - let text_bytes = text.as_bytes(); - let suffix_bytes = suffix.as_bytes(); - let start_pos = text_bytes.len() - suffix_bytes.len(); - - unsafe { - let text_suffix = text_bytes.as_ptr().add(start_pos); - let suffix_ptr = suffix_bytes.as_ptr(); - - libc::memcmp( - text_suffix as *const libc::c_void, - suffix_ptr as *const libc::c_void, - suffix_bytes.len(), - ) == 0 - } -} - -/// Optimized filter application -#[inline] -pub fn apply_filter_fast(key: &str, filter: &Filter) -> bool { - match filter { - Filter::None => true, - Filter::StartWith(prefix) => fast_prefix_match(key, prefix), - Filter::EndWith(suffix) => fast_suffix_match(key, suffix), - Filter::StartAndEndWith(prefix, suffix) => { - fast_prefix_match(key, prefix) && fast_suffix_match(key, suffix) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_fast_prefix_match() { - assert!(fast_prefix_match("hello_world", "hello")); - assert!(fast_prefix_match("hello", "hello")); - assert!(!fast_prefix_match("hello", "hello_world")); - assert!(fast_prefix_match("test", "")); - } - - #[test] - fn test_fast_suffix_match() { - assert!(fast_suffix_match("hello_world", "world")); - assert!(fast_suffix_match("world", "world")); - assert!(!fast_suffix_match("world", "hello_world")); - assert!(fast_suffix_match("test", "")); - } -} diff --git a/src/filters.rs b/src/filters.rs new file mode 100644 index 0000000..177b088 --- /dev/null +++ b/src/filters.rs @@ -0,0 +1,69 @@ +//! Optimized filter operations - simple and fast! + +use crate::filter::Filter; + +/// Fast and safe prefix matching using Rust's optimized implementation +#[inline(always)] +pub fn fast_prefix_match(text: &str, prefix: &str) -> bool { + text.starts_with(prefix) +} + +/// Fast and safe suffix matching using Rust's optimized implementation +#[inline(always)] +pub fn fast_suffix_match(text: &str, suffix: &str) -> bool { + text.ends_with(suffix) +} + +/// Optimized filter application - same interface, better performance +#[inline] +pub fn apply_filter_fast(key: &str, filter: &Filter) -> bool { + match filter { + Filter::None => true, + Filter::StartWith(prefix) => key.starts_with(prefix), + Filter::EndWith(suffix) => key.ends_with(suffix), + Filter::StartAndEndWith(prefix, suffix) => key.starts_with(prefix) && key.ends_with(suffix), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::filter::Filter; + + #[test] + fn test_fast_prefix_match() { + assert!(fast_prefix_match("hello_world", "hello")); + assert!(fast_prefix_match("hello", "hello")); + assert!(!fast_prefix_match("hello", "hello_world")); + assert!(fast_prefix_match("test", "")); + } + + #[test] + fn test_fast_suffix_match() { + assert!(fast_suffix_match("hello_world", "world")); + assert!(fast_suffix_match("world", "world")); + assert!(!fast_suffix_match("world", "hello_world")); + assert!(fast_suffix_match("test", "")); + } + + #[test] + fn test_apply_filter_fast() { + assert!(apply_filter_fast("test", &Filter::None)); + assert!(apply_filter_fast( + "hello_world", + &Filter::StartWith("hello".to_string()) + )); + assert!(apply_filter_fast( + "hello_world", + &Filter::EndWith("world".to_string()) + )); + assert!(apply_filter_fast( + "hello_world", + &Filter::StartAndEndWith("hello".to_string(), "world".to_string()) + )); + assert!(!apply_filter_fast( + "hello_world", + &Filter::StartWith("goodbye".to_string()) + )); + } +} diff --git a/src/lib.rs b/src/lib.rs index ceb91b2..70b2fa5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -327,8 +327,8 @@ mod cache; mod error; mod event; -mod fast_filters; mod filter; +pub mod filters; mod list_props; #[cfg(test)] #[cfg(feature = "persist")] @@ -338,7 +338,6 @@ pub mod prelude; mod quickleaf; #[cfg(feature = "persist")] mod sqlite_store; -mod string_pool; #[cfg(test)] mod tests; #[cfg(test)] diff --git a/src/string_pool.rs b/src/string_pool.rs deleted file mode 100644 index 2955c17..0000000 --- a/src/string_pool.rs +++ /dev/null @@ -1,55 +0,0 @@ -use hashbrown::HashMap; -use std::sync::Arc; - -/// String pool para reutilizar strings e reduzir alocações -/// Especialmente útil para keys repetitivas -#[derive(Debug, Clone)] -pub struct StringPool { - pool: HashMap>, -} - -impl StringPool { - #[inline] - pub fn new() -> Self { - Self { - pool: HashMap::with_capacity(512), - } - } - - /// Get or intern a string - #[inline] - pub fn get_or_intern(&mut self, s: &str) -> Arc { - if let Some(interned) = self.pool.get(s) { - Arc::clone(interned) - } else { - let interned: Arc = s.into(); - self.pool.insert(s.to_string(), Arc::clone(&interned)); - interned - } - } - - /// Clear the pool if it gets too large - #[inline] - pub fn clear_if_large(&mut self) { - if self.pool.len() > 10_000 { - self.pool.clear(); - } - } - - /// Clear the entire pool - #[inline] - pub fn clear(&mut self) { - self.pool.clear(); - } - - #[inline] - pub fn len(&self) -> usize { - self.pool.len() - } -} - -impl Default for StringPool { - fn default() -> Self { - Self::new() - } -}