diff --git a/Cargo.toml b/Cargo.toml index 24f3bdf..201dd30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "map-api" description = "Raft state machine" -version = "0.3.2" +version = "0.4.0" authors = ["Databend Authors "] license = "Apache-2.0" edition = "2021" diff --git a/src/mvcc/commit.rs b/src/mvcc/commit.rs index 16faad6..7af2ef4 100644 --- a/src/mvcc/commit.rs +++ b/src/mvcc/commit.rs @@ -41,3 +41,232 @@ where changes: BTreeMap>, ) -> Result<(), io::Error>; } + +#[async_trait::async_trait] +impl Commit for BTreeMap> +where + Self: Send + Sync, + S: ViewNamespace, + K: ViewKey, + V: ViewValue, +{ + async fn commit( + &mut self, + last_seq: InternalSeq, + changes: BTreeMap>, + ) -> Result<(), io::Error> { + let _ = last_seq; + for (space, table) in changes { + // insert the table if absent + self.entry(space).or_default(); + + let dst = self.get_mut(&space).unwrap(); + + dst.apply(table); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use seq_marked::InternalSeq; + use seq_marked::SeqMarked; + + use super::*; + + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + enum TestSpace { + A, + B, + } + + impl ViewNamespace for TestSpace { + fn increments_seq(&self) -> bool { + true + } + } + + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + struct TestKey(String); + + #[derive(Debug, Clone, PartialEq, Eq)] + struct TestValue(String); + + fn k(s: &str) -> TestKey { + TestKey(s.to_string()) + } + + fn v(s: &str) -> TestValue { + TestValue(s.to_string()) + } + + fn test_btreemap() -> BTreeMap> { + let mut table = Table::new(); + table.insert(k("k1"), 1, v("v1")).unwrap(); + table.insert(k("k2"), 2, v("v2")).unwrap(); + + let mut map = BTreeMap::new(); + map.insert(TestSpace::A, table); + map + } + + #[tokio::test] + async fn test_commit_empty() { + let mut map = test_btreemap(); + + map.commit(InternalSeq::new(0), BTreeMap::new()) + .await + .unwrap(); + + assert_eq!(map.len(), 1); + let table = &map[&TestSpace::A]; + assert_eq!(table.get(k("k1"), 100), SeqMarked::new_normal(1, &v("v1"))); + } + + #[tokio::test] + async fn test_commit_new_space() { + let mut map = test_btreemap(); + let mut table = Table::new(); + table.insert(k("k3"), 5, v("v3")).unwrap(); + + let mut changes = BTreeMap::new(); + changes.insert(TestSpace::B, table); + + map.commit(InternalSeq::new(10), changes).await.unwrap(); + + assert_eq!(map.len(), 2); + let table_b = &map[&TestSpace::B]; + assert_eq!( + table_b.get(k("k3"), 100), + SeqMarked::new_normal(5, &v("v3")) + ); + } + + #[tokio::test] + async fn test_commit_existing_space() { + let mut map = test_btreemap(); + let mut table = Table::new(); + table.insert(k("k1"), 5, v("v1_new")).unwrap(); + table.insert(k("k3"), 6, v("v3")).unwrap(); + table.insert_tombstone(k("k2"), 7).unwrap(); + + let mut changes = BTreeMap::new(); + changes.insert(TestSpace::A, table); + + map.commit(InternalSeq::new(10), changes).await.unwrap(); + + let table_a = &map[&TestSpace::A]; + assert_eq!( + table_a.get(k("k1"), 100), + SeqMarked::new_normal(5, &v("v1_new")) + ); + assert_eq!(table_a.get(k("k2"), 100), SeqMarked::new_tombstone(7)); + assert_eq!( + table_a.get(k("k3"), 100), + SeqMarked::new_normal(6, &v("v3")) + ); + } + + #[tokio::test] + async fn test_commit_multiple_spaces() { + let mut map = test_btreemap(); + + let mut table_a = Table::new(); + table_a.insert(k("k3"), 10, v("v3")).unwrap(); + + let mut table_b = Table::new(); + table_b.insert(k("k4"), 11, v("v4")).unwrap(); + + let mut changes = BTreeMap::new(); + changes.insert(TestSpace::A, table_a); + changes.insert(TestSpace::B, table_b); + + map.commit(InternalSeq::new(15), changes).await.unwrap(); + + assert_eq!(map.len(), 2); + let table_a = &map[&TestSpace::A]; + let table_b = &map[&TestSpace::B]; + + assert_eq!( + table_a.get(k("k3"), 100), + SeqMarked::new_normal(10, &v("v3")) + ); + assert_eq!( + table_b.get(k("k4"), 100), + SeqMarked::new_normal(11, &v("v4")) + ); + } + + #[tokio::test] + async fn test_commit_versioning() { + let mut map = test_btreemap(); + let mut table = Table::new(); + table.insert(k("k1"), 10, v("new")).unwrap(); + + let mut changes = BTreeMap::new(); + changes.insert(TestSpace::A, table); + + map.commit(InternalSeq::new(15), changes).await.unwrap(); + + let table_a = &map[&TestSpace::A]; + assert_eq!( + table_a.get(k("k1"), 100), + SeqMarked::new_normal(10, &v("new")) + ); + assert_eq!(table_a.get(k("k1"), 5), SeqMarked::new_normal(1, &v("v1"))); + } + + #[tokio::test] + #[should_panic(expected = "assertion failed: self.last_seq <= last_seq")] + async fn test_commit_sequence_conflict() { + let mut map = test_btreemap(); + + let mut table1 = Table::new(); + table1.insert(k("k3"), 10, v("v3")).unwrap(); + let mut changes1 = BTreeMap::new(); + changes1.insert(TestSpace::A, table1); + map.commit(InternalSeq::new(15), changes1).await.unwrap(); + + let mut table2 = Table::new(); + table2.insert(k("k4"), 5, v("v4")).unwrap(); + let mut changes2 = BTreeMap::new(); + changes2.insert(TestSpace::A, table2); + map.commit(InternalSeq::new(20), changes2).await.unwrap(); + } + + #[tokio::test] + async fn test_commit_empty_tables() { + let mut map = BTreeMap::new(); + let empty_table = Table::::new(); + + let mut changes = BTreeMap::new(); + changes.insert(TestSpace::A, empty_table); + + map.commit(InternalSeq::new(0), changes).await.unwrap(); + + assert_eq!(map.len(), 1); + let table_a = &map[&TestSpace::A]; + assert_eq!(table_a.last_seq, SeqMarked::zero()); + assert!(table_a.get(k("any"), 100).is_not_found()); + } + + #[tokio::test] + async fn test_commit_only_tombstones() { + let mut map = test_btreemap(); + let mut table = Table::new(); + table.insert_tombstone(k("k1"), 5).unwrap(); + table.insert_tombstone(k("k_new"), 6).unwrap(); + + let mut changes = BTreeMap::new(); + changes.insert(TestSpace::A, table); + + map.commit(InternalSeq::new(10), changes).await.unwrap(); + + let table_a = &map[&TestSpace::A]; + assert_eq!(table_a.get(k("k1"), 100), SeqMarked::new_tombstone(5)); + assert_eq!(table_a.get(k("k_new"), 100), SeqMarked::new_tombstone(6)); + } +} diff --git a/src/mvcc/key.rs b/src/mvcc/key.rs index 4876cba..a377c39 100644 --- a/src/mvcc/key.rs +++ b/src/mvcc/key.rs @@ -16,7 +16,20 @@ use std::fmt; /// Trait for types that can be used as keys in MVCC operations. /// -/// Keys must be orderable for range queries and serializable for storage. +/// # Requirements +/// +/// Keys must satisfy multiple constraints to work within the MVCC system: +/// - **Ordering**: `Ord` enables range queries and consistent iteration +/// - **Cloning**: `Clone` supports versioning and snapshot operations +/// - **Threading**: `Send + Sync` allows concurrent access across threads +/// - **Debugging**: `Debug` provides troubleshooting capabilities +/// - **Async**: `Unpin` enables use in async contexts +/// +/// # Automatic Implementation +/// +/// This trait is automatically implemented for any type that meets the trait bounds. +/// Common key types include `String`, `u64`, `Vec`, and custom structs that derive +/// the required traits. pub trait ViewKey where Self: Clone + Ord + fmt::Debug + Send + Sync + Unpin + 'static { diff --git a/src/mvcc/mod.rs b/src/mvcc/mod.rs index bf81620..5cf3a92 100644 --- a/src/mvcc/mod.rs +++ b/src/mvcc/mod.rs @@ -12,65 +12,86 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Multi-Version Concurrency Control (MVCC) implementation. +//! Multi-Version Concurrency Control (MVCC) for versioned key-value storage. //! -//! Provides snapshot isolation for concurrent access to versioned key-value data. -//! Operations are organized by namespaces and support both read-only and read-write access patterns. +//! Provides **Read Committed** isolation with atomic transactions and snapshot consistency. +//! Data is organized by namespaces with support for concurrent read-write operations. //! -//! # Core Components +//! # Architecture //! -//! - **[`Table`]**: In-memory storage for versioned key-value pairs -//! - **[`View`]**: Read-write transactional view with staged changes -//! - **Snapshot traits**: Read-only access at specific sequence points -//! - **Scoped traits**: Namespace-bound operations for convenience +//! - **[`Table`]**: In-memory versioned storage with sequence-based ordering +//! - **[`View`]**: Read-write transaction with staged changes and commit capability +//! - **[`Snapshot`]**: Read-only point-in-time view with fixed sequence boundary +//! - **Scoped APIs**: Namespace-bound convenience methods ([`ScopedApi`], [`ScopedGet`], etc.) +//! +//! # Key Features +//! +//! - **Snapshot Isolation**: Each transaction sees consistent data from start time +//! - **Atomic Commits**: All changes in a transaction commit together or fail together +//! - **Namespace Partitioning**: Logical separation of data domains +//! - **Streaming Range Queries**: Memory-efficient iteration over large datasets //! //! # Usage //! //! ```rust,ignore -//! // Create storage and view +//! // Basic transaction workflow //! let table = Table::new(); //! let mut view = View::new(table); //! -//! // Perform operations -//! view.set(namespace, key, Some(value)); -//! let result = view.get(namespace, key).await?; +//! // Stage changes +//! view.set(namespace, "key1".to_string(), Some("value1".to_string())); +//! view.set(namespace, "key2".to_string(), None); // deletion +//! +//! // Read with staged changes visible +//! let current = view.get(namespace, "key1".to_string()).await?; //! -//! // Commit changes +//! // Atomic commit //! let updated_table = view.commit().await?; //! ``` pub mod commit; pub mod key; -pub mod namespace_view; -pub mod scoped_snapshot_get; -pub mod scoped_snapshot_into_range; -pub mod scoped_snapshot_range; -pub mod scoped_snapshot_range_iter; -pub mod scoped_view; -pub mod scoped_view_readonly; -pub mod snapshot_get; -pub mod snapshot_into_range; -pub mod snapshot_range_iter; +pub mod scoped_api; +pub mod scoped_get; +pub mod scoped_range; +pub mod scoped_read; +pub mod scoped_seq_bounded_get; +pub mod scoped_seq_bounded_into_range; +pub mod scoped_seq_bounded_range; +pub mod scoped_seq_bounded_range_iter; +pub mod scoped_set; +pub mod seq_bounded_get; +pub mod seq_bounded_into_range; +pub mod seq_bounded_range; +pub mod seq_bounded_range_iter; +pub mod seq_bounded_read; +pub mod snapshot; +pub mod snapshot_seq; pub mod table; pub mod value; pub mod view; pub mod view_namespace; -pub mod view_readonly; #[cfg(test)] mod namespace_view_no_seq_increase_test; pub use self::commit::Commit; pub use self::key::ViewKey; -pub use self::scoped_snapshot_get::ScopedSnapshotGet; -pub use self::scoped_snapshot_into_range::ScopedSnapshotIntoRange; -pub use self::scoped_snapshot_range::ScopedSnapshotRange; -pub use self::scoped_snapshot_range_iter::ScopedSnapshotRangeIter; -pub use self::scoped_view::ScopedView; -pub use self::scoped_view_readonly::ScopedViewReadonly; +pub use self::scoped_api::ScopedApi; +pub use self::scoped_get::ScopedGet; +pub use self::scoped_range::ScopedRange; +pub use self::scoped_read::ScopedRead; +pub use self::scoped_seq_bounded_get::ScopedSeqBoundedGet; +pub use self::scoped_seq_bounded_into_range::ScopedSnapshotIntoRange; +pub use self::scoped_seq_bounded_range::ScopedSeqBoundedRange; +pub use self::scoped_seq_bounded_range_iter::ScopedSeqBoundedRangeIter; +pub use self::scoped_set::ScopedSet; +pub use self::seq_bounded_get::SeqBoundedGet; +pub use self::seq_bounded_range::SeqBoundedRange; +pub use self::snapshot::Snapshot; +pub use self::snapshot_seq::SnapshotSeq; pub use self::table::Table; -pub use self::table::TableViewReadonly; +pub use self::table::TablesSnapshot; pub use self::value::ViewValue; pub use self::view::View; pub use self::view_namespace::ViewNamespace; -pub use self::view_readonly::ViewReadonly; diff --git a/src/mvcc/namespace_view.rs b/src/mvcc/namespace_view.rs deleted file mode 100644 index afbd9c1..0000000 --- a/src/mvcc/namespace_view.rs +++ /dev/null @@ -1,448 +0,0 @@ -// Copyright 2021 Datafuse Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::io; -use std::ops::RangeBounds; - -use seq_marked::InternalSeq; -use seq_marked::SeqMarked; - -use crate::mvcc::Commit; -use crate::mvcc::ScopedView; -use crate::mvcc::ScopedViewReadonly; -use crate::mvcc::View; -use crate::mvcc::ViewKey; -use crate::mvcc::ViewNamespace; -use crate::mvcc::ViewReadonly; -use crate::mvcc::ViewValue; -use crate::IOResultStream; - -/// A view scoped to a specific namespace for convenient operations. -/// -/// Provides namespace-scoped access to an underlying `View`, eliminating -/// the need to specify the namespace parameter for each operation. -pub struct NamespaceView<'a, S, K, V, BaseView> -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, - BaseView: ViewReadonly + Commit, -{ - pub space: S, - pub view: &'a mut View, -} - -impl NamespaceView<'_, S, K, V, BaseView> -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, - BaseView: ViewReadonly + Commit, -{ - pub fn set(&mut self, key: K, value: Option) -> SeqMarked<()> { - self.view.set(self.space, key, value) - } - - pub async fn get(&self, key: K) -> Result, io::Error> { - self.view.get(self.space, key).await - } - - pub async fn range(&self, range: R) -> Result)>, io::Error> - where R: RangeBounds + Send + Sync + Clone + 'static { - self.view.range(self.space, range).await - } -} - -#[async_trait::async_trait] -impl ScopedViewReadonly for NamespaceView<'_, S, K, V, BaseView> -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, - BaseView: ViewReadonly + Commit, -{ - fn base_seq(&self) -> InternalSeq { - self.view.view_seq() - } - - async fn get(&self, key: K) -> Result, io::Error> { - self.view.get(self.space, key).await - } - - async fn range(&self, range: R) -> Result)>, io::Error> - where R: RangeBounds + Send + Sync + Clone + 'static { - self.view.range(self.space, range).await - } -} - -#[async_trait::async_trait] -impl ScopedView for NamespaceView<'_, S, K, V, BaseView> -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, - BaseView: ViewReadonly + Commit, -{ - fn set(&mut self, key: K, value: Option) -> SeqMarked<()> { - self.view.set(self.space, key, value) - } -} - -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - - use futures::StreamExt; - use seq_marked::InternalSeq; - use seq_marked::SeqMarked; - - use super::*; - use crate::mvcc::table::TableViewReadonly; - use crate::mvcc::Table; - - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - enum TestSpace { - Space1, - } - - impl ViewNamespace for TestSpace { - fn increments_seq(&self) -> bool { - true - } - } - - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] - struct TestKey(String); - - #[derive(Debug, Clone, PartialEq, Eq)] - struct TestValue(String); - - fn key(s: &str) -> TestKey { - TestKey(s.to_string()) - } - fn value(s: &str) -> TestValue { - TestValue(s.to_string()) - } - - fn create_base_view() -> TableViewReadonly { - let mut table = Table::new(); - table.insert(key("base_k1"), 1, value("base_v1")).unwrap(); - table.insert(key("base_k2"), 2, value("base_v2")).unwrap(); - - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, table); - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); // Set high enough to see all base data - view - } - - #[tokio::test] - async fn test_namespace_delegation() { - let mut view = View::new(create_base_view()); - - // Test delegation through namespace view - let mut ns = view.namespace(TestSpace::Space1); - let order_key = ns.set(key("k1"), Some(value("v1"))); - - // Should return the order key - assert_eq!(order_key, SeqMarked::new_normal(11, ())); - - // Should be equivalent to direct call - assert_eq!(view.last_seq, InternalSeq::new(11)); - let table = &view.changes[&TestSpace::Space1]; - assert_eq!( - table.get(key("k1"), 11), - SeqMarked::new_normal(11, &value("v1")) - ); - } - - #[tokio::test] - async fn test_namespace_get_delegation() { - let mut view = View::new(create_base_view()); - let _ = view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); - - let ns = view.namespace(TestSpace::Space1); - let result = ns.get(key("base_k1")).await.unwrap(); - - // Should match direct get call - assert_eq!(result, SeqMarked::new_normal(1, value("base_v1"))); - } - - #[tokio::test] - async fn test_namespace_get_multiple_delegation() { - let mut view = View::new(create_base_view()); - let _ = view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); - - let ns = view.namespace(TestSpace::Space1); - let result1 = ns.get(key("base_k1")).await.unwrap(); - let result2 = ns.get(key("k1")).await.unwrap(); - - // Should match direct get calls - assert_eq!(result1, SeqMarked::new_normal(1, value("base_v1"))); - assert_eq!(result2, SeqMarked::new_normal(11, value("v1"))); - } - - #[tokio::test] - async fn test_namespace_range_delegation() { - let mut view = View::new(create_base_view()); - let _ = view.set(TestSpace::Space1, key("c"), Some(value("vc"))); - - let ns = view.namespace(TestSpace::Space1); - let mut stream = ns.range(..).await.unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - // Should see both base and new data - assert_eq!(results.len(), 3); - assert_eq!( - results[0], - (key("base_k1"), SeqMarked::new_normal(1, value("base_v1"))) - ); - assert_eq!( - results[1], - (key("base_k2"), SeqMarked::new_normal(2, value("base_v2"))) - ); - assert_eq!( - results[2], - (key("c"), SeqMarked::new_normal(11, value("vc"))) - ); - } - - #[tokio::test] - async fn test_scoped_view_readonly_base_seq() { - let mut view = View::new(create_base_view()); - let ns = view.namespace(TestSpace::Space1); - - // Should delegate to underlying view's base_seq - assert_eq!(ns.base_seq(), InternalSeq::new(10)); - } - - #[tokio::test] - async fn test_scoped_view_readonly_get() { - let mut view = View::new(create_base_view()); - let _ = view.set( - TestSpace::Space1, - key("test_key"), - Some(value("test_value")), - ); - - let ns = view.namespace(TestSpace::Space1); - - // Should get from base view - let result1 = ns.get(key("base_k1")).await.unwrap(); - assert_eq!(result1, SeqMarked::new_normal(1, value("base_v1"))); - - // Should get from changes - let result2 = ns.get(key("test_key")).await.unwrap(); - assert_eq!(result2, SeqMarked::new_normal(11, value("test_value"))); - } - - #[tokio::test] - async fn test_scoped_view_readonly_range() { - let mut view = View::new(create_base_view()); - let _ = view.set(TestSpace::Space1, key("new_key"), Some(value("new_value"))); - - let ns = view.namespace(TestSpace::Space1); - - let mut stream = ns.range(..).await.unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 3); - assert_eq!( - results[0], - (key("base_k1"), SeqMarked::new_normal(1, value("base_v1"))) - ); - assert_eq!( - results[1], - (key("base_k2"), SeqMarked::new_normal(2, value("base_v2"))) - ); - assert_eq!( - results[2], - ( - key("new_key"), - SeqMarked::new_normal(11, value("new_value")) - ) - ); - } - - #[tokio::test] - async fn test_scoped_view_set() { - let mut view = View::new(create_base_view()); - let mut ns = view.namespace(TestSpace::Space1); - - let order_key = ns.set(key("scoped_key"), Some(value("scoped_value"))); - - // Should return the order key - assert_eq!(order_key, SeqMarked::new_normal(11, ())); - - // Should be reflected in the underlying view - let result = view - .get(TestSpace::Space1, key("scoped_key")) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_normal(11, value("scoped_value"))); - } - - #[tokio::test] - async fn test_scoped_view_set_tombstone() { - let mut view = View::new(create_base_view()); - let mut ns = view.namespace(TestSpace::Space1); - - let order_key = ns.set(key("tombstone_key"), None); - - // Should return the order key for tombstone - assert_eq!(order_key, SeqMarked::new_tombstone(10)); - - // Should create a tombstone in the underlying view - let result = view - .get(TestSpace::Space1, key("tombstone_key")) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_tombstone(10)); - } - - #[tokio::test] - async fn test_scoped_view_traits_consistency() { - let mut view = View::new(create_base_view()); - let mut ns = view.namespace(TestSpace::Space1); - - // Test that scoped view operations are equivalent to direct view operations - let order_key = ns.set(key("consistency_key"), Some(value("consistency_value"))); - - // Should return the order key - assert_eq!(order_key, SeqMarked::new_normal(11, ())); - - let scoped_result = ns.get(key("consistency_key")).await.unwrap(); - let direct_result = view - .get(TestSpace::Space1, key("consistency_key")) - .await - .unwrap(); - - assert_eq!(scoped_result, direct_result); - assert_eq!( - scoped_result, - SeqMarked::new_normal(11, value("consistency_value")) - ); - } - - #[tokio::test] - async fn test_scoped_view_fetch_and_set_nonexistent() { - let mut view = View::new(create_base_view()); - let mut ns = view.namespace(TestSpace::Space1); - - let (old_value, new_value) = ns - .fetch_and_set(key("new_key"), Some(value("new_value"))) - .await - .unwrap(); - - // Should return not_found for old value - assert_eq!(old_value, SeqMarked::new_not_found()); - // Should return the new value - assert_eq!(new_value, SeqMarked::new_normal(11, value("new_value"))); - - // Verify the value was actually set in the underlying view - let result = view.get(TestSpace::Space1, key("new_key")).await.unwrap(); - assert_eq!(result, SeqMarked::new_normal(11, value("new_value"))); - } - - #[tokio::test] - async fn test_scoped_view_fetch_and_set_existing() { - let mut view = View::new(create_base_view()); - let mut ns = view.namespace(TestSpace::Space1); - - let (old_value, new_value) = ns - .fetch_and_set(key("base_k1"), Some(value("updated_value"))) - .await - .unwrap(); - - // Should return the old value from base - assert_eq!(old_value, SeqMarked::new_normal(1, value("base_v1"))); - // Should return the new value - assert_eq!(new_value, SeqMarked::new_normal(11, value("updated_value"))); - - // Verify the value was updated in the underlying view - let result = view.get(TestSpace::Space1, key("base_k1")).await.unwrap(); - assert_eq!(result, SeqMarked::new_normal(11, value("updated_value"))); - } - - #[tokio::test] - async fn test_scoped_view_fetch_and_set_tombstone() { - let mut view = View::new(create_base_view()); - let mut ns = view.namespace(TestSpace::Space1); - - let (old_value, new_value) = ns.fetch_and_set(key("base_k1"), None).await.unwrap(); - - // Should return the old value from base - assert_eq!(old_value, SeqMarked::new_normal(1, value("base_v1"))); - // Should return tombstone as new value - assert_eq!(new_value, SeqMarked::new_tombstone(10)); - - // Verify the value was deleted in the underlying view - let result = view.get(TestSpace::Space1, key("base_k1")).await.unwrap(); - assert_eq!(result, SeqMarked::new_tombstone(10)); - } - - #[tokio::test] - async fn test_scoped_view_fetch_and_set_with_changes() { - let mut view = View::new(create_base_view()); - let mut ns = view.namespace(TestSpace::Space1); - - // First set a value to create changes - ns.set(key("test_key"), Some(value("first_value"))); - - let (old_value, new_value) = ns - .fetch_and_set(key("test_key"), Some(value("second_value"))) - .await - .unwrap(); - - // Should return the old value from changes - assert_eq!(old_value, SeqMarked::new_normal(11, value("first_value"))); - // Should return the new value - assert_eq!(new_value, SeqMarked::new_normal(12, value("second_value"))); - - // Verify the value was updated - let result = view.get(TestSpace::Space1, key("test_key")).await.unwrap(); - assert_eq!(result, SeqMarked::new_normal(12, value("second_value"))); - } - - #[tokio::test] - async fn test_scoped_view_fetch_and_set_sequence_consistency() { - let mut view = View::new(create_base_view()); - let mut ns = view.namespace(TestSpace::Space1); - - // Multiple fetch_and_set operations - let (old1, new1) = ns - .fetch_and_set(key("k1"), Some(value("v1"))) - .await - .unwrap(); - let (old2, new2) = ns - .fetch_and_set(key("k2"), Some(value("v2"))) - .await - .unwrap(); - - // Verify sequence ordering - assert_eq!(old1, SeqMarked::new_not_found()); - assert_eq!(old2, SeqMarked::new_not_found()); - assert_eq!(new1, SeqMarked::new_normal(11, value("v1"))); - assert_eq!(new2, SeqMarked::new_normal(12, value("v2"))); - - // Verify the view's last_seq was updated correctly - assert_eq!(view.last_seq, InternalSeq::new(12)); - } -} diff --git a/src/mvcc/namespace_view_no_seq_increase_test.rs b/src/mvcc/namespace_view_no_seq_increase_test.rs index 90e1648..8d40898 100644 --- a/src/mvcc/namespace_view_no_seq_increase_test.rs +++ b/src/mvcc/namespace_view_no_seq_increase_test.rs @@ -20,7 +20,7 @@ mod tests { use seq_marked::InternalSeq; use seq_marked::SeqMarked; - use crate::mvcc::table::TableViewReadonly; + use crate::mvcc::table::TablesSnapshot; use crate::mvcc::view::View; use crate::mvcc::view_namespace::ViewNamespace; use crate::mvcc::Table; @@ -50,16 +50,16 @@ mod tests { TestValue(s.to_string()) } - fn create_base_view() -> TableViewReadonly { + fn create_base_view() -> TablesSnapshot { let mut table = Table::new(); table.insert(key("base_k1"), 1, value("base_v1")).unwrap(); table.insert(key("base_k2"), 2, value("base_v2")).unwrap(); let mut tables = BTreeMap::new(); tables.insert(TestSpaceNoSeqIncrease::Space1, table); - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); // Set high enough to see all base data - view + // Set high enough to see all base data + + TablesSnapshot::new(InternalSeq::new(10), tables) } #[tokio::test] @@ -299,7 +299,8 @@ mod tests { } let tables = BTreeMap::new(); - let base = TableViewReadonly::::new(tables); + let base = + TablesSnapshot::::new(InternalSeq::new(10), tables); let mut view = View::new(base).with_initial_seq(InternalSeq::new(100)); let initial_seq = view.last_seq; @@ -339,7 +340,10 @@ mod tests { // Test with empty base view (starts at sequence 0) // This demonstrates that the constraint applies even at sequence 0 let tables = BTreeMap::new(); - let base = TableViewReadonly::::new(tables); + let base = TablesSnapshot::::new( + InternalSeq::new(1), + tables, + ); let mut view = View::new(base).with_initial_seq(InternalSeq::new(1)); assert_eq!(view.last_seq, InternalSeq::new(1)); diff --git a/src/mvcc/scoped_api.rs b/src/mvcc/scoped_api.rs new file mode 100644 index 0000000..33b4bf3 --- /dev/null +++ b/src/mvcc/scoped_api.rs @@ -0,0 +1,40 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::mvcc::ScopedGet; +use crate::mvcc::ScopedRange; +use crate::mvcc::ScopedSet; +use crate::mvcc::ViewKey; +use crate::mvcc::ViewValue; + +#[async_trait::async_trait] +pub trait ScopedApi +where + K: ViewKey, + V: ViewValue, + Self: ScopedGet, + Self: ScopedSet, + Self: ScopedRange, +{ +} + +impl ScopedApi for T +where + K: ViewKey, + V: ViewValue, + T: ScopedGet, + T: ScopedSet, + T: ScopedRange, +{ +} diff --git a/src/mvcc/scoped_get.rs b/src/mvcc/scoped_get.rs new file mode 100644 index 0000000..caa9467 --- /dev/null +++ b/src/mvcc/scoped_get.rs @@ -0,0 +1,37 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::io; + +use seq_marked::SeqMarked; + +use crate::mvcc::ViewKey; +use crate::mvcc::ViewValue; + +/// Read-only range view bound to a namespace that consumes self with snapshot isolation. +/// +/// Operations are bounded by `snapshot_seq`, ensuring only data with sequences ≤ `snapshot_seq` +/// is visible. Pre-scoped to eliminate namespace parameters. +/// +/// ⚠️ **Tombstone Anomaly**: May observe different deletion states for keys with identical sequences. +#[async_trait::async_trait] +pub trait ScopedGet +where + Self: Send + Sync, + K: ViewKey, + V: ViewValue, +{ + /// Returns an async stream of key-value pairs within the specified range, consuming self. + async fn get(&self, key: K) -> Result, io::Error>; +} diff --git a/src/mvcc/scoped_range.rs b/src/mvcc/scoped_range.rs new file mode 100644 index 0000000..4b0fc54 --- /dev/null +++ b/src/mvcc/scoped_range.rs @@ -0,0 +1,40 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::io; +use std::ops::RangeBounds; + +use seq_marked::SeqMarked; + +use crate::mvcc::ViewKey; +use crate::mvcc::ViewValue; +use crate::IOResultStream; + +/// Read-only range view bound to a namespace that consumes self with snapshot isolation. +/// +/// Operations are bounded by `snapshot_seq`, ensuring only data with sequences ≤ `snapshot_seq` +/// is visible. Pre-scoped to eliminate namespace parameters. +/// +/// ⚠️ **Tombstone Anomaly**: May observe different deletion states for keys with identical sequences. +#[async_trait::async_trait] +pub trait ScopedRange +where + Self: Send + Sync, + K: ViewKey, + V: ViewValue, +{ + /// Returns an async stream of key-value pairs within the specified range, consuming self. + async fn range(&self, range: R) -> Result)>, io::Error> + where R: RangeBounds + Send + Sync + Clone + 'static; +} diff --git a/src/mvcc/scoped_read.rs b/src/mvcc/scoped_read.rs new file mode 100644 index 0000000..e441fab --- /dev/null +++ b/src/mvcc/scoped_read.rs @@ -0,0 +1,37 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::mvcc::ScopedGet; +use crate::mvcc::ScopedRange; +use crate::mvcc::ViewKey; +use crate::mvcc::ViewValue; + +#[async_trait::async_trait] +pub trait ScopedRead +where + K: ViewKey, + V: ViewValue, + Self: ScopedGet, + Self: ScopedRange, +{ +} + +impl ScopedRead for T +where + K: ViewKey, + V: ViewValue, + T: ScopedGet, + T: ScopedRange, +{ +} diff --git a/src/mvcc/scoped_snapshot_get.rs b/src/mvcc/scoped_seq_bounded_get.rs similarity index 98% rename from src/mvcc/scoped_snapshot_get.rs rename to src/mvcc/scoped_seq_bounded_get.rs index fa5a69c..a20ea86 100644 --- a/src/mvcc/scoped_snapshot_get.rs +++ b/src/mvcc/scoped_seq_bounded_get.rs @@ -27,7 +27,7 @@ use crate::mvcc::ViewValue; /// /// ⚠️ **Tombstone Anomaly**: May observe different deletion states for keys with identical sequences. #[async_trait::async_trait] -pub trait ScopedSnapshotGet +pub trait ScopedSeqBoundedGet where Self: Send + Sync, K: ViewKey, @@ -97,7 +97,7 @@ mod tests { } #[async_trait::async_trait] - impl ScopedSnapshotGet for MockScopedSnapshotReader { + impl ScopedSeqBoundedGet for MockScopedSnapshotReader { async fn get( &self, key: TestKey, diff --git a/src/mvcc/scoped_snapshot_into_range.rs b/src/mvcc/scoped_seq_bounded_into_range.rs similarity index 92% rename from src/mvcc/scoped_snapshot_into_range.rs rename to src/mvcc/scoped_seq_bounded_into_range.rs index e4e38b2..d0d9b5e 100644 --- a/src/mvcc/scoped_snapshot_into_range.rs +++ b/src/mvcc/scoped_seq_bounded_into_range.rs @@ -20,7 +20,7 @@ use std::ops::RangeBounds; use futures_util::StreamExt; use seq_marked::SeqMarked; -use crate::mvcc::scoped_snapshot_range_iter::ScopedSnapshotRangeIter; +use crate::mvcc::scoped_seq_bounded_range_iter::ScopedSeqBoundedRangeIter; use crate::mvcc::ViewKey; use crate::mvcc::ViewValue; use crate::IOResultStream; @@ -56,7 +56,7 @@ impl ScopedSnapshotIntoRange for Owned where K: ViewKey, V: ViewValue, - Owned: ScopedSnapshotRangeIter + Send + Sync + 'static, + Owned: ScopedSeqBoundedRangeIter + Send + Sync + 'static, { async fn into_range( self, @@ -80,7 +80,7 @@ pub(crate) async fn owned_range_iter_to_stream( K: ViewKey, V: ViewValue, R: RangeBounds + Clone + Send + Sync + 'static, - Owned: ScopedSnapshotRangeIter + Send + Sync + 'static, + Owned: ScopedSeqBoundedRangeIter + Send + Sync + 'static, { let it = table.range_iter(range, snapshot_seq); @@ -96,7 +96,7 @@ mod tests { use futures_util::TryStreamExt; use seq_marked::SeqMarked; - use crate::mvcc::scoped_snapshot_into_range::ScopedSnapshotIntoRange; + use crate::mvcc::scoped_seq_bounded_into_range::ScopedSnapshotIntoRange; use crate::mvcc::Table; #[tokio::test] diff --git a/src/mvcc/scoped_snapshot_range.rs b/src/mvcc/scoped_seq_bounded_range.rs similarity index 97% rename from src/mvcc/scoped_snapshot_range.rs rename to src/mvcc/scoped_seq_bounded_range.rs index 1a73297..b37ba11 100644 --- a/src/mvcc/scoped_snapshot_range.rs +++ b/src/mvcc/scoped_seq_bounded_range.rs @@ -28,7 +28,7 @@ use crate::IOResultStream; /// /// ⚠️ **Tombstone Anomaly**: May observe different deletion states for keys with identical sequences. #[async_trait::async_trait] -pub trait ScopedSnapshotRange +pub trait ScopedSeqBoundedRange where Self: Send + Sync, K: ViewKey, diff --git a/src/mvcc/scoped_snapshot_range_iter.rs b/src/mvcc/scoped_seq_bounded_range_iter.rs similarity index 97% rename from src/mvcc/scoped_snapshot_range_iter.rs rename to src/mvcc/scoped_seq_bounded_range_iter.rs index 384bf6b..612f585 100644 --- a/src/mvcc/scoped_snapshot_range_iter.rs +++ b/src/mvcc/scoped_seq_bounded_range_iter.rs @@ -27,7 +27,7 @@ use crate::mvcc::ViewValue; /// is visible. Pre-scoped to eliminate namespace parameters. /// /// ⚠️ **Tombstone Anomaly**: May observe different deletion states for keys with identical sequences. -pub trait ScopedSnapshotRangeIter +pub trait ScopedSeqBoundedRangeIter where K: ViewKey, V: ViewValue, diff --git a/src/mvcc/scoped_view.rs b/src/mvcc/scoped_set.rs similarity index 84% rename from src/mvcc/scoped_view.rs rename to src/mvcc/scoped_set.rs index d95a7cc..a647daf 100644 --- a/src/mvcc/scoped_view.rs +++ b/src/mvcc/scoped_set.rs @@ -16,7 +16,7 @@ use std::io; use seq_marked::SeqMarked; -use crate::mvcc::ScopedViewReadonly; +use crate::mvcc::ScopedGet; use crate::mvcc::ViewKey; use crate::mvcc::ViewValue; @@ -26,15 +26,15 @@ use crate::mvcc::ViewValue; /// in the view implementation, eliminating the need to specify namespace /// parameters for each operation. /// -/// Extends [`ScopedViewReadonly`] with write operations: +/// Extends [`ScopedGet`] with write operations: /// - [`set`](Self::set) - Set or delete values within the scoped namespace /// - [`fetch_and_set`](Self::fetch_and_set) - Atomically get old value and set new value, returning both #[async_trait::async_trait] -pub trait ScopedView +pub trait ScopedSet where K: ViewKey, V: ViewValue, - Self: ScopedViewReadonly, + Self: ScopedGet, { /// Fetch the current value of a key and set it to a new value atomically. /// @@ -75,14 +75,10 @@ where mod tests { use std::collections::BTreeMap; use std::io; - use std::ops::RangeBounds; - use futures::StreamExt; - use seq_marked::InternalSeq; use seq_marked::SeqMarked; use super::*; - use crate::IOResultStream; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] struct TestKey(String); @@ -99,7 +95,6 @@ mod tests { // Mock implementation for testing struct MockScopedView { - base_seq: InternalSeq, data: BTreeMap>, } @@ -111,46 +106,22 @@ mod tests { SeqMarked::new_normal(1, value("initial_value")), ); - Self { - base_seq: InternalSeq::new(5), - data, - } + Self { data } } } #[async_trait::async_trait] - impl ScopedViewReadonly for MockScopedView { - fn base_seq(&self) -> InternalSeq { - self.base_seq - } - + impl ScopedGet for MockScopedView { async fn get(&self, key: TestKey) -> Result, io::Error> { match self.data.get(&key) { Some(value) => Ok(value.clone()), None => Ok(SeqMarked::new_not_found()), } } - - async fn range( - &self, - range: R, - ) -> Result)>, io::Error> - where - R: RangeBounds + Send + Sync + Clone + 'static, - { - let items: Vec<_> = self - .data - .range(range) - .map(|(k, v)| Ok((k.clone(), v.clone()))) - .collect(); - - let stream = futures::stream::iter(items); - Ok(Box::pin(stream)) - } } #[async_trait::async_trait] - impl ScopedView for MockScopedView { + impl ScopedSet for MockScopedView { fn set(&mut self, key: TestKey, value: Option) -> SeqMarked<()> { match value { Some(v) => { @@ -220,42 +191,10 @@ mod tests { // Should return the order key assert_eq!(order_key, SeqMarked::new_normal(2, ())); - assert_eq!(view.base_seq(), InternalSeq::new(5)); let result = view.get(key("test_key")).await.unwrap(); assert_eq!(result, SeqMarked::new_normal(2, value("test_value"))); } - #[tokio::test] - async fn test_scoped_view_trait_range_after_set() { - let mut view = MockScopedView::new(); - let order_key = view.set(key("added_key"), Some(value("added_value"))); - - // Should return the order key - assert_eq!(order_key, SeqMarked::new_normal(2, ())); - - let mut stream = view.range(..).await.unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 2); - assert_eq!( - results[0], - ( - key("added_key"), - SeqMarked::new_normal(2, value("added_value")) - ) - ); - assert_eq!( - results[1], - ( - key("initial_key"), - SeqMarked::new_normal(1, value("initial_value")) - ) - ); - } - #[tokio::test] async fn test_scoped_view_trait_fetch_and_set_nonexistent() { let mut view = MockScopedView::new(); diff --git a/src/mvcc/scoped_view_readonly.rs b/src/mvcc/scoped_view_readonly.rs deleted file mode 100644 index 129db09..0000000 --- a/src/mvcc/scoped_view_readonly.rs +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2021 Datafuse Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::io; -use std::ops::RangeBounds; - -use seq_marked::InternalSeq; -use seq_marked::SeqMarked; - -use crate::mvcc::ViewKey; -use crate::mvcc::ViewValue; -use crate::IOResultStream; - -/// Read-only access to a namespace-scoped view. -/// -/// Operations are pre-scoped to a namespace, eliminating the need to specify -/// namespace parameters on each call. -#[async_trait::async_trait] -pub trait ScopedViewReadonly -where - K: ViewKey, - V: ViewValue, -{ - /// Maximum sequence number visible in this view. - fn base_seq(&self) -> InternalSeq; - - /// Get a single value by key. - async fn get(&self, key: K) -> Result, io::Error>; - - /// Stream key-value pairs within the specified range. - async fn range(&self, range: R) -> Result)>, io::Error> - where R: RangeBounds + Send + Sync + Clone + 'static; -} - -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - use std::io; - use std::ops::RangeBounds; - - use futures::StreamExt; - use seq_marked::InternalSeq; - use seq_marked::SeqMarked; - - use super::*; - use crate::IOResultStream; - - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] - struct TestKey(String); - - #[derive(Debug, Clone, PartialEq, Eq)] - struct TestValue(String); - - fn key(s: &str) -> TestKey { - TestKey(s.to_string()) - } - fn value(s: &str) -> TestValue { - TestValue(s.to_string()) - } - - // Mock implementation for testing - struct MockScopedView { - base_seq: InternalSeq, - data: BTreeMap>, - } - - impl MockScopedView { - fn new() -> Self { - let mut data = BTreeMap::new(); - data.insert(key("key1"), SeqMarked::new_normal(1, value("value1"))); - data.insert(key("key2"), SeqMarked::new_normal(2, value("value2"))); - data.insert(key("key3"), SeqMarked::new_tombstone(3)); - - Self { - base_seq: InternalSeq::new(10), - data, - } - } - } - - #[async_trait::async_trait] - impl ScopedViewReadonly for MockScopedView { - fn base_seq(&self) -> InternalSeq { - self.base_seq - } - - async fn get(&self, key: TestKey) -> Result, io::Error> { - match self.data.get(&key) { - Some(value) => Ok(value.clone()), - None => Ok(SeqMarked::new_not_found()), - } - } - - async fn range( - &self, - range: R, - ) -> Result)>, io::Error> - where - R: RangeBounds + Send + Sync + Clone + 'static, - { - let items: Vec<_> = self - .data - .range(range) - .map(|(k, v)| Ok((k.clone(), v.clone()))) - .collect(); - - let stream = futures::stream::iter(items); - Ok(Box::pin(stream)) - } - } - - #[tokio::test] - async fn test_scoped_view_readonly_trait_base_seq() { - let view = MockScopedView::new(); - assert_eq!(view.base_seq(), InternalSeq::new(10)); - } - - #[tokio::test] - async fn test_scoped_view_readonly_trait_get_existing() { - let view = MockScopedView::new(); - let result = view.get(key("key1")).await.unwrap(); - assert_eq!(result, SeqMarked::new_normal(1, value("value1"))); - } - - #[tokio::test] - async fn test_scoped_view_readonly_trait_get_tombstone() { - let view = MockScopedView::new(); - let result = view.get(key("key3")).await.unwrap(); - assert_eq!(result, SeqMarked::new_tombstone(3)); - } - - #[tokio::test] - async fn test_scoped_view_readonly_trait_get_not_found() { - let view = MockScopedView::new(); - let result = view.get(key("nonexistent")).await.unwrap(); - assert_eq!(result, SeqMarked::new_not_found()); - } - - #[tokio::test] - async fn test_scoped_view_readonly_trait_range() { - let view = MockScopedView::new(); - let mut stream = view.range(..).await.unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 3); - assert_eq!( - results[0], - (key("key1"), SeqMarked::new_normal(1, value("value1"))) - ); - assert_eq!( - results[1], - (key("key2"), SeqMarked::new_normal(2, value("value2"))) - ); - assert_eq!(results[2], (key("key3"), SeqMarked::new_tombstone(3))); - } - - #[tokio::test] - async fn test_scoped_view_readonly_trait_range_bounded() { - let view = MockScopedView::new(); - let mut stream = view.range(key("key1")..=key("key2")).await.unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 2); - assert_eq!( - results[0], - (key("key1"), SeqMarked::new_normal(1, value("value1"))) - ); - assert_eq!( - results[1], - (key("key2"), SeqMarked::new_normal(2, value("value2"))) - ); - } -} diff --git a/src/mvcc/snapshot_get.rs b/src/mvcc/seq_bounded_get.rs similarity index 80% rename from src/mvcc/snapshot_get.rs rename to src/mvcc/seq_bounded_get.rs index b93e633..52d5195 100644 --- a/src/mvcc/snapshot_get.rs +++ b/src/mvcc/seq_bounded_get.rs @@ -14,10 +14,12 @@ //! Snapshot-based read operations for multi-version key-value storage with namespace isolation. +use std::collections::BTreeMap; use std::io; use seq_marked::SeqMarked; +use crate::mvcc::Table; use crate::mvcc::ViewKey; use crate::mvcc::ViewNamespace; use crate::mvcc::ViewValue; @@ -28,7 +30,7 @@ use crate::mvcc::ViewValue; /// /// ⚠️ **Tombstone Anomaly**: May observe different deletion states due to sequence reuse. #[async_trait::async_trait] -pub trait SnapshotGet +pub trait SeqBoundedGet where Self: Send + Sync, S: ViewNamespace, @@ -59,6 +61,25 @@ where } } +#[async_trait::async_trait] +impl SeqBoundedGet for BTreeMap> +where + Self: Send + Sync, + S: ViewNamespace, + K: ViewKey, + V: ViewValue, +{ + async fn get(&self, space: S, key: K, snapshot_seq: u64) -> Result, io::Error> { + let Some(table) = self.get(&space) else { + return Ok(SeqMarked::new_not_found()); + }; + + let got = table.get(key, snapshot_seq).cloned(); + + Ok(got) + } +} + #[cfg(test)] mod tests { use std::collections::BTreeMap; @@ -125,7 +146,7 @@ mod tests { } #[async_trait::async_trait] - impl SnapshotGet for MockSnapshotReader { + impl SeqBoundedGet for MockSnapshotReader { async fn get( &self, space: TestNamespace, @@ -262,4 +283,47 @@ mod tests { assert!(*results[0].internal_seq() <= snapshot_seq); assert_eq!(results[1], SeqMarked::new_not_found()); // not visible due to seq filter } + + #[tokio::test] + async fn test_btree_map_snapshot_get_implementation() { + use super::SeqBoundedGet; + + let mut map = BTreeMap::new(); + let mut table = Table::new(); + table.insert(key("k"), 1, value("v")).unwrap(); + table.insert_tombstone(key("deleted"), 2).unwrap(); + map.insert(namespace(1), table); + + // Test existing key + let result = SeqBoundedGet::get(&map, namespace(1), key("k"), 10) + .await + .unwrap(); + assert_eq!(result, SeqMarked::new_normal(1, value("v"))); + + // Test deleted key + let result = SeqBoundedGet::get(&map, namespace(1), key("deleted"), 10) + .await + .unwrap(); + assert_eq!(result, SeqMarked::new_tombstone(2)); + + // Test missing key + let result = SeqBoundedGet::get(&map, namespace(1), key("missing"), 10) + .await + .unwrap(); + assert_eq!(result, SeqMarked::new_not_found()); + + // Test missing namespace + let result = SeqBoundedGet::get(&map, namespace(99), key("k"), 10) + .await + .unwrap(); + assert_eq!(result, SeqMarked::new_not_found()); + + // Test get_many + let keys = vec![key("k"), key("missing")]; + let results = SeqBoundedGet::get_many(&map, namespace(1), keys, 10) + .await + .unwrap(); + assert_eq!(results[0], SeqMarked::new_normal(1, value("v"))); + assert_eq!(results[1], SeqMarked::new_not_found()); + } } diff --git a/src/mvcc/snapshot_into_range.rs b/src/mvcc/seq_bounded_into_range.rs similarity index 97% rename from src/mvcc/snapshot_into_range.rs rename to src/mvcc/seq_bounded_into_range.rs index 8de1b3b..5ced62d 100644 --- a/src/mvcc/snapshot_into_range.rs +++ b/src/mvcc/seq_bounded_into_range.rs @@ -31,7 +31,7 @@ use crate::IOResultStream; /// /// ⚠️ **Tombstone Anomaly**: May observe different deletion states due to sequence reuse. #[async_trait::async_trait] -pub trait SnapshotIntoRange +pub trait SeqBoundedIntoRange where Self: Send + Sync, S: ViewNamespace, diff --git a/src/mvcc/seq_bounded_range.rs b/src/mvcc/seq_bounded_range.rs new file mode 100644 index 0000000..7156ee6 --- /dev/null +++ b/src/mvcc/seq_bounded_range.rs @@ -0,0 +1,206 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Range-based read operations for multi-version key-value storage with namespace isolation. + +use std::collections::BTreeMap; +use std::io; +use std::ops::RangeBounds; + +use futures_util::StreamExt; +use seq_marked::SeqMarked; + +use crate::mvcc::Table; +use crate::mvcc::ViewKey; +use crate::mvcc::ViewNamespace; +use crate::mvcc::ViewValue; +use crate::IOResultStream; + +/// Read-only multi-version range view with snapshot isolation. +/// +/// Operations are bounded by `snapshot_seq` to ensure consistent temporal views. +/// Requires namespace parameters for each operation. +/// +/// ⚠️ **Tombstone Anomaly**: May observe different deletion states due to sequence reuse. +#[async_trait::async_trait] +pub trait SeqBoundedRange +where + Self: Send + Sync, + S: ViewNamespace, + K: ViewKey, + V: ViewValue, +{ + /// Returns an async stream of key-value pairs within the specified range. + /// + /// Returns the most recent visible version for each key with sequence ≤ `snapshot_seq`. + /// Keys are returned in sorted order, including tombstones. + async fn range( + &self, + space: S, + range: R, + snapshot_seq: u64, + ) -> Result)>, io::Error> + where + R: RangeBounds + Send + Sync + Clone + 'static; +} + +#[async_trait::async_trait] +impl SeqBoundedRange for BTreeMap> +where + Self: Send + Sync, + S: ViewNamespace, + K: ViewKey, + V: ViewValue, +{ + async fn range( + &self, + space: S, + range: R, + snapshot_seq: u64, + ) -> Result)>, io::Error> + where + R: RangeBounds + Send + Sync + Clone + 'static, + { + let Some(table) = self.get(&space) else { + let strm = futures::stream::empty(); + return Ok(strm.boxed()); + }; + + let it = table.range(range, snapshot_seq); + let vec = it.map(|(k, v)| (k.clone(), v.cloned())).collect::>(); + let strm = futures::stream::iter(vec).map(Ok); + Ok(strm.boxed()) + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use futures_util::StreamExt; + + use super::*; + + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + struct TestNamespace(u8); + + impl ViewNamespace for TestNamespace { + fn increments_seq(&self) -> bool { + true + } + } + + #[tokio::test] + async fn test_empty_map() { + let map: BTreeMap> = BTreeMap::new(); + let result = SeqBoundedRange::range(&map, TestNamespace(1), .., 5) + .await + .unwrap(); + let items: Vec<_> = result.collect().await; + assert!(items.is_empty()); + } + + #[tokio::test] + async fn test_nonexistent_namespace() { + let mut map = BTreeMap::new(); + map.insert(TestNamespace(1), create_test_table()); + + let result = SeqBoundedRange::range(&map, TestNamespace(2), .., 10) + .await + .unwrap(); + let items: Vec<_> = result.collect().await; + assert!(items.is_empty()); + } + + #[tokio::test] + async fn test_existing_table_full_range() { + let mut map = BTreeMap::new(); + map.insert(TestNamespace(1), create_test_table()); + + let result = SeqBoundedRange::range(&map, TestNamespace(1), .., 5) + .await + .unwrap(); + let items: Vec<_> = result.collect().await; + let extracted: Vec<_> = items.into_iter().map(|item| item.unwrap()).collect(); + + let expected = vec![ + (k("k1"), SeqMarked::new_normal(1, v("v1"))), + (k("k2"), SeqMarked::new_normal(3, v("v2"))), + (k("k3"), SeqMarked::new_normal(5, v("v3"))), + ]; + assert_eq!(extracted, expected); + } + + #[tokio::test] + async fn test_bounded_range() { + let mut map = BTreeMap::new(); + map.insert(TestNamespace(1), create_test_table()); + + let result = SeqBoundedRange::range(&map, TestNamespace(1), k("k2")..=k("k3"), 5) + .await + .unwrap(); + let items: Vec<_> = result.collect().await; + let extracted: Vec<_> = items.into_iter().map(|item| item.unwrap()).collect(); + + let expected = vec![ + (k("k2"), SeqMarked::new_normal(3, v("v2"))), + (k("k3"), SeqMarked::new_normal(5, v("v3"))), + ]; + assert_eq!(extracted, expected); + } + + #[tokio::test] + async fn test_different_snapshot_seq() { + let mut map = BTreeMap::new(); + map.insert(TestNamespace(1), create_test_table()); + + let result_early = SeqBoundedRange::range(&map, TestNamespace(1), .., 2) + .await + .unwrap(); + let items_early: Vec<_> = result_early.collect().await; + let extracted_early: Vec<_> = items_early.into_iter().map(|item| item.unwrap()).collect(); + + let result_late = SeqBoundedRange::range(&map, TestNamespace(1), .., 10) + .await + .unwrap(); + let items_late: Vec<_> = result_late.collect().await; + let extracted_late: Vec<_> = items_late.into_iter().map(|item| item.unwrap()).collect(); + + let expected_early = vec![(k("k1"), SeqMarked::new_normal(1, v("v1")))]; + let expected_late = vec![ + (k("k1"), SeqMarked::new_normal(1, v("v1"))), + (k("k2"), SeqMarked::new_normal(3, v("v2"))), + (k("k3"), SeqMarked::new_normal(5, v("v3"))), + ]; + + assert_eq!(extracted_early, expected_early); + assert_eq!(extracted_late, expected_late); + } + + fn create_test_table() -> Table { + let mut table = Table::new(); + table.insert(k("k1"), 1, v("v1")).unwrap(); + table.insert(k("k2"), 3, v("v2")).unwrap(); + table.insert(k("k3"), 5, v("v3")).unwrap(); + table + } + + fn k(s: &str) -> String { + s.to_string() + } + + fn v(s: &str) -> String { + s.to_string() + } +} diff --git a/src/mvcc/snapshot_range_iter.rs b/src/mvcc/seq_bounded_range_iter.rs similarity index 97% rename from src/mvcc/snapshot_range_iter.rs rename to src/mvcc/seq_bounded_range_iter.rs index 212fe57..33f58ab 100644 --- a/src/mvcc/snapshot_range_iter.rs +++ b/src/mvcc/seq_bounded_range_iter.rs @@ -21,7 +21,7 @@ use crate::mvcc::ViewNamespace; use crate::mvcc::ViewValue; /// Multi-version range iterator with snapshot isolation. -pub trait SnapshotRangeIter +pub trait SeqBoundedRangeIter where S: ViewNamespace, K: ViewKey, diff --git a/src/mvcc/seq_bounded_read.rs b/src/mvcc/seq_bounded_read.rs new file mode 100644 index 0000000..eab5852 --- /dev/null +++ b/src/mvcc/seq_bounded_read.rs @@ -0,0 +1,40 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::mvcc::seq_bounded_get::SeqBoundedGet; +use crate::mvcc::seq_bounded_range::SeqBoundedRange; +use crate::mvcc::ViewKey; +use crate::mvcc::ViewNamespace; +use crate::mvcc::ViewValue; + +#[async_trait::async_trait] +pub trait SeqBoundedRead +where + Self: Send + Sync, + Self: SeqBoundedGet + SeqBoundedRange, + S: ViewNamespace, + K: ViewKey, + V: ViewValue, +{ +} + +impl SeqBoundedRead for T +where + T: Send + Sync, + T: SeqBoundedGet + SeqBoundedRange, + S: ViewNamespace, + K: ViewKey, + V: ViewValue, +{ +} diff --git a/src/mvcc/snapshot/mod.rs b/src/mvcc/snapshot/mod.rs new file mode 100644 index 0000000..c9a2888 --- /dev/null +++ b/src/mvcc/snapshot/mod.rs @@ -0,0 +1,251 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! MVCC snapshot providing consistent point-in-time reads and transactional commits. +//! +//! Snapshots capture a consistent view of data at a specific sequence number, +//! ensuring all reads see the same data state regardless of concurrent writes. + +use std::collections::BTreeMap; +use std::io; +use std::marker::PhantomData; +use std::ops::RangeBounds; + +use seq_marked::InternalSeq; + +use crate::mvcc::seq_bounded_get::SeqBoundedGet; +use crate::mvcc::seq_bounded_range::SeqBoundedRange; +use crate::mvcc::Commit; +use crate::mvcc::Table; +use crate::mvcc::ViewKey; +use crate::mvcc::ViewNamespace; +use crate::mvcc::ViewValue; +use crate::IOResultStream; +use crate::SeqMarked; + +/// MVCC snapshot with fixed sequence boundary for consistent reads and commit operations. +/// +/// Provides a point-in-time view of data at a specific sequence number, ensuring all +/// read operations see the same consistent state regardless of concurrent writes. +/// Sequence-bounded reads filter out any changes with sequence numbers greater than +/// the snapshot's sequence boundary. +/// +/// # Type Parameters +/// - `S`: Namespace type for organizing data by logical partitions +/// - `K`: Key type that must satisfy [`ViewKey`] constraints +/// - `V`: Value type that must satisfy [`ViewValue`] constraints +/// - `D`: Data source implementing sequential read and commit operations +#[derive(Clone, Debug, Default)] +pub struct Snapshot +where + S: ViewNamespace, + K: ViewKey, + V: ViewValue, + D: SeqBoundedGet, + D: SeqBoundedRange, + D: Commit, + D: Send + Sync, +{ + /// The snapshot sequence number. Values with sequence numbers greater than this will be invisible. + snapshot_seq: InternalSeq, + data: D, + _phantom: PhantomData<(S, K, V)>, +} + +impl Snapshot +where + S: ViewNamespace, + K: ViewKey, + V: ViewValue, + D: SeqBoundedGet, + D: SeqBoundedRange, + D: Commit, + D: Send + Sync, +{ + pub fn new(snapshot_seq: InternalSeq, data: D) -> Self { + Self { + snapshot_seq, + data, + _phantom: PhantomData, + } + } + + pub fn snapshot_seq(&self) -> InternalSeq { + self.snapshot_seq + } + + /// Gets the value for a key upto the internal snapshot sequence(inclusive). + pub async fn get(&self, space: S, key: K) -> Result, io::Error> { + self.data.get(space, key, *self.snapshot_seq).await + } + + /// Gets the value for a key upto the internal snapshot sequence(inclusive). + pub async fn get_many(&self, space: S, keys: Vec) -> Result>, io::Error> { + self.data.get_many(space, keys, *self.snapshot_seq).await + } + + /// Gets the value for a key upto the internal snapshot sequence(inclusive). + pub async fn range( + &self, + space: S, + range: R, + ) -> Result)>, io::Error> + where + R: RangeBounds + Send + Sync + Clone + 'static, + { + self.data.range(space, range, *self.snapshot_seq).await + } + + /// Commits changes to the underlying data store and returns the updated data handle. + pub async fn commit( + mut self, + new_seq: InternalSeq, + changes: BTreeMap>, + ) -> Result { + self.data.commit(new_seq, changes).await?; + Ok(self.data) + } + + pub fn data(&self) -> &D { + &self.data + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + use std::sync::Mutex; + + use futures_util::StreamExt; + + use super::*; + + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + struct TestSpace(u8); + + impl ViewNamespace for TestSpace { + fn increments_seq(&self) -> bool { + true + } + } + + #[derive(Debug, Clone)] + struct MockData { + calls: Arc>>, + } + + impl MockData { + fn new() -> Self { + Self { + calls: Arc::new(Mutex::new(Vec::new())), + } + } + + fn record_call(&self, call: &str) { + self.calls.lock().unwrap().push(call.to_string()); + } + + fn get_calls(&self) -> Vec { + self.calls.lock().unwrap().clone() + } + } + + #[async_trait::async_trait] + impl SeqBoundedGet for MockData { + async fn get( + &self, + _space: TestSpace, + _k: String, + seq: u64, + ) -> Result, io::Error> { + self.record_call(&format!("get(seq:{})", seq)); + Ok(SeqMarked::new_not_found()) + } + + async fn get_many( + &self, + _space: TestSpace, + _keys: Vec, + seq: u64, + ) -> Result>, io::Error> { + self.record_call(&format!("get_many(seq:{})", seq)); + Ok(vec![]) + } + } + + #[async_trait::async_trait] + impl SeqBoundedRange for MockData { + async fn range( + &self, + _space: TestSpace, + _range: R, + seq: u64, + ) -> Result)>, io::Error> + where + R: RangeBounds + Send + Sync + Clone + 'static, + { + self.record_call(&format!("range(seq:{})", seq)); + Ok(futures::stream::empty().boxed()) + } + } + + #[async_trait::async_trait] + impl Commit for MockData { + async fn commit( + &mut self, + seq: InternalSeq, + _changes: BTreeMap>, + ) -> Result<(), io::Error> { + self.record_call(&format!("commit(seq:{})", *seq)); + Ok(()) + } + } + + #[tokio::test] + async fn test_snapshot_delegates_to_data() { + let mock = MockData::new(); + let snapshot = Snapshot { + snapshot_seq: InternalSeq::new(42), + data: mock.clone(), + _phantom: PhantomData, + }; + + snapshot.get(TestSpace(1), "k".to_string()).await.unwrap(); + snapshot + .get_many(TestSpace(1), vec!["k".to_string()]) + .await + .unwrap(); + let _stream = snapshot.range(TestSpace(1), ..).await.unwrap(); + + let calls = mock.get_calls(); + assert_eq!(calls, vec![ + "get(seq:42)", + "get_many(seq:42)", + "range(seq:42)" + ]); + + let mock2 = MockData::new(); + let snapshot2 = Snapshot { + snapshot_seq: InternalSeq::new(99), + data: mock2.clone(), + _phantom: PhantomData, + }; + + snapshot2 + .commit(InternalSeq::new(100), BTreeMap::new()) + .await + .unwrap(); + assert_eq!(mock2.get_calls(), vec!["commit(seq:100)"]); + } +} diff --git a/src/mvcc/snapshot_seq.rs b/src/mvcc/snapshot_seq.rs new file mode 100644 index 0000000..a71a5a1 --- /dev/null +++ b/src/mvcc/snapshot_seq.rs @@ -0,0 +1,21 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use seq_marked::InternalSeq; + +/// API to get the seq of the snapshot view. +#[async_trait::async_trait] +pub trait SnapshotSeq { + fn snapshot_seq(&self) -> InternalSeq; +} diff --git a/src/mvcc/table/impl_commit.rs b/src/mvcc/table/impl_commit.rs deleted file mode 100644 index 865e456..0000000 --- a/src/mvcc/table/impl_commit.rs +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright 2021 Datafuse Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::collections::BTreeMap; -use std::io; - -use seq_marked::InternalSeq; - -use crate::mvcc::commit::Commit; -use crate::mvcc::key::ViewKey; -use crate::mvcc::table::table_view_readonly::TableViewReadonly; -use crate::mvcc::table::Table; -use crate::mvcc::value::ViewValue; -use crate::mvcc::view_namespace::ViewNamespace; - -#[async_trait::async_trait] -impl Commit for TableViewReadonly -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, -{ - async fn commit( - &mut self, - last_seq: InternalSeq, - changes: BTreeMap>, - ) -> Result<(), io::Error> { - for (space, table_changes) in changes { - let t = self.tables.entry(space).or_default(); - t.apply(table_changes); - - // Applied but does not visible. - // self.base_seq = t.last_seq.internal_seq(); - } - - // Update the base sequence to the last sequence from the view - self.base_seq = last_seq; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - - use seq_marked::InternalSeq; - use seq_marked::SeqMarked; - - use super::*; - - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - enum TestSpace { - Space1, - Space2, - } - - impl ViewNamespace for TestSpace { - fn increments_seq(&self) -> bool { - true - } - } - - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] - struct TestKey(String); - - #[derive(Debug, Clone, PartialEq, Eq)] - struct TestValue(String); - - fn key(s: &str) -> TestKey { - TestKey(s.to_string()) - } - fn value(s: &str) -> TestValue { - TestValue(s.to_string()) - } - - fn test_view() -> TableViewReadonly { - let mut table = Table::new(); - table.insert(key("k1"), 1, value("v1")).unwrap(); - table.insert(key("k2"), 2, value("v2")).unwrap(); - - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, table); - TableViewReadonly::new(tables) - } - - #[tokio::test] - async fn test_commit_empty() { - let mut view = test_view(); - - view.commit(InternalSeq::new(0), BTreeMap::new()) - .await - .unwrap(); - - assert_eq!(view.base_seq, InternalSeq::new(0)); - assert_eq!(view.tables.len(), 1); - } - - #[tokio::test] - async fn test_commit_new_space() { - let mut view = test_view(); - let mut table = Table::new(); - table.insert(key("k1"), 10, value("v10")).unwrap(); - - let mut changes = BTreeMap::new(); - changes.insert(TestSpace::Space2, table); - - view.commit(InternalSeq::new(7), changes).await.unwrap(); - - assert_eq!(view.tables.len(), 2); - let result = view.tables[&TestSpace::Space2].get(key("k1"), 100); - assert_eq!(result, SeqMarked::new_normal(10, &value("v10"))); - } - - #[tokio::test] - async fn test_commit_existing_space() { - let mut view = test_view(); - let mut table = Table::new(); - table.insert(key("k1"), 5, value("v1_new")).unwrap(); // update - table.insert(key("k3"), 6, value("v3")).unwrap(); // insert - table.insert_tombstone(key("k2"), 7).unwrap(); // delete - - let mut changes = BTreeMap::new(); - changes.insert(TestSpace::Space1, table); - - view.commit(InternalSeq::new(7), changes).await.unwrap(); - - let space1 = &view.tables[&TestSpace::Space1]; - assert_eq!( - space1.get(key("k1"), 100), - SeqMarked::new_normal(5, &value("v1_new")) - ); - assert_eq!(space1.get(key("k2"), 100), SeqMarked::new_tombstone(7)); - assert_eq!( - space1.get(key("k3"), 100), - SeqMarked::new_normal(6, &value("v3")) - ); - } - - #[tokio::test] - async fn test_commit_updates_base_seq() { - let mut view = test_view(); - view.base_seq = InternalSeq::new(42); - - let mut table = Table::new(); - table.insert(key("k1"), 100, value("v")).unwrap(); - let mut changes = BTreeMap::new(); - changes.insert(TestSpace::Space1, table); - - view.commit(InternalSeq::new(7), changes).await.unwrap(); - - assert_eq!(view.base_seq, InternalSeq::new(7)); // updated to last_seq - } - - #[tokio::test] - async fn test_commit_versioning() { - let mut view = test_view(); - let mut table = Table::new(); - table.insert(key("k1"), 10, value("new")).unwrap(); - - let mut changes = BTreeMap::new(); - changes.insert(TestSpace::Space1, table); - - view.commit(InternalSeq::new(7), changes).await.unwrap(); - - let space1 = &view.tables[&TestSpace::Space1]; - // High seq sees new version - assert_eq!( - space1.get(key("k1"), 100), - SeqMarked::new_normal(10, &value("new")) - ); - // Low seq sees original - assert_eq!( - space1.get(key("k1"), 5), - SeqMarked::new_normal(1, &value("v1")) - ); - } - - #[tokio::test] - #[should_panic(expected = "assertion failed: self.last_seq <= last_seq")] - async fn test_commit_sequence_conflict() { - let mut view = test_view(); - - // First commit with high sequence - let mut table1 = Table::new(); - table1.insert(key("k1"), 10, value("v10")).unwrap(); - let mut changes1 = BTreeMap::new(); - changes1.insert(TestSpace::Space1, table1); - view.commit(InternalSeq::new(10), changes1).await.unwrap(); - - // Verify the first commit's data is accessible - let space1 = &view.tables[&TestSpace::Space1]; - assert_eq!( - space1.get(key("k1"), 100), - SeqMarked::new_normal(10, &value("v10")) - ); - // Original data should still be accessible at appropriate sequence - assert_eq!( - space1.get(key("k1"), 5), - SeqMarked::new_normal(1, &value("v1")) - ); - - // Second commit with lower sequence - should panic - let mut table2 = Table::new(); - table2.insert(key("k2"), 5, value("v5")).unwrap(); // last_seq = 5 < 10 - let mut changes2 = BTreeMap::new(); - changes2.insert(TestSpace::Space1, table2); - view.commit(InternalSeq::new(5), changes2).await.unwrap(); // PANIC - } - - #[tokio::test] - async fn test_commit_empty_table() { - let mut view = test_view(); - let empty_table = Table::new(); // last_seq = 0 - - let mut changes = BTreeMap::new(); - changes.insert(TestSpace::Space1, empty_table); - - // Should panic because existing table has last_seq > 0 - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - futures::executor::block_on(view.commit(InternalSeq::new(7), changes)) - })); - - assert!(result.is_err()); // Should panic - } - - #[tokio::test] - async fn test_commit_only_tombstones() { - let mut view = test_view(); - let mut table = Table::new(); - table.insert_tombstone(key("k1"), 5).unwrap(); - table.insert_tombstone(key("k_new"), 6).unwrap(); - - let mut changes = BTreeMap::new(); - changes.insert(TestSpace::Space1, table); - - view.commit(InternalSeq::new(7), changes).await.unwrap(); - - let space1 = &view.tables[&TestSpace::Space1]; - assert_eq!(space1.get(key("k1"), 100), SeqMarked::new_tombstone(5)); - assert_eq!(space1.get(key("k_new"), 100), SeqMarked::new_tombstone(6)); - } - - #[tokio::test] - async fn test_commit_multiple_same_space() { - let mut view = test_view(); - - // First commit - let mut table1 = Table::new(); - table1.insert(key("k3"), 5, value("v3")).unwrap(); - let mut changes1 = BTreeMap::new(); - changes1.insert(TestSpace::Space1, table1); - view.commit(InternalSeq::new(5), changes1).await.unwrap(); - - // Verify first commit's data is accessible - let space1 = &view.tables[&TestSpace::Space1]; - assert_eq!( - space1.get(key("k3"), 100), - SeqMarked::new_normal(5, &value("v3")) - ); - // Original test data should still be there - assert_eq!( - space1.get(key("k1"), 100), - SeqMarked::new_normal(1, &value("v1")) - ); - assert_eq!( - space1.get(key("k2"), 100), - SeqMarked::new_normal(2, &value("v2")) - ); - - // Second commit to same space with higher seq - let mut table2 = Table::new(); - table2.insert(key("k4"), 10, value("v4")).unwrap(); - let mut changes2 = BTreeMap::new(); - changes2.insert(TestSpace::Space1, table2); - view.commit(InternalSeq::new(10), changes2).await.unwrap(); - - let space1 = &view.tables[&TestSpace::Space1]; - assert_eq!( - space1.get(key("k3"), 100), - SeqMarked::new_normal(5, &value("v3")) - ); - assert_eq!( - space1.get(key("k4"), 100), - SeqMarked::new_normal(10, &value("v4")) - ); - } - - #[tokio::test] - async fn test_commit_zero_sequences() { - // Test edge case with zero sequence numbers - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, Table::::new()); // empty table, last_seq = 0 - let mut view = TableViewReadonly::new(tables); - - let table = Table::::new(); // also last_seq = 0 - let mut changes = BTreeMap::new(); - changes.insert(TestSpace::Space1, table); - - view.commit(InternalSeq::new(7), changes).await.unwrap(); // Should not panic (0 <= 0) - - // Verify the committed empty table state - assert_eq!(view.tables.len(), 1); - let space1 = &view.tables[&TestSpace::Space1]; - // Empty table should have last_seq of 0 - assert_eq!(space1.last_seq, SeqMarked::zero()); - // Querying any key should return not_found - assert!(space1.get(key("any_key"), 100).is_not_found()); - } - - #[tokio::test] - async fn test_commit_max_sequence() { - // Test edge case with maximum sequence numbers - let mut view = test_view(); - let mut table = Table::new(); - table.insert(key("k1"), u64::MAX, value("max_seq")).unwrap(); - - let mut changes = BTreeMap::new(); - changes.insert(TestSpace::Space1, table); - - view.commit(InternalSeq::new(7), changes).await.unwrap(); - - let space1 = &view.tables[&TestSpace::Space1]; - assert_eq!( - space1.get(key("k1"), u64::MAX), - SeqMarked::new_normal(u64::MAX, &value("max_seq")) - ); - } -} diff --git a/src/mvcc/table/impl_scoped_snapshot_get.rs b/src/mvcc/table/impl_scoped_seq_bounded_get.rs similarity index 82% rename from src/mvcc/table/impl_scoped_snapshot_get.rs rename to src/mvcc/table/impl_scoped_seq_bounded_get.rs index 22e10b7..1a61a6a 100644 --- a/src/mvcc/table/impl_scoped_snapshot_get.rs +++ b/src/mvcc/table/impl_scoped_seq_bounded_get.rs @@ -17,12 +17,12 @@ use std::io; use seq_marked::SeqMarked; use super::Table; -use crate::mvcc::scoped_snapshot_get::ScopedSnapshotGet; +use crate::mvcc::scoped_seq_bounded_get::ScopedSeqBoundedGet; use crate::mvcc::ViewKey; use crate::mvcc::ViewValue; #[async_trait::async_trait] -impl ScopedSnapshotGet for Table +impl ScopedSeqBoundedGet for Table where K: ViewKey, V: ViewValue, @@ -38,7 +38,7 @@ mod tests { use seq_marked::SeqMarked; use super::*; - use crate::mvcc::scoped_snapshot_get::ScopedSnapshotGet; + use crate::mvcc::scoped_seq_bounded_get::ScopedSeqBoundedGet; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct TestKey(String); @@ -69,7 +69,7 @@ mod tests { let table = create_test_table(); let snapshot_seq = 10; - let result = ScopedSnapshotGet::get(&table, key("key1"), snapshot_seq) + let result = ScopedSeqBoundedGet::get(&table, key("key1"), snapshot_seq) .await .unwrap(); assert_eq!(result, SeqMarked::new_normal(5, value("value1_v2"))); // Most recent version @@ -81,7 +81,7 @@ mod tests { let table = create_test_table(); let snapshot_seq = 10; - let result = ScopedSnapshotGet::get(&table, key("key3"), snapshot_seq) + let result = ScopedSeqBoundedGet::get(&table, key("key3"), snapshot_seq) .await .unwrap(); assert_eq!(result, SeqMarked::new_tombstone(3)); @@ -93,7 +93,7 @@ mod tests { let table = create_test_table(); let snapshot_seq = 10; - let result = ScopedSnapshotGet::get(&table, key("nonexistent"), snapshot_seq) + let result = ScopedSeqBoundedGet::get(&table, key("nonexistent"), snapshot_seq) .await .unwrap(); assert_eq!(result, SeqMarked::new_not_found()); @@ -105,7 +105,7 @@ mod tests { // key4 has seq 10, should be visible with snapshot_seq 10 let snapshot_seq1 = 10; - let result = ScopedSnapshotGet::get(&table, key("key4"), snapshot_seq1) + let result = ScopedSeqBoundedGet::get(&table, key("key4"), snapshot_seq1) .await .unwrap(); assert_eq!(result, SeqMarked::new_normal(10, value("value4"))); @@ -113,14 +113,14 @@ mod tests { // key4 has seq 10, should not be visible with snapshot_seq 9 let snapshot_seq2 = 9; - let result = ScopedSnapshotGet::get(&table, key("key4"), snapshot_seq2) + let result = ScopedSeqBoundedGet::get(&table, key("key4"), snapshot_seq2) .await .unwrap(); assert_eq!(result, SeqMarked::new_not_found()); // key1 with snapshot_seq 3 should return version 1, not version 5 let snapshot_seq3 = 3; - let result = ScopedSnapshotGet::get(&table, key("key1"), snapshot_seq3) + let result = ScopedSeqBoundedGet::get(&table, key("key1"), snapshot_seq3) .await .unwrap(); assert_eq!(result, SeqMarked::new_normal(1, value("value1"))); @@ -133,7 +133,7 @@ mod tests { let keys = vec![key("key1"), key("key2"), key("nonexistent")]; let snapshot_seq = 10; - let results = ScopedSnapshotGet::get_many(&table, keys, snapshot_seq) + let results = ScopedSeqBoundedGet::get_many(&table, keys, snapshot_seq) .await .unwrap(); @@ -151,7 +151,7 @@ mod tests { let keys = vec![]; let snapshot_seq = 10; - let results = ScopedSnapshotGet::get_many(&table, keys, snapshot_seq) + let results = ScopedSeqBoundedGet::get_many(&table, keys, snapshot_seq) .await .unwrap(); assert_eq!(results.len(), 0); @@ -163,7 +163,7 @@ mod tests { let keys = vec![key("key1"), key("key4")]; // key1 has multiple versions, key4 seq=10 let snapshot_seq = 5; - let results = ScopedSnapshotGet::get_many(&table, keys, snapshot_seq) + let results = ScopedSeqBoundedGet::get_many(&table, keys, snapshot_seq) .await .unwrap(); @@ -183,13 +183,17 @@ mod tests { table.insert(key("key"), 5, value("v5")).unwrap(); // Test different snapshot points - let result = ScopedSnapshotGet::get(&table, key("key"), 2).await.unwrap(); + let result = ScopedSeqBoundedGet::get(&table, key("key"), 2) + .await + .unwrap(); assert_eq!(result, SeqMarked::new_normal(1, value("v1"))); - let result = ScopedSnapshotGet::get(&table, key("key"), 4).await.unwrap(); + let result = ScopedSeqBoundedGet::get(&table, key("key"), 4) + .await + .unwrap(); assert_eq!(result, SeqMarked::new_normal(3, value("v3"))); - let result = ScopedSnapshotGet::get(&table, key("key"), 10) + let result = ScopedSeqBoundedGet::get(&table, key("key"), 10) .await .unwrap(); assert_eq!(result, SeqMarked::new_normal(5, value("v5"))); diff --git a/src/mvcc/table/impl_scoped_snapshot_range_iter.rs b/src/mvcc/table/impl_scoped_seq_bounded_range_iter.rs similarity index 85% rename from src/mvcc/table/impl_scoped_snapshot_range_iter.rs rename to src/mvcc/table/impl_scoped_seq_bounded_range_iter.rs index 82fd0ee..1fdc577 100644 --- a/src/mvcc/table/impl_scoped_snapshot_range_iter.rs +++ b/src/mvcc/table/impl_scoped_seq_bounded_range_iter.rs @@ -17,11 +17,11 @@ use std::ops::RangeBounds; use seq_marked::SeqMarked; use super::Table; -use crate::mvcc::scoped_snapshot_range_iter::ScopedSnapshotRangeIter; +use crate::mvcc::scoped_seq_bounded_range_iter::ScopedSeqBoundedRangeIter; use crate::mvcc::ViewKey; use crate::mvcc::ViewValue; -impl ScopedSnapshotRangeIter for Table +impl ScopedSeqBoundedRangeIter for Table where K: ViewKey, V: ViewValue, @@ -38,7 +38,7 @@ where } } -impl ScopedSnapshotRangeIter for T +impl ScopedSeqBoundedRangeIter for T where K: ViewKey, V: ViewValue, @@ -63,7 +63,7 @@ mod tests { use seq_marked::SeqMarked; use super::*; - use crate::mvcc::scoped_snapshot_range_iter::ScopedSnapshotRangeIter; + use crate::mvcc::scoped_seq_bounded_range_iter::ScopedSeqBoundedRangeIter; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct TestKey(String); @@ -95,7 +95,7 @@ mod tests { let table = Arc::new(table); let snapshot_seq = 10; - let iter = ScopedSnapshotRangeIter::range_iter(&table, .., snapshot_seq); + let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., snapshot_seq); assert_eq!(iter.collect::>(), vec![ (&key("key1"), SeqMarked::new_normal(5, &value("value1_v2"))), @@ -110,7 +110,7 @@ mod tests { let table = create_test_table(); let snapshot_seq = 10; - let iter = ScopedSnapshotRangeIter::range_iter(&table, .., snapshot_seq); + let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., snapshot_seq); let results: Vec<_> = iter.collect(); assert_eq!(results, vec![ @@ -127,7 +127,7 @@ mod tests { let snapshot_seq = 10; let iter = - ScopedSnapshotRangeIter::range_iter(&table, key("key1")..=key("key2"), snapshot_seq); + ScopedSeqBoundedRangeIter::range_iter(&table, key("key1")..=key("key2"), snapshot_seq); let results: Vec<_> = iter.collect(); assert_eq!(results, vec![ @@ -141,7 +141,7 @@ mod tests { let table = create_test_table(); let snapshot_seq = 5; - let iter = ScopedSnapshotRangeIter::range_iter(&table, .., snapshot_seq); + let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., snapshot_seq); let results: Vec<_> = iter.collect(); assert_eq!(results, vec![ @@ -156,7 +156,7 @@ mod tests { let table: Table = Table::new(); let snapshot_seq = 10; - let iter = ScopedSnapshotRangeIter::range_iter(&table, .., snapshot_seq); + let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., snapshot_seq); let results: Vec<_> = iter.collect(); assert_eq!(results.len(), 0); @@ -172,7 +172,7 @@ mod tests { table.insert(key("key"), 5, value("v5")).unwrap(); // Test different snapshot points - let iter = ScopedSnapshotRangeIter::range_iter(&table, .., 2); + let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., 2); let results: Vec<_> = iter.collect(); assert_eq!(results, vec![( @@ -180,7 +180,7 @@ mod tests { SeqMarked::new_normal(1, &value("v1")) ),]); - let iter = ScopedSnapshotRangeIter::range_iter(&table, .., 4); + let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., 4); let results: Vec<_> = iter.collect(); assert_eq!(results, vec![( @@ -188,7 +188,7 @@ mod tests { SeqMarked::new_normal(3, &value("v3")) ),]); - let iter = ScopedSnapshotRangeIter::range_iter(&table, .., 10); + let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., 10); let results: Vec<_> = iter.collect(); assert_eq!(results, vec![( diff --git a/src/mvcc/table/mod.rs b/src/mvcc/table/mod.rs index 7ad7263..173ddf7 100644 --- a/src/mvcc/table/mod.rs +++ b/src/mvcc/table/mod.rs @@ -13,11 +13,10 @@ // limitations under the License. pub mod errors; -mod impl_commit; -mod impl_scoped_snapshot_get; -mod impl_scoped_snapshot_range_iter; +mod impl_scoped_seq_bounded_get; +mod impl_scoped_seq_bounded_range_iter; pub mod range_iter; -mod table_view_readonly; +mod table_snapshot; use std::cmp::Reverse; use std::collections::BTreeMap; @@ -29,7 +28,8 @@ use errors::InsertError; use futures::Stream; use futures_util::TryStreamExt; use range_iter::RangeIter; -pub use table_view_readonly::TableViewReadonly; +pub use table_snapshot::Tables; +pub use table_snapshot::TablesSnapshot; use crate::SeqMarked; diff --git a/src/mvcc/table/table_snapshot.rs b/src/mvcc/table/table_snapshot.rs new file mode 100644 index 0000000..aa1178f --- /dev/null +++ b/src/mvcc/table/table_snapshot.rs @@ -0,0 +1,200 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::BTreeMap; + +use crate::mvcc::snapshot::Snapshot; +use crate::mvcc::table::Table; + +pub type Tables = BTreeMap>; + +pub type TablesSnapshot = Snapshot>>; + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use futures_util::StreamExt; + use seq_marked::InternalSeq; + use seq_marked::SeqMarked; + + use super::*; + use crate::mvcc::ViewNamespace; + + // Test types that implement the required traits + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + enum TestSpace { + Space1, + } + + impl ViewNamespace for TestSpace { + fn increments_seq(&self) -> bool { + true + } + } + + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + struct TestKey(String); + + #[derive(Debug, Clone, PartialEq, Eq)] + struct TestValue(String); + + // Helper functions + fn key(s: &str) -> TestKey { + TestKey(s.to_string()) + } + + fn value(s: &str) -> TestValue { + TestValue(s.to_string()) + } + + fn create_test_table() -> Table { + let mut table = Table::new(); + table.insert(key("k1"), 1, value("v1")).unwrap(); + table.insert(key("k2"), 2, value("v2")).unwrap(); + table.insert(key("k3"), 3, value("v3")).unwrap(); + table.insert_tombstone(key("k4"), 4).unwrap(); + + // Add a key with both normal value and tombstone + table.insert(key("k5"), 5, value("v5")).unwrap(); + table.insert_tombstone(key("k5"), 6).unwrap(); + + // Add a key whose tombstone is newer than its normal record + table.insert(key("k6"), 7, value("v6")).unwrap(); + table.insert_tombstone(key("k6"), 8).unwrap(); + + table + } + + #[tokio::test] + async fn test_view_seq() { + let mut tables = BTreeMap::new(); + tables.insert(TestSpace::Space1, create_test_table()); + + let view = TablesSnapshot::new(InternalSeq::new(5), tables); + + assert_eq!(view.snapshot_seq(), InternalSeq::new(5)); + } + + #[tokio::test] + async fn test_mget_existing_space() { + let mut tables = BTreeMap::new(); + tables.insert(TestSpace::Space1, create_test_table()); + + let view = TablesSnapshot::new(InternalSeq::new(10), tables); + + let keys = vec![ + key("k1"), + key("k2"), + key("k3"), + key("k4"), + key("k5"), + key("k6"), + ]; + let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); + + assert_eq!(result.len(), 6); + assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); + assert_eq!(result[1], SeqMarked::new_normal(2, value("v2"))); + assert_eq!(result[2], SeqMarked::new_normal(3, value("v3"))); + assert_eq!(result[3], SeqMarked::new_tombstone(4)); + assert_eq!(result[4], SeqMarked::new_tombstone(6)); // Latest version is tombstone + assert_eq!(result[5], SeqMarked::new_tombstone(8)); // Latest version is tombstone + } + + #[tokio::test] + async fn test_mget_with_tombstone_base_seq_after_tombstone() { + let mut tables = BTreeMap::new(); + tables.insert(TestSpace::Space1, create_test_table()); + + let view = TablesSnapshot::new(InternalSeq::new(6), tables); + + let keys = vec![ + key("k1"), + key("k2"), + key("k3"), + key("k4"), + key("k5"), + key("k6"), + ]; + let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); + + assert_eq!(result.len(), 6); + assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); + assert_eq!(result[1], SeqMarked::new_normal(2, value("v2"))); + assert_eq!(result[2], SeqMarked::new_normal(3, value("v3"))); + assert_eq!(result[3], SeqMarked::new_tombstone(4)); + assert_eq!(result[4], SeqMarked::new_tombstone(6)); // Can see the tombstone + assert!(result[5].is_not_found()); // seq 7 > base_seq 6 + } + + #[tokio::test] + async fn test_mget_with_tombstone_base_seq_after_all_tombstones() { + let mut tables = BTreeMap::new(); + tables.insert(TestSpace::Space1, create_test_table()); + + let view = TablesSnapshot::new(InternalSeq::new(8), tables); + + let keys = vec![ + key("k1"), + key("k2"), + key("k3"), + key("k4"), + key("k5"), + key("k6"), + ]; + let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); + + assert_eq!(result.len(), 6); + assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); + assert_eq!(result[1], SeqMarked::new_normal(2, value("v2"))); + assert_eq!(result[2], SeqMarked::new_normal(3, value("v3"))); + assert_eq!(result[3], SeqMarked::new_tombstone(4)); + assert_eq!(result[4], SeqMarked::new_tombstone(6)); // Can see the tombstone + assert_eq!(result[5], SeqMarked::new_tombstone(8)); // Can see the tombstone + } + + #[tokio::test] + async fn test_range_existing_space() { + let mut tables = BTreeMap::new(); + tables.insert(TestSpace::Space1, create_test_table()); + + let view = TablesSnapshot::new(InternalSeq::new(10), tables); + + let range = key("k1")..=key("k6"); + let mut stream = view.range(TestSpace::Space1, range).await.unwrap(); + + let mut results = Vec::new(); + while let Some(result) = stream.next().await { + results.push(result.unwrap()); + } + + assert_eq!(results.len(), 6); + assert_eq!( + results[0], + (key("k1"), SeqMarked::new_normal(1, value("v1"))) + ); + assert_eq!( + results[1], + (key("k2"), SeqMarked::new_normal(2, value("v2"))) + ); + assert_eq!( + results[2], + (key("k3"), SeqMarked::new_normal(3, value("v3"))) + ); + assert_eq!(results[3], (key("k4"), SeqMarked::new_tombstone(4))); + assert_eq!(results[4], (key("k5"), SeqMarked::new_tombstone(6))); // Latest version is tombstone + assert_eq!(results[5], (key("k6"), SeqMarked::new_tombstone(8))); // Latest version is tombstone + } +} diff --git a/src/mvcc/table/table_view_readonly.rs b/src/mvcc/table/table_view_readonly.rs deleted file mode 100644 index 938cf96..0000000 --- a/src/mvcc/table/table_view_readonly.rs +++ /dev/null @@ -1,1028 +0,0 @@ -// Copyright 2021 Datafuse Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::collections::BTreeMap; -use std::io::Error; -use std::ops::RangeBounds; - -use futures::StreamExt; -use seq_marked::InternalSeq; -use seq_marked::SeqMarked; - -use crate::mvcc::key::ViewKey; -use crate::mvcc::table::Table; -use crate::mvcc::value::ViewValue; -use crate::mvcc::view_namespace::ViewNamespace; -use crate::mvcc::view_readonly::ViewReadonly; -use crate::IOResultStream; - -/// Implement ViewReadonly for a [`Table`] containing multiple key spaces. -pub struct TableViewReadonly -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, -{ - /// The sequence number marking the visibility boundary for key-value pairs(equal or lower than this value can be seen). - /// - /// Note: This is incomplete barrier if deletion insertion does not increase the seq. - pub(crate) base_seq: InternalSeq, - - /// The data in each key space. - pub(crate) tables: BTreeMap>, -} - -impl TableViewReadonly -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, -{ - pub fn new(tables: BTreeMap>) -> Self { - Self { - base_seq: InternalSeq::new(0), - tables, - } - } -} - -#[async_trait::async_trait] -impl ViewReadonly for TableViewReadonly -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, -{ - fn view_seq(&self) -> InternalSeq { - self.base_seq - } - - async fn get(&self, space: S, key: K) -> Result, Error> { - let table = self.tables.get(&space); - - let Some(table) = table else { - return Ok(SeqMarked::new_not_found()); - }; - - let value = table.get(key, *self.base_seq).cloned(); - Ok(value) - } - - async fn range( - &self, - space: S, - range: R, - ) -> Result)>, Error> - where - R: RangeBounds + Send + Sync + Clone + 'static, - { - let table = self.tables.get(&space); - - let Some(table) = table else { - let strm = futures::stream::empty(); - return Ok(strm.boxed()); - }; - - let pairs: Vec<_> = table - .range(range, *self.base_seq) - .map(|x| Ok((x.0.clone(), x.1.cloned()))) - .collect(); - - let strm = futures::stream::iter(pairs); - Ok(strm.boxed()) - } -} - -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - - use seq_marked::SeqMarked; - - use super::*; - - // Test types that implement the required traits - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - enum TestSpace { - Space1, - Space2, - Space3, - } - - impl ViewNamespace for TestSpace { - fn increments_seq(&self) -> bool { - true - } - } - - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] - struct TestKey(String); - - #[derive(Debug, Clone, PartialEq, Eq)] - struct TestValue(String); - - // Helper functions - fn key(s: &str) -> TestKey { - TestKey(s.to_string()) - } - - fn value(s: &str) -> TestValue { - TestValue(s.to_string()) - } - - fn create_test_table() -> Table { - let mut table = Table::new(); - table.insert(key("k1"), 1, value("v1")).unwrap(); - table.insert(key("k2"), 2, value("v2")).unwrap(); - table.insert(key("k3"), 3, value("v3")).unwrap(); - table.insert_tombstone(key("k4"), 4).unwrap(); - - // Add a key with both normal value and tombstone - table.insert(key("k5"), 5, value("v5")).unwrap(); - table.insert_tombstone(key("k5"), 6).unwrap(); - - // Add a key whose tombstone is newer than its normal record - table.insert(key("k6"), 7, value("v6")).unwrap(); - table.insert_tombstone(key("k6"), 8).unwrap(); - - table - } - - #[tokio::test] - async fn test_new() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let view = TableViewReadonly::new(tables); - assert_eq!(view.view_seq(), InternalSeq::new(0)); - assert_eq!(view.tables.len(), 1); - } - - #[tokio::test] - async fn test_base_seq() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(5); - - assert_eq!(view.view_seq(), InternalSeq::new(5)); - } - - #[tokio::test] - async fn test_mget_existing_space() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - let keys = vec![ - key("k1"), - key("k2"), - key("k3"), - key("k4"), - key("k5"), - key("k6"), - ]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 6); - assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); - assert_eq!(result[1], SeqMarked::new_normal(2, value("v2"))); - assert_eq!(result[2], SeqMarked::new_normal(3, value("v3"))); - assert_eq!(result[3], SeqMarked::new_tombstone(4)); - assert_eq!(result[4], SeqMarked::new_tombstone(6)); // Latest version is tombstone - assert_eq!(result[5], SeqMarked::new_tombstone(8)); // Latest version is tombstone - } - - #[tokio::test] - async fn test_mget_nonexistent_space() { - let tables = BTreeMap::new(); - let mut view = TableViewReadonly::<_, _, String>::new(tables); - view.base_seq = InternalSeq::new(10); - - let keys = vec![key("k1"), key("k2")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 2); - assert!(result[0].is_not_found()); - assert!(result[1].is_not_found()); - } - - #[tokio::test] - async fn test_mget_empty_keys() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - let result = view.get_many(TestSpace::Space1, vec![]).await.unwrap(); - assert_eq!(result.len(), 0); - } - - #[tokio::test] - async fn test_mget_with_base_seq_limit() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(2); // Only see up to seq 2 - - let keys = vec![ - key("k1"), - key("k2"), - key("k3"), - key("k4"), - key("k5"), - key("k6"), - ]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 6); - assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); - assert_eq!(result[1], SeqMarked::new_normal(2, value("v2"))); - assert!(result[2].is_not_found()); // seq 3 > base_seq 2 - assert!(result[3].is_not_found()); // seq 4 > base_seq 2 - assert!(result[4].is_not_found()); // seq 5 > base_seq 2 - assert!(result[5].is_not_found()); // seq 7 > base_seq 2 - } - - #[tokio::test] - async fn test_mget_with_tombstone_base_seq() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(4); // See up to tombstone seq 4 - - let keys = vec![ - key("k1"), - key("k2"), - key("k3"), - key("k4"), - key("k5"), - key("k6"), - ]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 6); - assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); - assert_eq!(result[1], SeqMarked::new_normal(2, value("v2"))); - assert_eq!(result[2], SeqMarked::new_normal(3, value("v3"))); - assert_eq!(result[3], SeqMarked::new_tombstone(4)); - assert!(result[4].is_not_found()); // seq 5 > base_seq 4 - assert!(result[5].is_not_found()); // seq 7 > base_seq 4 - } - - #[tokio::test] - async fn test_mget_with_tombstone_base_seq_after_tombstone() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(6); // See up to tombstone seq 6 - - let keys = vec![ - key("k1"), - key("k2"), - key("k3"), - key("k4"), - key("k5"), - key("k6"), - ]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 6); - assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); - assert_eq!(result[1], SeqMarked::new_normal(2, value("v2"))); - assert_eq!(result[2], SeqMarked::new_normal(3, value("v3"))); - assert_eq!(result[3], SeqMarked::new_tombstone(4)); - assert_eq!(result[4], SeqMarked::new_tombstone(6)); // Can see the tombstone - assert!(result[5].is_not_found()); // seq 7 > base_seq 6 - } - - #[tokio::test] - async fn test_mget_with_tombstone_base_seq_after_all_tombstones() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(8); // See up to tombstone seq 8 - - let keys = vec![ - key("k1"), - key("k2"), - key("k3"), - key("k4"), - key("k5"), - key("k6"), - ]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 6); - assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); - assert_eq!(result[1], SeqMarked::new_normal(2, value("v2"))); - assert_eq!(result[2], SeqMarked::new_normal(3, value("v3"))); - assert_eq!(result[3], SeqMarked::new_tombstone(4)); - assert_eq!(result[4], SeqMarked::new_tombstone(6)); // Can see the tombstone - assert_eq!(result[5], SeqMarked::new_tombstone(8)); // Can see the tombstone - } - - #[tokio::test] - async fn test_mget_key_with_both_value_and_tombstone() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - - // Test viewing at seq 5 (before tombstone) - view.base_seq = InternalSeq::new(5); - let keys = vec![key("k5")]; - let result = view - .get_many(TestSpace::Space1, keys.clone()) - .await - .unwrap(); - assert_eq!(result.len(), 1); - assert_eq!(result[0], SeqMarked::new_normal(5, value("v5"))); - - // Test viewing at seq 6 (after tombstone) - view.base_seq = InternalSeq::new(6); - let result = view - .get_many(TestSpace::Space1, keys.clone()) - .await - .unwrap(); - assert_eq!(result.len(), 1); - assert_eq!(result[0], SeqMarked::new_tombstone(6)); - - // Test viewing at seq 7 (after tombstone) - view.base_seq = InternalSeq::new(7); - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - assert_eq!(result.len(), 1); - assert_eq!(result[0], SeqMarked::new_tombstone(6)); // Still see tombstone - } - - #[tokio::test] - async fn test_mget_key_with_newer_tombstone() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - - // Test viewing at seq 7 (before tombstone) - view.base_seq = InternalSeq::new(7); - let keys = vec![key("k6")]; - let result = view - .get_many(TestSpace::Space1, keys.clone()) - .await - .unwrap(); - assert_eq!(result.len(), 1); - assert_eq!(result[0], SeqMarked::new_normal(7, value("v6"))); - - view.base_seq = InternalSeq::new(8); - let result = view - .get_many(TestSpace::Space1, keys.clone()) - .await - .unwrap(); - assert_eq!(result.len(), 1); - assert_eq!(result[0], SeqMarked::new_tombstone(8)); // Latest version is tombstone - - // Test viewing at seq 9 (after tombstone) - view.base_seq = InternalSeq::new(9); - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - assert_eq!(result.len(), 1); - assert_eq!(result[0], SeqMarked::new_tombstone(8)); // Still see tombstone - } - - #[tokio::test] - async fn test_range_existing_space() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - let range = key("k1")..=key("k6"); - let mut stream = view.range(TestSpace::Space1, range).await.unwrap(); - - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 6); - assert_eq!( - results[0], - (key("k1"), SeqMarked::new_normal(1, value("v1"))) - ); - assert_eq!( - results[1], - (key("k2"), SeqMarked::new_normal(2, value("v2"))) - ); - assert_eq!( - results[2], - (key("k3"), SeqMarked::new_normal(3, value("v3"))) - ); - assert_eq!(results[3], (key("k4"), SeqMarked::new_tombstone(4))); - assert_eq!(results[4], (key("k5"), SeqMarked::new_tombstone(6))); // Latest version is tombstone - assert_eq!(results[5], (key("k6"), SeqMarked::new_tombstone(8))); // Latest version is tombstone - } - - #[tokio::test] - async fn test_range_nonexistent_space() { - let tables = BTreeMap::new(); - let mut view = TableViewReadonly::<_, _, String>::new(tables); - view.base_seq = InternalSeq::new(10); - - let range = key("k1")..=key("k3"); - let mut stream = view.range(TestSpace::Space1, range).await.unwrap(); - - let result = stream.next().await; - assert!(result.is_none()); - } - - #[tokio::test] - async fn test_range_empty_range() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - // Range that doesn't match any keys - let range = key("kx")..=key("ky"); - let mut stream = view.range(TestSpace::Space1, range).await.unwrap(); - - let result = stream.next().await; - assert!(result.is_none()); - } - - #[tokio::test] - async fn test_range_with_base_seq_limit() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(2); // Only see up to seq 2 - - let range = key("k1")..=key("k6"); - let mut stream = view.range(TestSpace::Space1, range).await.unwrap(); - - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 2); - assert_eq!( - results[0], - (key("k1"), SeqMarked::new_normal(1, value("v1"))) - ); - assert_eq!( - results[1], - (key("k2"), SeqMarked::new_normal(2, value("v2"))) - ); - // k3, k4, k5, and k6 are filtered out due to base_seq limit - } - - #[tokio::test] - async fn test_range_with_tombstone_base_seq() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(4); - - let range = key("k1")..=key("k6"); - let mut stream = view.range(TestSpace::Space1, range).await.unwrap(); - - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 4); - assert_eq!( - results[0], - (key("k1"), SeqMarked::new_normal(1, value("v1"))) - ); - assert_eq!( - results[1], - (key("k2"), SeqMarked::new_normal(2, value("v2"))) - ); - assert_eq!( - results[2], - (key("k3"), SeqMarked::new_normal(3, value("v3"))) - ); - assert_eq!(results[3], (key("k4"), SeqMarked::new_tombstone(4))); - // k5 and k6 are filtered out due to base_seq limit (seq 5,7 > base_seq 4) - } - - #[tokio::test] - async fn test_range_with_tombstone_base_seq_after_tombstone() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(6); - - let range = key("k1")..=key("k6"); - let mut stream = view.range(TestSpace::Space1, range).await.unwrap(); - - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 5); - assert_eq!( - results[0], - (key("k1"), SeqMarked::new_normal(1, value("v1"))) - ); - assert_eq!( - results[1], - (key("k2"), SeqMarked::new_normal(2, value("v2"))) - ); - assert_eq!( - results[2], - (key("k3"), SeqMarked::new_normal(3, value("v3"))) - ); - assert_eq!(results[3], (key("k4"), SeqMarked::new_tombstone(4))); - assert_eq!(results[4], (key("k5"), SeqMarked::new_tombstone(6))); // Can see the tombstone - // k6 is filtered out due to base_seq limit (seq 7 > base_seq 6) - } - - #[tokio::test] - async fn test_range_with_tombstone_base_seq_after_all_tombstones() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(8); - - let range = key("k1")..=key("k6"); - let mut stream = view.range(TestSpace::Space1, range).await.unwrap(); - - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 6); - assert_eq!( - results[0], - (key("k1"), SeqMarked::new_normal(1, value("v1"))) - ); - assert_eq!( - results[1], - (key("k2"), SeqMarked::new_normal(2, value("v2"))) - ); - assert_eq!( - results[2], - (key("k3"), SeqMarked::new_normal(3, value("v3"))) - ); - assert_eq!(results[3], (key("k4"), SeqMarked::new_tombstone(4))); - assert_eq!(results[4], (key("k5"), SeqMarked::new_tombstone(6))); // Can see the tombstone - assert_eq!(results[5], (key("k6"), SeqMarked::new_tombstone(8))); // Can see the tombstone - } - - #[tokio::test] - async fn test_multiple_spaces() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut table2 = Table::new(); - table2.insert(key("a1"), 1, value("av1")).unwrap(); - table2.insert(key("a2"), 2, value("av2")).unwrap(); - tables.insert(TestSpace::Space2, table2); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - // Test Space1 - let keys1 = vec![key("k1"), key("k2")]; - let result1 = view.get_many(TestSpace::Space1, keys1).await.unwrap(); - assert_eq!(result1.len(), 2); - assert_eq!(result1[0], SeqMarked::new_normal(1, value("v1"))); - assert_eq!(result1[1], SeqMarked::new_normal(2, value("v2"))); - - // Test Space2 - let keys2 = vec![key("a1"), key("a2")]; - let result2 = view.get_many(TestSpace::Space2, keys2).await.unwrap(); - assert_eq!(result2.len(), 2); - assert_eq!(result2[0], SeqMarked::new_normal(1, value("av1"))); - assert_eq!(result2[1], SeqMarked::new_normal(2, value("av2"))); - - // Test nonexistent Space3 - let keys3 = vec![key("x1")]; - let result3 = view.get_many(TestSpace::Space3, keys3).await.unwrap(); - assert_eq!(result3.len(), 1); - assert!(result3[0].is_not_found()); - } - - #[tokio::test] - async fn test_edge_cases() { - let mut tables = BTreeMap::new(); - let mut table = Table::new(); - - // Insert values with various sequence numbers - table.insert(key("k1"), 1, value("v1")).unwrap(); - table.insert_tombstone(key("k3"), 50).unwrap(); - table.insert(key("k2"), 100, value("v100")).unwrap(); - - tables.insert(TestSpace::Space1, table); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(75); // Between 50 and 100 - - let keys = vec![key("k1"), key("k2"), key("k3")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 3); - assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); - assert!(result[1].is_not_found()); // seq 100 > base_seq 75 - assert_eq!(result[2], SeqMarked::new_tombstone(50)); - } - - #[tokio::test] - async fn test_zero_base_seq() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(0); // Can't see anything - - let keys = vec![key("k1"), key("k2")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 2); - assert!(result[0].is_not_found()); - assert!(result[1].is_not_found()); - } - - #[tokio::test] - async fn test_not_found_base_seq() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(0); // Can't see anything - - let keys = vec![key("k1"), key("k2")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 2); - assert!(result[0].is_not_found()); - assert!(result[1].is_not_found()); - } - - // Additional corner case tests - - #[tokio::test] - async fn test_key_with_multiple_versions() { - let mut tables = BTreeMap::new(); - let mut table = Table::new(); - - // Create a key with multiple normal values and tombstones - table.insert(key("k1"), 1, value("v1")).unwrap(); - table.insert(key("k1"), 3, value("v1_updated")).unwrap(); - table.insert_tombstone(key("k1"), 5).unwrap(); - table.insert(key("k1"), 7, value("v1_resurrected")).unwrap(); // Resurrection - table.insert_tombstone(key("k1"), 9).unwrap(); // Final tombstone - - tables.insert(TestSpace::Space1, table); - let mut view = TableViewReadonly::new(tables); - - // Test at different view sequences - view.base_seq = InternalSeq::new(1); - let result = view - .get_many(TestSpace::Space1, vec![key("k1")]) - .await - .unwrap(); - assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); - - view.base_seq = InternalSeq::new(3); - let result = view - .get_many(TestSpace::Space1, vec![key("k1")]) - .await - .unwrap(); - assert_eq!(result[0], SeqMarked::new_normal(3, value("v1_updated"))); - - view.base_seq = InternalSeq::new(5); - let result = view - .get_many(TestSpace::Space1, vec![key("k1")]) - .await - .unwrap(); - assert_eq!(result[0], SeqMarked::new_tombstone(5)); - - view.base_seq = InternalSeq::new(7); - let result = view - .get_many(TestSpace::Space1, vec![key("k1")]) - .await - .unwrap(); - assert_eq!(result[0], SeqMarked::new_normal(7, value("v1_resurrected"))); - - view.base_seq = InternalSeq::new(9); - let result = view - .get_many(TestSpace::Space1, vec![key("k1")]) - .await - .unwrap(); - assert_eq!(result[0], SeqMarked::new_tombstone(9)); - } - - #[tokio::test] - async fn test_range_unbounded() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - // Test unbounded range (..) - let mut stream = view.range(TestSpace::Space1, ..).await.unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 6); - assert_eq!( - results[0], - (key("k1"), SeqMarked::new_normal(1, value("v1"))) - ); - assert_eq!(results[5], (key("k6"), SeqMarked::new_tombstone(8))); - } - - #[tokio::test] - async fn test_range_from_unbounded() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - // Test range from k3 to end - let mut stream = view.range(TestSpace::Space1, key("k3")..).await.unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 4); // k3, k4, k5, k6 - assert_eq!( - results[0], - (key("k3"), SeqMarked::new_normal(3, value("v3"))) - ); - assert_eq!(results[3], (key("k6"), SeqMarked::new_tombstone(8))); - } - - #[tokio::test] - async fn test_range_to_unbounded() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - // Test range from start to k3 (exclusive) - let mut stream = view.range(TestSpace::Space1, ..key("k4")).await.unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 3); // k1, k2, k3 - assert_eq!( - results[0], - (key("k1"), SeqMarked::new_normal(1, value("v1"))) - ); - assert_eq!( - results[2], - (key("k3"), SeqMarked::new_normal(3, value("v3"))) - ); - } - - #[tokio::test] - async fn test_range_exclusive() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - // Test exclusive range k2..k5 - let mut stream = view - .range(TestSpace::Space1, key("k2")..key("k5")) - .await - .unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 3); // k2, k3, k4 (k5 excluded) - assert_eq!( - results[0], - (key("k2"), SeqMarked::new_normal(2, value("v2"))) - ); - assert_eq!(results[2], (key("k4"), SeqMarked::new_tombstone(4))); - } - - #[tokio::test] - async fn test_range_single_point() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - // Test single point range k3..=k3 - let mut stream = view - .range(TestSpace::Space1, key("k3")..=key("k3")) - .await - .unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 1); - assert_eq!( - results[0], - (key("k3"), SeqMarked::new_normal(3, value("v3"))) - ); - } - - #[tokio::test] - async fn test_range_empty_result() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - // Test range that should be empty - let mut stream = view - .range(TestSpace::Space1, key("k5")..key("k5")) - .await - .unwrap(); - let result = stream.next().await; - assert!(result.is_none()); - } - - #[tokio::test] - async fn test_empty_table_space() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, Table::<_, String>::new()); // Empty table - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - // Test mget on empty table - let keys = vec![key("k1"), key("k2")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - assert_eq!(result.len(), 2); - assert!(result[0].is_not_found()); - assert!(result[1].is_not_found()); - - // Test range on empty table - let mut stream = view.range(TestSpace::Space1, ..).await.unwrap(); - let result = stream.next().await; - assert!(result.is_none()); - } - - #[tokio::test] - async fn test_base_seq_boundary_conditions() { - let mut tables = BTreeMap::new(); - let mut table = Table::new(); - - // Create entries at boundary sequence numbers - table - .insert(key("k1"), u64::MAX - 1, value("v_max_minus_1")) - .unwrap(); - table.insert_tombstone(key("k2"), u64::MAX).unwrap(); - - tables.insert(TestSpace::Space1, table); - let mut view = TableViewReadonly::new(tables); - - // - view.base_seq = InternalSeq::new(u64::MAX); - let keys = vec![key("k1"), key("k2")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 2); - assert_eq!( - result[0], - SeqMarked::new_normal(u64::MAX - 1, value("v_max_minus_1")) - ); - assert_eq!(result[1], SeqMarked::new_tombstone(u64::MAX)); - } - - #[tokio::test] - async fn test_range_with_filtered_out_keys() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(3); // Only see k1, k2, k3 - - // Test range that includes filtered keys - let mut stream = view - .range(TestSpace::Space1, key("k1")..=key("k6")) - .await - .unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - // Should only see k1, k2, k3 (k4, k5, k6 filtered out) - assert_eq!(results.len(), 3); - assert_eq!( - results[0], - (key("k1"), SeqMarked::new_normal(1, value("v1"))) - ); - assert_eq!( - results[1], - (key("k2"), SeqMarked::new_normal(2, value("v2"))) - ); - assert_eq!( - results[2], - (key("k3"), SeqMarked::new_normal(3, value("v3"))) - ); - } - - #[tokio::test] - async fn test_mget_mixed_found_and_not_found() { - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, create_test_table()); - - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - // Mix of existing and non-existing keys - let keys = vec![ - key("k1"), // exists - key("kx"), // doesn't exist - key("k3"), // exists - key("ky"), // doesn't exist - key("k5"), // exists (tombstone) - ]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 5); - assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); - assert!(result[1].is_not_found()); - assert_eq!(result[2], SeqMarked::new_normal(3, value("v3"))); - assert!(result[3].is_not_found()); - assert_eq!(result[4], SeqMarked::new_tombstone(6)); - } - - #[tokio::test] - async fn test_key_ordering_in_range() { - let mut tables = BTreeMap::new(); - let mut table = Table::new(); - - // Insert keys in non-lexicographic order to test sorting - table.insert(key("key10"), 1, value("v10")).unwrap(); - table.insert(key("key2"), 2, value("v2")).unwrap(); - table.insert(key("key1"), 3, value("v1")).unwrap(); - - tables.insert(TestSpace::Space1, table); - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); - - let mut stream = view.range(TestSpace::Space1, ..).await.unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - // Should be ordered lexicographically: key1, key10, key2 - assert_eq!(results.len(), 3); - assert_eq!( - results[0], - (key("key1"), SeqMarked::new_normal(3, value("v1"))) - ); - assert_eq!( - results[1], - (key("key10"), SeqMarked::new_normal(1, value("v10"))) - ); - assert_eq!( - results[2], - (key("key2"), SeqMarked::new_normal(2, value("v2"))) - ); - } -} diff --git a/src/mvcc/value.rs b/src/mvcc/value.rs index a242d2a..a6cbbbf 100644 --- a/src/mvcc/value.rs +++ b/src/mvcc/value.rs @@ -16,7 +16,19 @@ use std::fmt; /// Trait for types that can be stored as values in MVCC operations. /// -/// Values must be cloneable for versioning and serializable for storage. +/// # Requirements +/// +/// Values must satisfy multiple constraints for versioned storage: +/// - **Cloning**: `Clone` enables efficient versioning and multi-reader access +/// - **Threading**: `Send + Sync` allows concurrent access across threads +/// - **Debugging**: `Debug` provides troubleshooting and logging capabilities +/// - **Async**: `Unpin` enables use in async stream operations +/// +/// # Automatic Implementation +/// +/// This trait is automatically implemented for any type that meets the trait bounds. +/// Common value types include `String`, primitive types, `Vec`, serializable structs, +/// and enum variants that derive the required traits. pub trait ViewValue: fmt::Debug + Clone + Send + Sync + Unpin + 'static {} impl ViewValue for V where V: fmt::Debug + Clone + Send + Sync + Unpin + 'static {} diff --git a/src/mvcc/view.rs b/src/mvcc/view.rs index b7881e7..1deb161 100644 --- a/src/mvcc/view.rs +++ b/src/mvcc/view.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::collections::BTreeMap; +use std::fmt; use std::io; use std::ops::RangeBounds; @@ -26,11 +27,11 @@ use stream_more::StreamMore; use crate::compact::compact_seq_marked_pair; use crate::mvcc::commit::Commit; use crate::mvcc::key::ViewKey; -use crate::mvcc::namespace_view::NamespaceView; +use crate::mvcc::seq_bounded_read::SeqBoundedRead; +use crate::mvcc::snapshot::Snapshot; use crate::mvcc::table::Table; use crate::mvcc::value::ViewValue; use crate::mvcc::view_namespace::ViewNamespace; -use crate::mvcc::view_readonly::ViewReadonly; use crate::util; use crate::IOResultStream; @@ -128,12 +129,12 @@ use crate::IOResultStream; /// - `V`: value type /// - `BaseView`: the base view that this view is based on. /// `BaseView` is usually a `ViewRo` that is created from the snapshot of the underlying storage. -pub struct View +pub struct View where S: ViewNamespace, K: ViewKey, V: ViewValue, - BaseView: ViewReadonly + Commit, + D: SeqBoundedRead + Commit, { /// Whether to increase the seq for tombstone insertion. /// @@ -148,18 +149,39 @@ where /// This seq will be updated to the underlaying [`Table`] when the transaction is committed. pub(crate) last_seq: InternalSeq, - pub(crate) base: BaseView, + pub(crate) base: Snapshot, } -impl View +impl fmt::Debug for View where S: ViewNamespace, K: ViewKey, V: ViewValue, - BaseView: ViewReadonly + Commit, + D: SeqBoundedRead + Commit, + D: fmt::Debug, { - pub fn new(base: BaseView) -> Self { - let seq = base.view_seq(); + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("View") + .field( + "increase_seq_for_tombstone", + &self.increase_seq_for_tombstone, + ) + .field("changes", &self.changes) + .field("last_seq", &self.last_seq) + .field("base", &self.base) + .finish() + } +} + +impl View +where + S: ViewNamespace, + K: ViewKey, + V: ViewValue, + D: SeqBoundedRead + Commit, +{ + pub fn new(base: Snapshot) -> Self { + let seq = base.snapshot_seq(); Self { increase_seq_for_tombstone: false, changes: BTreeMap::new(), @@ -178,6 +200,11 @@ where self } + /// Return the reference to the snapshot this view is based on. + pub fn snapshot(&self) -> &Snapshot { + &self.base + } + fn current_normal_seq(&self) -> SeqMarked<()> { debug!("current_normal_seq: last_seq: {}", self.last_seq); SeqMarked::new_normal(*self.last_seq, ()) @@ -204,7 +231,7 @@ where SeqMarked::new_tombstone(*self.last_seq) } - pub fn base(&self) -> &BaseView { + pub fn base(&self) -> &Snapshot { &self.base } @@ -334,41 +361,9 @@ where Ok(coalesce.boxed()) } - pub async fn commit(mut self) -> Result { - self.base.commit(self.last_seq, self.changes).await?; - Ok(self.base) - } - - pub fn namespace(&mut self, space: S) -> NamespaceView<'_, S, K, V, BaseView> { - NamespaceView { space, view: self } - } -} - -#[async_trait::async_trait] -impl ViewReadonly for View -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, - BaseView: ViewReadonly + Commit, -{ - fn view_seq(&self) -> InternalSeq { - self.base.view_seq() - } - - async fn get(&self, space: S, key: K) -> Result, io::Error> { - self.base.get(space, key).await - } - - async fn range( - &self, - space: S, - range: R, - ) -> Result)>, io::Error> - where - R: RangeBounds + Send + Sync + Clone + 'static, - { - self.base.range(space, range).await + pub async fn commit(self) -> Result { + let d = self.base.commit(self.last_seq, self.changes).await?; + Ok(d) } } @@ -381,7 +376,9 @@ mod tests { use seq_marked::SeqMarked; use super::*; - use crate::mvcc::table::TableViewReadonly; + use crate::mvcc::seq_bounded_get::SeqBoundedGet; + use crate::mvcc::table::Tables; + use crate::mvcc::table::TablesSnapshot; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum TestSpace { @@ -408,33 +405,32 @@ mod tests { TestValue(s.to_string()) } - fn create_base_view() -> TableViewReadonly { + fn create_base_snapshot() -> TablesSnapshot { let mut table = Table::new(); table.insert(key("base_k1"), 1, value("base_v1")).unwrap(); table.insert(key("base_k2"), 2, value("base_v2")).unwrap(); let mut tables = BTreeMap::new(); tables.insert(TestSpace::Space1, table); - let mut view = TableViewReadonly::new(tables); - view.base_seq = InternalSeq::new(10); // Set high enough to see all base data - view + + TablesSnapshot::new(InternalSeq::new(10), tables) } fn create_view( - base: TableViewReadonly, - ) -> View> { + base: TablesSnapshot, + ) -> View> { View::new(base) } fn create_view_with_tombstone_seq( - base: TableViewReadonly, - ) -> View> { + base: TablesSnapshot, + ) -> View> { View::new(base).with_tombstone_seq_increment(true) } #[tokio::test] async fn test_set_normal_value() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); @@ -449,7 +445,7 @@ mod tests { #[tokio::test] async fn test_set_tombstone_no_seq_increase() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // First insert view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); @@ -469,7 +465,7 @@ mod tests { #[tokio::test] async fn test_set_tombstone_with_seq_increase() { - let mut view = create_view_with_tombstone_seq(create_base_view()); + let mut view = create_view_with_tombstone_seq(create_base_snapshot()); // First insert view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); @@ -489,7 +485,7 @@ mod tests { #[tokio::test] async fn test_mget_from_base_only() { - let view = create_view(create_base_view()); + let view = create_view(create_base_snapshot()); let keys = vec![key("base_k1"), key("base_k2")]; let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); @@ -501,7 +497,7 @@ mod tests { #[tokio::test] async fn test_mget_from_changes_only() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); view.set(TestSpace::Space1, key("new_k1"), Some(value("new_v1"))); view.set(TestSpace::Space1, key("new_k2"), Some(value("new_v2"))); @@ -526,7 +522,7 @@ mod tests { #[tokio::test] async fn test_mget_merge_base_and_changes() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Override base value view.set(TestSpace::Space1, key("base_k1"), Some(value("updated_v1"))); // Add new value @@ -554,7 +550,7 @@ mod tests { #[tokio::test] async fn test_mget_tombstone_override() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Delete base value view.set(TestSpace::Space1, key("base_k1"), None); @@ -572,7 +568,7 @@ mod tests { #[tokio::test] async fn test_mget_nonexistent_space() { - let view = create_view(create_base_view()); + let view = create_view(create_base_snapshot()); let keys = vec![key("k1")]; let result = view.get_many(TestSpace::Space2, keys).await.unwrap(); @@ -583,7 +579,7 @@ mod tests { #[tokio::test] async fn test_range_from_base_only() { - let view = create_view(create_base_view()); + let view = create_view(create_base_snapshot()); let mut stream = view.range(TestSpace::Space1, ..).await.unwrap(); let mut results = Vec::new(); @@ -604,7 +600,7 @@ mod tests { #[tokio::test] async fn test_range_from_changes_only() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); view.set(TestSpace::Space2, key("new_k1"), Some(value("new_v1"))); view.set(TestSpace::Space2, key("new_k2"), Some(value("new_v2"))); @@ -638,7 +634,7 @@ mod tests { #[tokio::test] async fn test_range_merge_base_and_changes() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); view.set(TestSpace::Space1, key("base_k1"), Some(value("updated_v1"))); view.set(TestSpace::Space1, key("new_k1"), Some(value("new_v1"))); @@ -680,7 +676,7 @@ mod tests { #[tokio::test] async fn test_range_nonexistent_space() { - let view = create_view(create_base_view()); + let view = create_view(create_base_snapshot()); let mut stream = view.range(TestSpace::Space2, ..).await.unwrap(); let result = stream.next().await; @@ -690,7 +686,7 @@ mod tests { #[tokio::test] async fn test_commit() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); view.set(TestSpace::Space2, key("k2"), Some(value("v2"))); @@ -708,22 +704,31 @@ mod tests { assert_eq!(view.changes.len(), 2); - let base_view = view.commit().await.unwrap(); + let base_data = view.commit().await.unwrap(); // Verify the base view contains the committed changes let keys = vec![key("k1")]; - let result = base_view.get_many(TestSpace::Space1, keys).await.unwrap(); + let result = base_data + .get_many(TestSpace::Space1, keys, u64::MAX) + .await + .unwrap(); assert_eq!(result.len(), 1); assert_eq!(result[0], SeqMarked::new_normal(11, value("v1"))); let keys = vec![key("k2")]; - let result = base_view.get_many(TestSpace::Space2, keys).await.unwrap(); + let result = base_data + .get_many(TestSpace::Space2, keys, u64::MAX) + .await + .unwrap(); assert_eq!(result.len(), 1); assert_eq!(result[0], SeqMarked::new_normal(12, value("v2"))); // Should also still contain original base data let keys = vec![key("base_k1"), key("base_k2")]; - let result = base_view.get_many(TestSpace::Space1, keys).await.unwrap(); + let result = base_data + .get_many(TestSpace::Space1, keys, u64::MAX) + .await + .unwrap(); assert_eq!(result.len(), 2); assert_eq!(result[0], SeqMarked::new_normal(1, value("base_v1"))); assert_eq!(result[1], SeqMarked::new_normal(2, value("base_v2"))); @@ -731,7 +736,7 @@ mod tests { #[tokio::test] async fn test_sequence_ordering() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); assert_eq!(view.last_seq, InternalSeq::new(11)); @@ -760,7 +765,7 @@ mod tests { #[tokio::test] async fn test_multiple_spaces() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); view.set(TestSpace::Space2, key("k2"), Some(value("v2"))); @@ -785,7 +790,7 @@ mod tests { #[tokio::test] async fn test_complex_scenario() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Insert new values view.set(TestSpace::Space1, key("new_k1"), Some(value("new_v1"))); @@ -828,31 +833,33 @@ mod tests { #[tokio::test] async fn test_empty_base_view() { let tables = BTreeMap::new(); // Completely empty base - let base = TableViewReadonly::new(tables); + let base = TablesSnapshot::new(InternalSeq::new(1), tables); let mut view = create_view(base); // Operations on completely empty view view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); view.set(TestSpace::Space1, key("k2"), None); + println!("{:#?}", view); + // Verify operations work on empty base let table = &view.changes[&TestSpace::Space1]; assert_eq!( table.get(key("k1"), 2), - SeqMarked::new_normal(1, &value("v1")) + SeqMarked::new_normal(2, &value("v1")) ); - assert_eq!(table.get(key("k2"), 2), SeqMarked::new_tombstone(1)); + assert_eq!(table.get(key("k2"), 2), SeqMarked::new_tombstone(2)); // mget should work with empty base let keys = vec![key("k1"), key("k2")]; let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); - assert_eq!(result[1], SeqMarked::new_tombstone(1)); + assert_eq!(result[0], SeqMarked::new_normal(2, value("v1"))); + assert_eq!(result[1], SeqMarked::new_tombstone(2)); } #[tokio::test] async fn test_zero_initial_sequence() { - let mut view = View::new(create_base_view()).with_initial_seq(InternalSeq::new(0)); + let mut view = View::new(create_base_snapshot()).with_initial_seq(InternalSeq::new(0)); view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); assert_eq!(view.last_seq, InternalSeq::new(1)); @@ -872,7 +879,7 @@ mod tests { #[tokio::test] async fn test_max_sequence_boundary() { let mut view = - View::new(create_base_view()).with_initial_seq(InternalSeq::new(u64::MAX - 2)); + View::new(create_base_snapshot()).with_initial_seq(InternalSeq::new(u64::MAX - 2)); view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); assert_eq!(view.last_seq, InternalSeq::new(u64::MAX - 1)); @@ -889,7 +896,7 @@ mod tests { #[tokio::test] async fn test_tombstone_resurrection() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Delete base key view.set(TestSpace::Space1, key("base_k1"), None); @@ -916,7 +923,7 @@ mod tests { #[tokio::test] async fn test_multiple_updates_same_key() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Multiple updates to same key view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); @@ -951,7 +958,7 @@ mod tests { #[tokio::test] async fn test_empty_key_lists() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); // Empty key list should return empty result @@ -965,7 +972,7 @@ mod tests { #[tokio::test] async fn test_range_bounded_operations() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); view.set(TestSpace::Space1, key("a"), Some(value("va"))); view.set(TestSpace::Space1, key("c"), Some(value("vc"))); view.set(TestSpace::Space1, key("e"), Some(value("ve"))); @@ -1017,7 +1024,7 @@ mod tests { #[tokio::test] async fn test_range_with_all_tombstones() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Delete all base keys and add only tombstones view.set(TestSpace::Space1, key("base_k1"), None); @@ -1041,7 +1048,7 @@ mod tests { #[tokio::test] async fn test_range_single_key() { - let view = create_view(create_base_view()); + let view = create_view(create_base_snapshot()); // Single key range let mut stream = view @@ -1062,7 +1069,7 @@ mod tests { #[tokio::test] async fn test_range_empty_result() { - let view = create_view(create_base_view()); + let view = create_view(create_base_snapshot()); // Range that matches no keys let mut stream = view @@ -1089,8 +1096,8 @@ mod tests { let mut tables = BTreeMap::new(); tables.insert(TestSpace::Space1, table); - let mut base = TableViewReadonly::new(tables); - base.base_seq = InternalSeq::new(15); // Can only see k1, not k2 + // Can only see k1, not k2 + let base = TablesSnapshot::new(InternalSeq::new(15), tables); let view = create_view(base); @@ -1104,7 +1111,7 @@ mod tests { #[tokio::test] async fn test_large_key_value_operations() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Large key and value let large_key = key(&"x".repeat(1000)); @@ -1129,7 +1136,7 @@ mod tests { #[tokio::test] async fn test_many_keys_operation() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Insert many keys for i in 0..100 { @@ -1158,7 +1165,7 @@ mod tests { #[tokio::test] async fn test_sequence_exact_match_boundary() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); // seq 6 view.set(TestSpace::Space1, key("k2"), Some(value("v2"))); // seq 7 @@ -1174,7 +1181,7 @@ mod tests { #[tokio::test] async fn test_cross_space_sequence_isolation() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Operations in different spaces should share sequence counter view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); // seq 6 @@ -1203,7 +1210,7 @@ mod tests { #[tokio::test] async fn test_set_return_value() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Test normal value insertion let order_key1 = view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); @@ -1224,7 +1231,7 @@ mod tests { #[tokio::test] async fn test_set_return_value_with_increment() { - let mut view = create_view_with_tombstone_seq(create_base_view()); + let mut view = create_view_with_tombstone_seq(create_base_snapshot()); // Test normal value insertion let order_key1 = view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); @@ -1241,7 +1248,7 @@ mod tests { #[tokio::test] async fn test_fetch_and_set_nonexistent() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); let (old_value, new_value) = view .fetch_and_set(TestSpace::Space1, key("new_key"), Some(value("new_value"))) @@ -1260,7 +1267,7 @@ mod tests { #[tokio::test] async fn test_fetch_and_set_existing_from_base() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); let (old_value, new_value) = view .fetch_and_set( @@ -1286,7 +1293,7 @@ mod tests { #[tokio::test] async fn test_fetch_and_set_existing_from_changes() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // First set a value to create changes view.set( @@ -1319,7 +1326,7 @@ mod tests { #[tokio::test] async fn test_fetch_and_set_tombstone() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); let (old_value, new_value) = view .fetch_and_set(TestSpace::Space1, key("base_k1"), None) @@ -1338,7 +1345,7 @@ mod tests { #[tokio::test] async fn test_fetch_and_set_cross_space() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Set value in Space1 let (old1, new1) = view @@ -1364,7 +1371,7 @@ mod tests { #[tokio::test] async fn test_fetch_and_set_tombstone_resurrection() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // First delete a base key let (old1, new1) = view @@ -1396,7 +1403,7 @@ mod tests { #[tokio::test] async fn test_fetch_and_set_delete_nonexistent() { - let mut view = create_view(create_base_view()); + let mut view = create_view(create_base_snapshot()); // Try to delete a key that doesn't exist let (old_value, new_value) = view diff --git a/src/mvcc/view_readonly.rs b/src/mvcc/view_readonly.rs deleted file mode 100644 index 25e8226..0000000 --- a/src/mvcc/view_readonly.rs +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2021 Datafuse Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::io; -use std::ops::RangeBounds; - -use seq_marked::InternalSeq; -use seq_marked::SeqMarked; - -use crate::mvcc::ViewKey; -use crate::mvcc::ViewNamespace; -use crate::mvcc::ViewValue; -use crate::IOResultStream; - -/// Read-only view providing snapshot isolation at a specific sequence point. -/// -/// All operations see a consistent snapshot as of `base_seq()`. Safe for concurrent access. -/// -/// ⚠️ **Tombstone Anomaly**: Deletion operations may reuse sequence numbers, -/// causing inconsistent visibility at sequence boundaries. -#[async_trait::async_trait] -pub trait ViewReadonly -where - Self: Send + Sync, - S: ViewNamespace, - K: ViewKey, - V: ViewValue, -{ - /// Maximum sequence number visible in this view. - fn view_seq(&self) -> InternalSeq; - - /// Get value for key in the specified namespace. - async fn get(&self, space: S, key: K) -> Result, io::Error>; - - /// Get multiple keys atomically. Defaults to sequential `get()` calls. - async fn get_many(&self, space: S, keys: Vec) -> Result>, io::Error> { - let mut results = Vec::with_capacity(keys.len()); - for key in keys { - let result = self.get(space, key).await?; - results.push(result); - } - Ok(results) - } - - /// Stream key-value pairs within the specified range in sorted order. - async fn range( - &self, - space: S, - range: R, - ) -> Result)>, io::Error> - where - R: RangeBounds + Send + Sync + Clone + 'static; -} - -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - - use futures_util::stream; - use seq_marked::SeqMarked; - - use super::*; - - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - struct TestNamespace(u8); - - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] - struct TestKey(String); - - #[derive(Debug, Clone, PartialEq, Eq)] - struct TestValue(String); - - impl ViewNamespace for TestNamespace { - fn increments_seq(&self) -> bool { - true - } - } - - fn ns(id: u8) -> TestNamespace { - TestNamespace(id) - } - - fn key(s: &str) -> TestKey { - TestKey(s.to_string()) - } - - fn value(s: &str) -> TestValue { - TestValue(s.to_string()) - } - - struct MockViewReadonly { - data: BTreeMap<(TestNamespace, TestKey), SeqMarked>, - seq: InternalSeq, - } - - impl MockViewReadonly { - fn new() -> Self { - let mut data = BTreeMap::new(); - - // Namespace 1 - data.insert((ns(1), key("k1")), SeqMarked::new_normal(1, value("v1"))); - data.insert((ns(1), key("k2")), SeqMarked::new_normal(2, value("v2"))); - data.insert((ns(1), key("k3")), SeqMarked::new_tombstone(3)); - data.insert((ns(1), key("k4")), SeqMarked::new_normal(5, value("v4"))); - - // Namespace 2 - data.insert((ns(2), key("k1")), SeqMarked::new_normal(4, value("n2v1"))); - - Self { - data, - seq: InternalSeq::new(10), - } - } - } - - #[async_trait::async_trait] - impl ViewReadonly for MockViewReadonly { - fn view_seq(&self) -> InternalSeq { - self.seq - } - - async fn get( - &self, - space: TestNamespace, - key: TestKey, - ) -> Result, io::Error> { - Ok(self - .data - .get(&(space, key)) - .cloned() - .unwrap_or_else(SeqMarked::new_not_found)) - } - - async fn range( - &self, - _space: TestNamespace, - _range: R, - ) -> Result)>, io::Error> - where - R: RangeBounds + Send + Sync + Clone + 'static, - { - Ok(Box::pin(stream::empty())) - } - } - - #[tokio::test] - async fn test_get_many_mixed() { - let view = MockViewReadonly::new(); - let keys = vec![key("k1"), key("nx"), key("k3"), key("k4")]; - let res = view.get_many(ns(1), keys).await.unwrap(); - - let expected = vec![ - SeqMarked::new_normal(1, value("v1")), - SeqMarked::new_not_found(), - SeqMarked::new_tombstone(3), - SeqMarked::new_normal(5, value("v4")), - ]; - assert_eq!(res, expected); - } - - #[tokio::test] - async fn test_get_many_empty() { - let view = MockViewReadonly::new(); - let res = view.get_many(ns(1), vec![]).await.unwrap(); - assert_eq!(res, vec![]); - } - - #[tokio::test] - async fn test_get_many_single() { - let view = MockViewReadonly::new(); - let keys = vec![key("k2")]; - let res = view.get_many(ns(1), keys).await.unwrap(); - let expected = vec![SeqMarked::new_normal(2, value("v2"))]; - assert_eq!(res, expected); - } - - #[tokio::test] - async fn test_get_many_duplicates() { - let view = MockViewReadonly::new(); - let keys = vec![key("k1"), key("k1"), key("k2")]; - let res = view.get_many(ns(1), keys).await.unwrap(); - let expected = vec![ - SeqMarked::new_normal(1, value("v1")), - SeqMarked::new_normal(1, value("v1")), - SeqMarked::new_normal(2, value("v2")), - ]; - assert_eq!(res, expected); - } - - #[tokio::test] - async fn test_get_many_maintains_order() { - let view = MockViewReadonly::new(); - let keys = vec![key("k4"), key("k1"), key("k2")]; - let res = view.get_many(ns(1), keys).await.unwrap(); - let expected = vec![ - SeqMarked::new_normal(5, value("v4")), - SeqMarked::new_normal(1, value("v1")), - SeqMarked::new_normal(2, value("v2")), - ]; - assert_eq!(res, expected); - } - - #[tokio::test] - async fn test_get_many_different_namespace() { - let view = MockViewReadonly::new(); - let keys = vec![key("k1")]; - - // Namespace 1 - let res = view.get_many(ns(1), keys.clone()).await.unwrap(); - assert_eq!(res, vec![SeqMarked::new_normal(1, value("v1"))]); - - // Namespace 2 - let res = view.get_many(ns(2), keys).await.unwrap(); - assert_eq!(res, vec![SeqMarked::new_normal(4, value("n2v1"))]); - } - - #[tokio::test] - async fn test_get_many_nonexistent_namespace() { - let view = MockViewReadonly::new(); - let keys = vec![key("k1"), key("k2")]; - let res = view.get_many(ns(99), keys).await.unwrap(); - let expected = vec![SeqMarked::new_not_found(), SeqMarked::new_not_found()]; - assert_eq!(res, expected); - } - - // Test that errors from get() are propagated - struct ErrorView; - - #[async_trait::async_trait] - impl ViewReadonly for ErrorView { - fn view_seq(&self) -> InternalSeq { - InternalSeq::new(0) - } - - async fn get( - &self, - _space: TestNamespace, - _key: TestKey, - ) -> Result, io::Error> { - Err(io::Error::new(io::ErrorKind::Other, "test error")) - } - - async fn range( - &self, - _space: TestNamespace, - _range: R, - ) -> Result)>, io::Error> - where - R: RangeBounds + Send + Sync + Clone + 'static, - { - Ok(Box::pin(stream::empty())) - } - } - - #[tokio::test] - async fn test_get_many_error_propagation() { - let view = ErrorView; - let keys = vec![key("k1")]; - let result = view.get_many(ns(1), keys).await; - assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "test error"); - } -}