From c108ba8b4e102ce55927d1f1545d9e255cf958a1 Mon Sep 17 00:00:00 2001 From: Zhang Yanpo Date: Sun, 19 Jul 2026 20:44:28 +0800 Subject: [PATCH 1/2] chore: bump version: 0.5.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f24973e..a16361a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "map-api" description = "Raft state machine" -version = "0.4.2" +version = "0.5.0" authors = ["Databend Authors "] license = "Apache-2.0" edition = "2021" From d97c52345d115fe79f739f7c72d5aa556b2950e7 Mon Sep 17 00:00:00 2001 From: Zhang Yanpo Date: Sun, 19 Jul 2026 20:41:11 +0800 Subject: [PATCH 2/2] refactor: remove multi-namespace support from mvcc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Summary The mvcc module's read/write/range API collapses from a namespace-scoped, per-call sequence-bounded trait zoo into a single-space view API: every `Table`, `Snapshot`, and `View` now owns exactly one key-value space, reads/writes/ranges are split into focused single-purpose traits that compose into `ViewApi`, and `View` itself implements that trait directly. # Details Removed the entire `scoped_*`/`seq_bounded_*` trait family (`ScopedApi`, `ScopedGet`, `ScopedRange`, `ScopedRead`, `ScopedSet`, `ScopedSeqBoundedGet`, `ScopedSeqBoundedIntoRange`, `ScopedSeqBoundedRange`, `ScopedSeqBoundedRangeIter`, `SeqBoundedGet`, `SeqBoundedIntoRange`, `SeqBoundedRange`, `SeqBoundedRangeIter`) along with `ViewNamespace`, `ViewKey`, `ViewValue`, and the `Commit` trait. `Table`'s own lookups already covered the point/multi-key/range cases, and key/value bounds now come from the crate's existing `MapKey`/`MapValue` traits, so this whole parallel hierarchy was redundant. In its place, `read_at_seq.rs` provides `GetAtSeq`/`RangeAtSeq` for per-call sequence-bounded reads, and `view_get.rs`/`view_set.rs`/ `view_range.rs` provide `ViewGet`/`ViewSet`/`ViewRange` for a view that owns its own sequence boundary. `ViewSet: ViewGet` is a supertrait, so its default `fetch_and_set()` gets `get()`/`set()` for free instead of every implementor rewriting it. `ViewApi` is a blanket trait over all three, and `View` now implements them directly instead of only exposing equivalent inherent methods. With `ViewNamespace` gone, `Table`, `Snapshot`, and `View` drop their namespace type parameter (`` becomes `` / ``), and the public `Tables`/`TablesSnapshot` type aliases that named the old `BTreeMap>` composition are gone too — every method loses its `space` argument. - The per-namespace `increments_seq()` flag is replaced by a per-`View` `with_tombstone_seq_increment()` setting plus explicit `set_without_seq_increment` / `fetch_and_set_without_seq_increment` calls for callers that need the old opt-out behavior. - `Snapshot::commit()` is gone. `View::into_parts()` now returns `(reader, last_seq, changes)` so callers merge with `Table::merge()` instead of going through a namespace-aware commit trait. - Dropped `namespace_view_no_seq_increase_test.rs`, which only exercised the now-removed per-namespace `increments_seq()` behavior. --- src/map_value.rs | 6 +- src/mvcc/commit.rs | 278 --- src/mvcc/key.rs | 38 - src/mvcc/mod.rs | 76 +- .../namespace_view_no_seq_increase_test.rs | 370 ---- src/mvcc/read_at_seq.rs | 167 ++ src/mvcc/scoped_api.rs | 54 - src/mvcc/scoped_get.rs | 37 - src/mvcc/scoped_seq_bounded_get.rs | 177 -- src/mvcc/scoped_seq_bounded_into_range.rs | 117 -- src/mvcc/scoped_seq_bounded_range.rs | 48 - src/mvcc/scoped_seq_bounded_range_iter.rs | 46 - src/mvcc/seq_bounded_get.rs | 329 ---- src/mvcc/seq_bounded_into_range.rs | 53 - src/mvcc/seq_bounded_range.rs | 206 --- src/mvcc/seq_bounded_range_iter.rs | 42 - src/mvcc/snapshot/mod.rs | 179 +- src/mvcc/table/impl_scoped_seq_bounded_get.rs | 201 --- .../impl_scoped_seq_bounded_range_iter.rs | 199 --- src/mvcc/table/mod.rs | 11 +- src/mvcc/table/table_snapshot.rs | 67 +- src/mvcc/value.rs | 34 - src/mvcc/view.rs | 1515 +++++------------ src/mvcc/{seq_bounded_read.rs => view_api.rs} | 34 +- src/mvcc/{scoped_read.rs => view_get.rs} | 34 +- src/mvcc/view_namespace.rs | 64 - src/mvcc/{scoped_range.rs => view_range.rs} | 21 +- src/mvcc/{scoped_set.rs => view_set.rs} | 113 +- 28 files changed, 745 insertions(+), 3771 deletions(-) delete mode 100644 src/mvcc/commit.rs delete mode 100644 src/mvcc/key.rs delete mode 100644 src/mvcc/namespace_view_no_seq_increase_test.rs create mode 100644 src/mvcc/read_at_seq.rs delete mode 100644 src/mvcc/scoped_api.rs delete mode 100644 src/mvcc/scoped_get.rs delete mode 100644 src/mvcc/scoped_seq_bounded_get.rs delete mode 100644 src/mvcc/scoped_seq_bounded_into_range.rs delete mode 100644 src/mvcc/scoped_seq_bounded_range.rs delete mode 100644 src/mvcc/scoped_seq_bounded_range_iter.rs delete mode 100644 src/mvcc/seq_bounded_get.rs delete mode 100644 src/mvcc/seq_bounded_into_range.rs delete mode 100644 src/mvcc/seq_bounded_range.rs delete mode 100644 src/mvcc/seq_bounded_range_iter.rs delete mode 100644 src/mvcc/table/impl_scoped_seq_bounded_get.rs delete mode 100644 src/mvcc/table/impl_scoped_seq_bounded_range_iter.rs delete mode 100644 src/mvcc/value.rs rename src/mvcc/{seq_bounded_read.rs => view_api.rs} (51%) rename src/mvcc/{scoped_read.rs => view_get.rs} (55%) delete mode 100644 src/mvcc/view_namespace.rs rename src/mvcc/{scoped_range.rs => view_range.rs} (61%) rename src/mvcc/{scoped_set.rs => view_set.rs} (75%) diff --git a/src/map_value.rs b/src/map_value.rs index 231c3cf..6b8dbe6 100644 --- a/src/map_value.rs +++ b/src/map_value.rs @@ -14,12 +14,14 @@ //! Defines the value behavior of the map. +use std::fmt::Debug; + /// MapValue defines the behavior of a value in a map. /// /// It is `Clone` to let MapApi return an owned value. /// It is `Unpin` to let MapApi extract a value from pinned data, such as a stream. /// And it only accepts `static` value for simplicity. -pub trait MapValue: Clone + Send + Sync + Unpin + 'static {} +pub trait MapValue: Clone + Debug + Send + Sync + Unpin + 'static {} // Auto implement MapValue for all types that satisfy the constraints. -impl MapValue for V where V: Clone + Send + Sync + Unpin + 'static {} +impl MapValue for V where V: Clone + Debug + Send + Sync + Unpin + 'static {} diff --git a/src/mvcc/commit.rs b/src/mvcc/commit.rs deleted file mode 100644 index c7d9701..0000000 --- a/src/mvcc/commit.rs +++ /dev/null @@ -1,278 +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::key::ViewKey; -use crate::mvcc::table::Table; -use crate::mvcc::value::ViewValue; -use crate::mvcc::view_namespace::ViewNamespace; - -/// Trait for committing staged changes to persistent storage. -/// -/// Implementors define how transactions are atomically applied to underlying data stores. -/// All changes within a transaction are applied together or fail together. -#[async_trait::async_trait] -pub trait Commit -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, -{ - /// Atomically apply staged changes to underlying storage. - /// - /// # Parameters - /// - `last_seq`: Highest sequence number in the changes - /// - `changes`: Pending modifications organized by namespace - /// - /// # Errors - /// Returns `io::Error` if the commit operation fails, leaving the storage unchanged. - async fn commit( - &mut self, - last_seq: InternalSeq, - 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 deleted file mode 100644 index a377c39..0000000 --- a/src/mvcc/key.rs +++ /dev/null @@ -1,38 +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::fmt; - -/// Trait for types that can be used as keys in MVCC operations. -/// -/// # 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 -{ -} - -impl ViewKey for K where K: Clone + Ord + fmt::Debug + Send + Sync + Unpin + 'static {} diff --git a/src/mvcc/mod.rs b/src/mvcc/mod.rs index 2880931..540ae56 100644 --- a/src/mvcc/mod.rs +++ b/src/mvcc/mod.rs @@ -14,87 +14,61 @@ //! Multi-Version Concurrency Control (MVCC) for versioned key-value storage. //! -//! Provides **Read Committed** isolation level with atomic transactions and snapshot consistency. -//! Data is organized by namespaces with support for concurrent read-write operations. +//! Provides read-committed isolation with staged writes and snapshot-consistent reads. //! //! # Architecture //! //! - **[`Table`]**: In-memory versioned storage with sequence-based ordering -//! - **[`View`]**: Read-write transaction with staged changes and commit capability +//! - **[`View`]**: Read-write transaction with staged changes //! - **[`Snapshot`]**: Read-only point-in-time view with fixed sequence boundary -//! - **Scoped APIs**: Namespace-bound convenience methods ([`ScopedApi`], [`ScopedGet`], etc.) +//! - **[`ViewApi`]**: High-level reads and writes that own their sequence boundary +//! - **[`GetAtSeq`]** and **[`RangeAtSeq`]**: Low-level reads at an explicit sequence boundary //! //! # 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 -//! use crate::mvcc::{Table, View}; +//! use crate::mvcc::{Snapshot, Table, View}; +//! use seq_marked::InternalSeq; //! //! // Create table and transaction view -//! let table = Table::new(); -//! let mut view = View::new(table); +//! let table = Table::>::new(); +//! let snapshot = Snapshot::new(InternalSeq::new(0), table); +//! let mut view = View::new(snapshot); //! //! // Stage changes within transaction -//! view.set(namespace, "key1".to_string(), Some("value1".to_string())); -//! view.set(namespace, "key2".to_string(), None); // deletion +//! view.set("key1".to_string(), Some(b"value1".to_vec())); +//! view.set("key2".to_string(), None); // deletion //! //! // Read includes staged changes -//! let current = view.get(namespace, "key1".to_string()).await?; +//! let current = view.get("key1".to_string()).await?; //! -//! // Atomic commit of all changes -//! let updated_table = view.commit().await?; +//! // Give the low-level store the staged changes to commit atomically. +//! let (_reader, last_seq, changes) = view.into_parts(); //! ``` pub mod coalesce; -pub mod commit; -pub mod key; -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 read_at_seq; pub mod snapshot; pub mod snapshot_seq; pub mod table; -pub mod value; pub mod view; -pub mod view_namespace; - -#[cfg(test)] -mod namespace_view_no_seq_increase_test; - -pub use self::commit::Commit; -pub use self::key::ViewKey; -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::ScopedSeqBoundedIntoRange; -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 mod view_api; +pub mod view_get; +pub mod view_range; +pub mod view_set; +pub use self::read_at_seq::GetAtSeq; +pub use self::read_at_seq::RangeAtSeq; pub use self::snapshot::Snapshot; pub use self::snapshot_seq::SnapshotSeq; pub use self::table::Table; -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_api::ViewApi; +pub use self::view_get::ViewGet; +pub use self::view_range::ViewRange; +pub use self::view_set::ViewSet; diff --git a/src/mvcc/namespace_view_no_seq_increase_test.rs b/src/mvcc/namespace_view_no_seq_increase_test.rs deleted file mode 100644 index 8d40898..0000000 --- a/src/mvcc/namespace_view_no_seq_increase_test.rs +++ /dev/null @@ -1,370 +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. - -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - - use futures_util::StreamExt; - use seq_marked::InternalSeq; - use seq_marked::SeqMarked; - - use crate::mvcc::table::TablesSnapshot; - use crate::mvcc::view::View; - use crate::mvcc::view_namespace::ViewNamespace; - use crate::mvcc::Table; - - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - enum TestSpaceNoSeqIncrease { - Space1, - } - - impl ViewNamespace for TestSpaceNoSeqIncrease { - fn increments_seq(&self) -> bool { - false // This namespace does NOT increase sequence on normal inserts - } - } - - #[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() -> 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); - // Set high enough to see all base data - - TablesSnapshot::new(InternalSeq::new(10), tables) - } - - #[tokio::test] - async fn test_no_seq_increase_single_insert() { - let mut view = View::new(create_base_view()); - - // Insert a value - should NOT increment sequence - let initial_seq = view.last_seq; - let result_seq = view.set(TestSpaceNoSeqIncrease::Space1, key("k1"), Some(value("v1"))); - - // Sequence should remain the same - assert_eq!(view.last_seq, initial_seq); - assert_eq!(result_seq, SeqMarked::new_normal(*initial_seq, ())); - - // Verify the value was stored at the current sequence - let table = &view.changes[&TestSpaceNoSeqIncrease::Space1]; - assert_eq!( - table.get(key("k1"), *initial_seq), - SeqMarked::new_normal(*initial_seq, &value("v1")) - ); - } - - #[tokio::test] - async fn test_no_seq_increase_cannot_insert_multiple_values_same_seq() { - let mut view = View::new(create_base_view()); - - let initial_seq = view.last_seq; - - // First insert should succeed - view.set(TestSpaceNoSeqIncrease::Space1, key("k1"), Some(value("v1"))); - assert_eq!(view.last_seq, initial_seq); - - // The table constraint prevents multiple normal values at the same sequence - // This is by design - non-incrementing namespaces are intended for secondary indices - // where you typically have one operation per sequence number - - // Verify first value was stored - let table = &view.changes[&TestSpaceNoSeqIncrease::Space1]; - assert_eq!( - table.get(key("k1"), *initial_seq), - SeqMarked::new_normal(*initial_seq, &value("v1")) - ); - - // Note: Attempting another insert at the same sequence would fail with NonIncremental error - // This is expected behavior for the current table implementation - } - - #[tokio::test] - async fn test_no_seq_increase_constraint() { - // This test demonstrates the table constraint that prevents - // inserting normal values at the same sequence number - let mut view = View::new(create_base_view()); - - let initial_seq = view.last_seq; - - // First insert should succeed and use current sequence without incrementing - view.set(TestSpaceNoSeqIncrease::Space1, key("k1"), Some(value("v1"))); - assert_eq!(view.last_seq, initial_seq); - - // The table constraint means we cannot insert another normal value at the same sequence - // This is by design - the table requires seq_marked > last_seq for normal values - - // What we CAN do is insert a tombstone at the same sequence (since tombstones allow >=) - view.set(TestSpaceNoSeqIncrease::Space1, key("k2"), None); - assert_eq!(view.last_seq, initial_seq); // Still no increment - - // Verify the values - let table = &view.changes[&TestSpaceNoSeqIncrease::Space1]; - assert_eq!( - table.get(key("k1"), *initial_seq), - SeqMarked::new_normal(*initial_seq, &value("v1")) - ); - assert_eq!( - table.get(key("k2"), *initial_seq), - SeqMarked::new_tombstone(*initial_seq) - ); - } - - #[tokio::test] - async fn test_no_seq_increase_tombstone_behavior() { - let mut view = View::new(create_base_view()); - - let initial_seq = view.last_seq; - - // Insert a value - view.set(TestSpaceNoSeqIncrease::Space1, key("k1"), Some(value("v1"))); - assert_eq!(view.last_seq, initial_seq); - - // Delete (tombstone) - should also use same sequence (default tombstone behavior) - view.set(TestSpaceNoSeqIncrease::Space1, key("k1"), None); - assert_eq!(view.last_seq, initial_seq); // Still no change - - // Should see tombstone at the same sequence - let table = &view.changes[&TestSpaceNoSeqIncrease::Space1]; - assert_eq!( - table.get(key("k1"), *initial_seq), - SeqMarked::new_tombstone(*initial_seq) - ); - } - - #[tokio::test] - async fn test_no_seq_increase_with_tombstone_seq_increment() { - let mut view = View::new(create_base_view()).with_tombstone_seq_increment(true); - - let initial_seq = view.last_seq; - - // Insert a value - should NOT increment sequence - view.set(TestSpaceNoSeqIncrease::Space1, key("k1"), Some(value("v1"))); - assert_eq!(view.last_seq, initial_seq); - - // Delete (tombstone) - SHOULD increment sequence because tombstone increment is enabled - view.set(TestSpaceNoSeqIncrease::Space1, key("k1"), None); - assert_eq!(view.last_seq, InternalSeq::new(*initial_seq + 1)); - - // Should see tombstone at the incremented sequence - let table = &view.changes[&TestSpaceNoSeqIncrease::Space1]; - assert_eq!( - table.get(key("k1"), *initial_seq + 1), - SeqMarked::new_tombstone(*initial_seq + 1) - ); - } - - #[tokio::test] - async fn test_no_seq_increase_secondary_index_pattern() { - // Demonstrate the intended use case: secondary index operations - let mut view = View::new(create_base_view()); - - let initial_seq = view.last_seq; - - // Insert a value in the primary space (which would increment sequence) - // Then insert corresponding secondary index entry (which should use current sequence) - view.set( - TestSpaceNoSeqIncrease::Space1, - key("secondary_index_k1"), - Some(value("index_v1")), - ); - - // Sequence should not increment for secondary index space - assert_eq!(view.last_seq, initial_seq); - - // Verify the secondary index entry - let table = &view.changes[&TestSpaceNoSeqIncrease::Space1]; - assert_eq!( - table.get(key("secondary_index_k1"), *initial_seq), - SeqMarked::new_normal(*initial_seq, &value("index_v1")) - ); - - // Test mget on secondary index - let keys = vec![key("secondary_index_k1")]; - let result = view - .get_many(TestSpaceNoSeqIncrease::Space1, keys) - .await - .unwrap(); - - assert_eq!(result.len(), 1); - assert_eq!( - result[0], - SeqMarked::new_normal(*initial_seq, value("index_v1")) - ); - } - - #[tokio::test] - async fn test_no_seq_increase_range() { - let mut view = View::new(create_base_view()); - - let initial_seq = view.last_seq; - - // Insert a single secondary index entry - view.set( - TestSpaceNoSeqIncrease::Space1, - key("range_k1"), - Some(value("rv1")), - ); - - // Sequence should not increment - assert_eq!(view.last_seq, initial_seq); - - // Test range - let mut stream = view - .range( - TestSpaceNoSeqIncrease::Space1, - key("range_k1")..=key("range_k1"), - ) - .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("range_k1"), - SeqMarked::new_normal(*initial_seq, value("rv1")) - ) - ); - } - - #[tokio::test] - async fn test_no_seq_increase_fetch_and_set() { - let mut view = View::new(create_base_view()); - - let initial_seq = view.last_seq; - - // Test fetch_and_set on non-existent key - let (old_val, new_val) = view - .fetch_and_set(TestSpaceNoSeqIncrease::Space1, key("k1"), Some(value("v1"))) - .await - .unwrap(); - - assert!(old_val.is_not_found()); - assert_eq!(new_val, SeqMarked::new_normal(*initial_seq, value("v1"))); - assert_eq!(view.last_seq, initial_seq); // No sequence increment - - // Note: Due to table constraints, we cannot do another fetch_and_set - // with a normal value at the same sequence. This is expected behavior. - } - - #[tokio::test] - async fn test_no_seq_increase_mixed_with_increasing_space() { - // Create a view that uses both types of namespaces - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - enum MixedSpace { - NoIncrement, - WithIncrement, - } - - impl ViewNamespace for MixedSpace { - fn increments_seq(&self) -> bool { - match self { - MixedSpace::NoIncrement => false, - MixedSpace::WithIncrement => true, - } - } - } - - let tables = BTreeMap::new(); - 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; - - // Insert into no-increment space - should NOT increment sequence - view.set(MixedSpace::NoIncrement, key("k1"), Some(value("v1"))); - assert_eq!(view.last_seq, initial_seq); - - // Insert into increment space - SHOULD increment sequence - view.set(MixedSpace::WithIncrement, key("k2"), Some(value("v2"))); - assert_eq!(view.last_seq, InternalSeq::new(*initial_seq + 1)); - - // Insert again into no-increment space - should use current sequence without incrementing - view.set(MixedSpace::NoIncrement, key("k3"), Some(value("v3"))); - assert_eq!(view.last_seq, InternalSeq::new(*initial_seq + 1)); // No further increment - - // Verify the values are stored at correct sequences - let table_no_inc = &view.changes[&MixedSpace::NoIncrement]; - let table_inc = &view.changes[&MixedSpace::WithIncrement]; - - assert_eq!( - table_no_inc.get(key("k1"), *initial_seq + 1), - SeqMarked::new_normal(*initial_seq, &value("v1")) - ); - assert_eq!( - table_inc.get(key("k2"), *initial_seq + 1), - SeqMarked::new_normal(*initial_seq + 1, &value("v2")) - ); - assert_eq!( - table_no_inc.get(key("k3"), *initial_seq + 1), - SeqMarked::new_normal(*initial_seq + 1, &value("v3")) - ); - } - - #[tokio::test] - async fn test_no_seq_increase_empty_base_view() { - // 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 = 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)); - - // Insert single value - should use sequence 1 without incrementing - view.set(TestSpaceNoSeqIncrease::Space1, key("k1"), Some(value("v1"))); - assert_eq!(view.last_seq, InternalSeq::new(1)); - - // Verify value is stored at sequence 1 - let table = &view.changes[&TestSpaceNoSeqIncrease::Space1]; - assert_eq!( - table.get(key("k1"), 1), - SeqMarked::new_normal(1, &value("v1")) - ); - - // Test mget - let keys = vec![key("k1")]; - let result = view - .get_many(TestSpaceNoSeqIncrease::Space1, keys) - .await - .unwrap(); - assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); - } -} diff --git a/src/mvcc/read_at_seq.rs b/src/mvcc/read_at_seq.rs new file mode 100644 index 0000000..e35e227 --- /dev/null +++ b/src/mvcc/read_at_seq.rs @@ -0,0 +1,167 @@ +// 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. + +//! Reads at an explicit sequence for a single key-value space. + +use std::io; +use std::ops::RangeBounds; + +use futures_util::StreamExt; +use seq_marked::SeqMarked; + +use crate::mvcc::Table; +use crate::IOResultStream; +use crate::MapKey; + +/// Gets values visible at a supplied snapshot sequence. +/// +/// The implementor does not own a snapshot sequence. Callers provide it for every read. +#[async_trait::async_trait] +pub trait GetAtSeq: Send + Sync +where K: MapKey +{ + async fn get_at_seq(&self, key: K, snapshot_seq: u64) -> Result, io::Error>; + + async fn get_many_at_seq( + &self, + keys: Vec, + snapshot_seq: u64, + ) -> Result>, io::Error> { + let mut values = Vec::with_capacity(keys.len()); + for key in keys { + values.push(self.get_at_seq(key, snapshot_seq).await?); + } + Ok(values) + } +} + +#[async_trait::async_trait] +impl GetAtSeq for Table +where K: MapKey +{ + async fn get_at_seq(&self, key: K, snapshot_seq: u64) -> Result, io::Error> { + Ok(self.get(key, snapshot_seq).cloned()) + } +} + +/// Ranges over values visible at a supplied snapshot sequence. +/// +/// The implementor does not own a snapshot sequence. Callers provide it for every read. +#[async_trait::async_trait] +pub trait RangeAtSeq: Send + Sync +where K: MapKey +{ + async fn range_at_seq( + &self, + range: R, + snapshot_seq: u64, + ) -> Result)>, io::Error> + where + R: RangeBounds + Send + Sync + Clone + 'static; +} + +#[async_trait::async_trait] +impl RangeAtSeq for Table +where K: MapKey +{ + async fn range_at_seq( + &self, + range: R, + snapshot_seq: u64, + ) -> Result)>, io::Error> + where + R: RangeBounds + Send + Sync + Clone + 'static, + { + let values = self + .range(range, snapshot_seq) + .map(|(key, value)| (key.clone(), value.cloned())) + .collect::>(); + + Ok(futures::stream::iter(values.into_iter().map(Ok)).boxed()) + } +} + +#[cfg(test)] +mod tests { + use futures_util::TryStreamExt; + + use super::*; + + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + struct TestKey(String); + + impl MapKey for TestKey { + type V = String; + } + + fn key(value: &str) -> TestKey { + TestKey(value.to_string()) + } + + fn value(value: &str) -> String { + value.to_string() + } + + fn table() -> Table { + let mut table = Table::new(); + table.insert(key("a"), 1, value("a1")).unwrap(); + table.insert(key("b"), 2, value("b2")).unwrap(); + table.insert(key("a"), 3, value("a3")).unwrap(); + table.insert_tombstone(key("c"), 3).unwrap(); + table.insert(key("d"), 5, value("d5")).unwrap(); + table + } + + #[tokio::test] + async fn test_get_at_seq_and_get_many_at_seq() { + let table = table(); + + assert_eq!( + table.get_at_seq(key("a"), 2).await.unwrap(), + SeqMarked::new_normal(1, value("a1")) + ); + assert_eq!( + table.get_at_seq(key("c"), 3).await.unwrap(), + SeqMarked::new_tombstone(3) + ); + + let values = table + .get_many_at_seq(vec![key("a"), key("b"), key("c"), key("d")], 3) + .await + .unwrap(); + assert_eq!(values, vec![ + SeqMarked::new_normal(3, value("a3")), + SeqMarked::new_normal(2, value("b2")), + SeqMarked::new_tombstone(3), + SeqMarked::new_not_found(), + ]); + } + + #[tokio::test] + async fn test_range_at_seq() { + let values = table() + .range_at_seq(key("a")..=key("d"), 3) + .await + .unwrap() + .try_collect::>() + .await + .unwrap(); + + assert_eq!(values, vec![ + (key("a"), SeqMarked::new_normal(3, value("a3"))), + (key("b"), SeqMarked::new_normal(2, value("b2"))), + (key("c"), SeqMarked::new_tombstone(3)), + ]); + } +} diff --git a/src/mvcc/scoped_api.rs b/src/mvcc/scoped_api.rs deleted file mode 100644 index 6bcde64..0000000 --- a/src/mvcc/scoped_api.rs +++ /dev/null @@ -1,54 +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 crate::mvcc::ScopedGet; -use crate::mvcc::ScopedRange; -use crate::mvcc::ScopedSet; -use crate::mvcc::ViewKey; -use crate::mvcc::ViewValue; - -/// Combined MVCC API for namespace-scoped operations. -/// -/// This trait combines read, write, and range operations within a single namespace, -/// providing a unified interface for common MVCC patterns. Implementors automatically -/// gain access to all scoped operations without namespace parameters. -/// -/// # Auto-Implementation -/// -/// This trait is automatically implemented for any type that provides the constituent -/// scoped operations: [`ScopedGet`], [`ScopedSet`], and [`ScopedRange`]. -/// -/// # Type Parameters -/// - `K`: Key type satisfying [`ViewKey`] constraints -/// - `V`: Value type satisfying [`ViewValue`] constraints -#[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 deleted file mode 100644 index caa9467..0000000 --- a/src/mvcc/scoped_get.rs +++ /dev/null @@ -1,37 +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 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_seq_bounded_get.rs b/src/mvcc/scoped_seq_bounded_get.rs deleted file mode 100644 index a20ea86..0000000 --- a/src/mvcc/scoped_seq_bounded_get.rs +++ /dev/null @@ -1,177 +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. - -//! Namespace-scoped read operations with snapshot isolation. - -use std::io; - -use seq_marked::SeqMarked; - -use crate::mvcc::ViewKey; -use crate::mvcc::ViewValue; -/// Read-only view bound to a namespace 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 ScopedSeqBoundedGet -where - Self: Send + Sync, - K: ViewKey, - V: ViewValue, -{ - /// Retrieves the value for a specific key at the given snapshot sequence. - /// - /// Returns the most recent version with sequence ≤ `snapshot_seq`, - /// or `SeqMarked::new_not_found()` if no such version exists. - async fn get(&self, key: K, snapshot_seq: u64) -> Result, io::Error>; - - /// Retrieves multiple keys atomically at the given snapshot sequence. - /// - /// Results maintain the same order as input keys. Default implementation calls `get()` sequentially. - async fn get_many( - &self, - keys: Vec, - snapshot_seq: u64, - ) -> Result>, io::Error> { - let mut values = Vec::with_capacity(keys.len()); - for key in keys { - let value = self.get(key, snapshot_seq).await?; - values.push(value); - } - Ok(values) - } -} - -#[cfg(test)] -mod tests { - use seq_marked::SeqMarked; - - use super::*; - use crate::mvcc::table::Table; - - #[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 scoped snapshot get - struct MockScopedSnapshotReader { - table: Table, - } - - impl MockScopedSnapshotReader { - fn new() -> Self { - let mut table = Table::new(); - table.insert(key("k1"), 1, value("v1")).unwrap(); - table.insert(key("k2"), 2, value("v2")).unwrap(); - table.insert_tombstone(key("k3"), 3).unwrap(); - table.insert(key("k5"), 5, value("v5")).unwrap(); - table.insert(key("k6"), 8, value("v6")).unwrap(); - table.insert(key("k4"), 10, value("v4")).unwrap(); - - Self { table } - } - } - - #[async_trait::async_trait] - impl ScopedSeqBoundedGet for MockScopedSnapshotReader { - async fn get( - &self, - key: TestKey, - snapshot_seq: u64, - ) -> Result, io::Error> { - let result = self.table.get(key, snapshot_seq); - Ok(result.cloned()) - } - } - - #[tokio::test] - async fn test_get_basic() { - let reader = MockScopedSnapshotReader::new(); - let res = reader.get(key("k1"), 10).await.unwrap(); - assert_eq!(res, SeqMarked::new_normal(1, value("v1"))); - } - - #[tokio::test] - async fn test_get_many_mixed_results() { - let reader = MockScopedSnapshotReader::new(); - let keys = vec![key("k1"), key("nx"), key("k3"), key("k5")]; - let res = reader.get_many(keys, 10).await.unwrap(); - let expected = vec![ - SeqMarked::new_normal(1, value("v1")), - SeqMarked::new_not_found(), - SeqMarked::new_tombstone(3), - SeqMarked::new_normal(5, value("v5")), - ]; - assert_eq!(res, expected); - } - - #[tokio::test] - async fn test_get_many_empty() { - let reader = MockScopedSnapshotReader::new(); - let res = reader.get_many(vec![], 10).await.unwrap(); - assert_eq!(res, vec![]); - } - - #[tokio::test] - async fn test_get_many_duplicates() { - let reader = MockScopedSnapshotReader::new(); - let keys = vec![key("k1"), key("k1"), key("k2")]; - let res = reader.get_many(keys, 10).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_sequence_filtering() { - let reader = MockScopedSnapshotReader::new(); - let keys = vec![key("k1"), key("k4"), key("k5")]; - let res = reader.get_many(keys, 4).await.unwrap(); - let expected = vec![ - SeqMarked::new_normal(1, value("v1")), - SeqMarked::new_not_found(), // seq 10 > 4 - SeqMarked::new_not_found(), // seq 5 > 4 - ]; - assert_eq!(res, expected); - } - - #[tokio::test] - async fn test_get_many_maintains_order() { - let reader = MockScopedSnapshotReader::new(); - let keys = vec![key("k6"), key("k1"), key("k5")]; - let res = reader.get_many(keys, 10).await.unwrap(); - let expected = vec![ - SeqMarked::new_normal(8, value("v6")), - SeqMarked::new_normal(1, value("v1")), - SeqMarked::new_normal(5, value("v5")), - ]; - assert_eq!(res, expected); - } -} diff --git a/src/mvcc/scoped_seq_bounded_into_range.rs b/src/mvcc/scoped_seq_bounded_into_range.rs deleted file mode 100644 index 3c997f1..0000000 --- a/src/mvcc/scoped_seq_bounded_into_range.rs +++ /dev/null @@ -1,117 +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. - -//! Namespace-scoped range operations that consume self with snapshot isolation. - -use std::io; -use std::ops::RangeBounds; - -use futures_util::StreamExt; -use seq_marked::SeqMarked; - -use crate::mvcc::scoped_seq_bounded_range_iter::ScopedSeqBoundedRangeIter; -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 ScopedSeqBoundedIntoRange -where - Self: Send + Sync, - K: ViewKey, - V: ViewValue, -{ - /// Returns an async stream of key-value pairs within the specified range, consuming self. - /// - /// Returns the most recent visible version for each key with sequence ≤ `snapshot_seq`. - /// Keys are returned in sorted order, including tombstones. - async fn into_range( - self, - range: R, - snapshot_seq: u64, - ) -> Result)>, io::Error> - where - R: RangeBounds + Send + Sync + Clone + 'static; -} - -#[async_trait::async_trait] -impl ScopedSeqBoundedIntoRange for Owned -where - K: ViewKey, - V: ViewValue, - Owned: ScopedSeqBoundedRangeIter + Send + Sync + 'static, -{ - async fn into_range( - self, - range: R, - snapshot_seq: u64, - ) -> Result)>, io::Error> - where - R: RangeBounds + Send + Sync + Clone + 'static, - { - let strm = owned_range_iter_to_stream(self, range, snapshot_seq); - Ok(strm.boxed()) - } -} - -#[futures_async_stream::try_stream(boxed, ok = (K, SeqMarked), error = io::Error)] -pub(crate) async fn owned_range_iter_to_stream( - table: Owned, - range: R, - snapshot_seq: u64, -) where - K: ViewKey, - V: ViewValue, - R: RangeBounds + Clone + Send + Sync + 'static, - Owned: ScopedSeqBoundedRangeIter + Send + Sync + 'static, -{ - let it = table.range_iter(range, snapshot_seq); - - for (k, v) in it { - yield (k.clone(), v.cloned()); - } -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use futures_util::TryStreamExt; - use seq_marked::SeqMarked; - - use crate::mvcc::scoped_seq_bounded_into_range::ScopedSeqBoundedIntoRange; - use crate::mvcc::Table; - - #[tokio::test] - async fn test_into_range() { - let mut t = Table::::new(); - t.insert(1, 5, 1).unwrap(); - - let at = Arc::new(t); - - let strm = at.clone().into_range(.., 5).await.unwrap(); - let got = strm.try_collect::>().await.unwrap(); - assert_eq!(got, vec![(1u64, SeqMarked::new_normal(5, 1u64)),]); - - let strm = at.into_range(.., 2).await.unwrap(); - let got = strm.try_collect::>().await.unwrap(); - assert_eq!(got, vec![]); - } -} diff --git a/src/mvcc/scoped_seq_bounded_range.rs b/src/mvcc/scoped_seq_bounded_range.rs deleted file mode 100644 index b37ba11..0000000 --- a/src/mvcc/scoped_seq_bounded_range.rs +++ /dev/null @@ -1,48 +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::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 ScopedSeqBoundedRange -where - Self: Send + Sync, - K: ViewKey, - V: ViewValue, -{ - /// Returns an async stream of key-value pairs within the specified range, consuming self. - /// - /// 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, - range: R, - snapshot_seq: u64, - ) -> Result)>, io::Error> - where - R: RangeBounds + Send + Sync + Clone + 'static; -} diff --git a/src/mvcc/scoped_seq_bounded_range_iter.rs b/src/mvcc/scoped_seq_bounded_range_iter.rs deleted file mode 100644 index 612f585..0000000 --- a/src/mvcc/scoped_seq_bounded_range_iter.rs +++ /dev/null @@ -1,46 +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. - -//! Namespace-scoped range iteration with snapshot isolation. - -use std::ops::RangeBounds; - -use seq_marked::SeqMarked; - -use crate::mvcc::ViewKey; -use crate::mvcc::ViewValue; - -/// Read-only range view bound to a namespace 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. -pub trait ScopedSeqBoundedRangeIter -where - K: ViewKey, - V: ViewValue, -{ - /// Returns an iterator 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. - fn range_iter( - &self, - range: R, - snapshot_seq: u64, - ) -> impl Iterator)> + Send - where - R: RangeBounds + Clone + 'static; -} diff --git a/src/mvcc/seq_bounded_get.rs b/src/mvcc/seq_bounded_get.rs deleted file mode 100644 index 52d5195..0000000 --- a/src/mvcc/seq_bounded_get.rs +++ /dev/null @@ -1,329 +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. - -//! 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; -/// Read-only multi-version 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 SeqBoundedGet -where - Self: Send + Sync, - S: ViewNamespace, - K: ViewKey, - V: ViewValue, -{ - /// Gets the value for a key at the given snapshot sequence. - /// - /// Returns the most recent version with sequence ≤ `snapshot_seq`, - /// or `SeqMarked::new_not_found()` if no such version exists. - async fn get(&self, space: S, key: K, snapshot_seq: u64) -> Result, io::Error>; - - /// Gets multiple keys at the given snapshot sequence. - /// - /// Results maintain the same order as input keys. Default implementation calls `get()` sequentially. - async fn get_many( - &self, - space: S, - keys: Vec, - snapshot_seq: u64, - ) -> Result>, io::Error> { - let mut values = Vec::with_capacity(keys.len()); - for key in keys { - let value = self.get(space, key, snapshot_seq).await?; - values.push(value); - } - Ok(values) - } -} - -#[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; - - use seq_marked::SeqMarked; - - use super::*; - use crate::mvcc::table::Table; - use crate::mvcc::ViewNamespace; - - #[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 namespace(id: u8) -> TestNamespace { - TestNamespace(id) - } - - fn key(s: &str) -> TestKey { - TestKey(s.to_string()) - } - - fn value(s: &str) -> TestValue { - TestValue(s.to_string()) - } - - // Mock implementation for testing - struct MockSnapshotReader { - // Each namespace has its own Table - tables: BTreeMap>, - } - - impl MockSnapshotReader { - fn new() -> Self { - let mut tables = BTreeMap::new(); - - // Namespace 1 - let mut table1 = Table::new(); - table1.insert(key("key1"), 1, value("value1")).unwrap(); - table1.insert(key("key2"), 2, value("value2")).unwrap(); - table1.insert_tombstone(key("key3"), 3).unwrap(); - table1.insert(key("key4"), 10, value("value4")).unwrap(); - tables.insert(namespace(1), table1); - - // Namespace 2 - let mut table2 = Table::new(); - table2.insert(key("key1"), 5, value("ns2_value1")).unwrap(); - table2.insert(key("key5"), 7, value("value5")).unwrap(); - tables.insert(namespace(2), table2); - - Self { tables } - } - } - - #[async_trait::async_trait] - impl SeqBoundedGet for MockSnapshotReader { - async fn get( - &self, - space: TestNamespace, - key: TestKey, - snapshot_seq: u64, - ) -> Result, io::Error> { - match self.tables.get(&space) { - Some(table) => { - let result = table.get(key, snapshot_seq); - Ok(result.cloned()) // Convert SeqMarked<&TestValue> to SeqMarked - } - None => Ok(SeqMarked::new_not_found()), - } - } - } - - #[tokio::test] - async fn test_snapshot_reader_get_basic_operations() { - let reader = MockSnapshotReader::new(); - let snapshot_seq = 10; - - // Test existing key - let result = reader - .get(namespace(1), key("key1"), snapshot_seq) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_normal(1, value("value1"))); - assert!(*result.internal_seq() <= snapshot_seq); - - // Test tombstone key - let result = reader - .get(namespace(1), key("key3"), snapshot_seq) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_tombstone(3)); - assert!(*result.internal_seq() <= snapshot_seq); - - // Test non-existent key - let result = reader - .get(namespace(1), key("nonexistent"), snapshot_seq) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_not_found()); - - // Test non-existent namespace - let result = reader - .get(namespace(99), key("key1"), snapshot_seq) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_not_found()); - } - - #[tokio::test] - async fn test_snapshot_reader_get_sequence_filter() { - let reader = MockSnapshotReader::new(); - - // key4 has seq 10, should be visible with snapshot_seq 10 - let snapshot_seq1 = 10; - let result = reader - .get(namespace(1), key("key4"), snapshot_seq1) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_normal(10, value("value4"))); - assert!(*result.internal_seq() <= snapshot_seq1); - - // key4 has seq 10, should not be visible with snapshot_seq 9 - let snapshot_seq2 = 9; - let result = reader - .get(namespace(1), key("key4"), snapshot_seq2) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_not_found()); - // Note: not_found doesn't have a meaningful sequence to check - } - - #[tokio::test] - async fn test_snapshot_reader_get_different_namespaces() { - let reader = MockSnapshotReader::new(); - let snapshot_seq = 10; - - // Same key in different namespaces should return different values - let result1 = reader - .get(namespace(1), key("key1"), snapshot_seq) - .await - .unwrap(); - assert_eq!(result1, SeqMarked::new_normal(1, value("value1"))); - assert!(*result1.internal_seq() <= snapshot_seq); - - let result2 = reader - .get(namespace(2), key("key1"), snapshot_seq) - .await - .unwrap(); - assert_eq!(result2, SeqMarked::new_normal(5, value("ns2_value1"))); - assert!(*result2.internal_seq() <= snapshot_seq); - } - - #[tokio::test] - async fn test_snapshot_reader_mget_operations() { - let reader = MockSnapshotReader::new(); - - // Test basic mget with mixed results (existing, not found) - let keys = vec![key("key1"), key("key2"), key("nonexistent")]; - let snapshot_seq = 10; - let results = reader - .get_many(namespace(1), keys, snapshot_seq) - .await - .unwrap(); - - assert_eq!(results.len(), 3); - assert_eq!(results[0], SeqMarked::new_normal(1, value("value1"))); - assert!(*results[0].internal_seq() <= snapshot_seq); - assert_eq!(results[1], SeqMarked::new_normal(2, value("value2"))); - assert!(*results[1].internal_seq() <= snapshot_seq); - assert_eq!(results[2], SeqMarked::new_not_found()); - - // Test empty key vector - let empty_keys = vec![]; - let results = reader - .get_many(namespace(1), empty_keys, snapshot_seq) - .await - .unwrap(); - assert_eq!(results.len(), 0); - - // Test sequence filtering in mget - let keys = vec![key("key1"), key("key4")]; // key1 seq=1, key4 seq=10 - let snapshot_seq = 5; - let results = reader - .get_many(namespace(1), keys, snapshot_seq) - .await - .unwrap(); - - assert_eq!(results.len(), 2); - assert_eq!(results[0], SeqMarked::new_normal(1, value("value1"))); // visible - 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/seq_bounded_into_range.rs b/src/mvcc/seq_bounded_into_range.rs deleted file mode 100644 index 5ced62d..0000000 --- a/src/mvcc/seq_bounded_into_range.rs +++ /dev/null @@ -1,53 +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. - -//! Range-based read operations for multi-version key-value storage with namespace isolation. - -use std::io; -use std::ops::RangeBounds; - -use seq_marked::SeqMarked; - -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 SeqBoundedIntoRange -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 into_range( - self, - space: S, - range: R, - snapshot_seq: u64, - ) -> Result)>, io::Error> - where - R: RangeBounds + Send + Sync + Clone + 'static; -} diff --git a/src/mvcc/seq_bounded_range.rs b/src/mvcc/seq_bounded_range.rs deleted file mode 100644 index 7156ee6..0000000 --- a/src/mvcc/seq_bounded_range.rs +++ /dev/null @@ -1,206 +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. - -//! 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/seq_bounded_range_iter.rs b/src/mvcc/seq_bounded_range_iter.rs deleted file mode 100644 index 33f58ab..0000000 --- a/src/mvcc/seq_bounded_range_iter.rs +++ /dev/null @@ -1,42 +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::ops::RangeBounds; - -use seq_marked::SeqMarked; - -use crate::mvcc::ViewKey; -use crate::mvcc::ViewNamespace; -use crate::mvcc::ViewValue; - -/// Multi-version range iterator with snapshot isolation. -pub trait SeqBoundedRangeIter -where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, -{ - /// Returns iterator of key-value pairs within range at snapshot sequence. - /// - /// Returns most recent version for each key with sequence ≤ `snapshot_seq`. - /// Keys returned in sorted order, including tombstones. - fn range_iter( - &self, - space: S, - range: R, - snapshot_seq: u64, - ) -> impl Iterator)> - where - R: RangeBounds + Clone + 'static; -} diff --git a/src/mvcc/snapshot/mod.rs b/src/mvcc/snapshot/mod.rs index ac50969..d2881c0 100644 --- a/src/mvcc/snapshot/mod.rs +++ b/src/mvcc/snapshot/mod.rs @@ -12,66 +12,36 @@ // 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. +//! Point-in-time reads over a single key-value space. -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::mvcc::GetAtSeq; +use crate::mvcc::RangeAtSeq; use crate::IOResultStream; +use crate::MapKey; 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 +/// A fixed sequence boundary over a low-level reader. #[derive(Clone, Debug, Default)] -pub struct Snapshot +pub struct Snapshot where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, - D: SeqBoundedGet, - D: SeqBoundedRange, - D: Commit, - D: Send + Sync, + K: MapKey, + D: GetAtSeq + RangeAtSeq, { - /// The snapshot sequence number. Values with sequence numbers greater than this will be invisible. snapshot_seq: InternalSeq, data: D, - _phantom: PhantomData<(S, K, V)>, + _phantom: PhantomData, } -impl Snapshot +impl Snapshot where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, - D: SeqBoundedGet, - D: SeqBoundedRange, - D: Commit, - D: Send + Sync, + K: MapKey, + D: GetAtSeq + RangeAtSeq, { pub fn new(snapshot_seq: InternalSeq, data: D) -> Self { Self { @@ -85,41 +55,31 @@ where self.snapshot_seq } - /// Retrieves the value for a key up to the snapshot sequence (inclusive). - pub async fn get(&self, space: S, key: K) -> Result, io::Error> { - self.data.get(space, key, *self.snapshot_seq).await + pub async fn get(&self, key: K) -> Result, io::Error> { + self.data.get_at_seq(key, *self.snapshot_seq).await } - /// Retrieves values for multiple keys up to the 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 + pub async fn get_many(&self, keys: Vec) -> Result>, io::Error> { + self.data.get_many_at_seq(keys, *self.snapshot_seq).await } - /// Returns a range of key-value pairs up to the snapshot sequence (inclusive). pub async fn range( &self, - space: S, range: R, - ) -> Result)>, io::Error> + ) -> 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) + self.data.range_at_seq(range, *self.snapshot_seq).await } pub fn data(&self) -> &D { &self.data } + + pub fn into_data(self) -> D { + self.data + } } #[cfg(test)] @@ -131,15 +91,6 @@ mod tests { 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>>, @@ -151,101 +102,49 @@ mod tests { 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( + impl GetAtSeq for MockData { + async fn get_at_seq( &self, - _space: TestSpace, - _k: String, + _key: String, seq: u64, - ) -> Result, io::Error> { - self.record_call(&format!("get(seq:{})", seq)); + ) -> Result>, io::Error> { + self.calls.lock().unwrap().push(format!("get:{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( + impl RangeAtSeq for MockData { + async fn range_at_seq( &self, - _space: TestSpace, _range: R, seq: u64, - ) -> Result)>, io::Error> + ) -> Result>)>, io::Error> where R: RangeBounds + Send + Sync + Clone + 'static, { - self.record_call(&format!("range(seq:{})", seq)); + self.calls.lock().unwrap().push(format!("range:{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, - }; + async fn test_snapshot_binds_sequence() { + let data = MockData::new(); + let snapshot = Snapshot::new(InternalSeq::new(42), data.clone()); - snapshot.get(TestSpace(1), "k".to_string()).await.unwrap(); + snapshot.get("k".to_string()).await.unwrap(); snapshot - .get_many(TestSpace(1), vec!["k".to_string()]) + .get_many(vec!["k1".to_string(), "k2".to_string()]) .await .unwrap(); - let _stream = snapshot.range(TestSpace(1), ..).await.unwrap(); + let _stream = snapshot.range(..).await.unwrap(); - let calls = mock.get_calls(); - assert_eq!(calls, vec![ - "get(seq:42)", - "get_many(seq:42)", - "range(seq:42)" + assert_eq!(*data.calls.lock().unwrap(), vec![ + "get:42", "get:42", "get:42", "range: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/table/impl_scoped_seq_bounded_get.rs b/src/mvcc/table/impl_scoped_seq_bounded_get.rs deleted file mode 100644 index 1a61a6a..0000000 --- a/src/mvcc/table/impl_scoped_seq_bounded_get.rs +++ /dev/null @@ -1,201 +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 seq_marked::SeqMarked; - -use super::Table; -use crate::mvcc::scoped_seq_bounded_get::ScopedSeqBoundedGet; -use crate::mvcc::ViewKey; -use crate::mvcc::ViewValue; - -#[async_trait::async_trait] -impl ScopedSeqBoundedGet for Table -where - K: ViewKey, - V: ViewValue, -{ - async fn get(&self, key: K, snapshot_seq: u64) -> Result, io::Error> { - let result = self.get(key, snapshot_seq); - Ok(result.cloned()) - } -} - -#[cfg(test)] -mod tests { - use seq_marked::SeqMarked; - - use super::*; - use crate::mvcc::scoped_seq_bounded_get::ScopedSeqBoundedGet; - - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] - 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_test_table() -> Table { - let mut table = Table::new(); - table.insert(key("key1"), 1, value("value1")).unwrap(); - table.insert(key("key2"), 2, value("value2")).unwrap(); - table.insert_tombstone(key("key3"), 3).unwrap(); - table.insert(key("key1"), 5, value("value1_v2")).unwrap(); // Multiple versions - must be after seq 3 - table.insert(key("key4"), 10, value("value4")).unwrap(); - table - } - - #[tokio::test] - async fn test_scoped_snapshot_reader_get_existing() { - let table = create_test_table(); - let snapshot_seq = 10; - - let result = ScopedSeqBoundedGet::get(&table, key("key1"), snapshot_seq) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_normal(5, value("value1_v2"))); // Most recent version - assert!(*result.internal_seq() <= snapshot_seq); - } - - #[tokio::test] - async fn test_scoped_snapshot_reader_get_tombstone() { - let table = create_test_table(); - let snapshot_seq = 10; - - let result = ScopedSeqBoundedGet::get(&table, key("key3"), snapshot_seq) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_tombstone(3)); - assert!(*result.internal_seq() <= snapshot_seq); - } - - #[tokio::test] - async fn test_scoped_snapshot_reader_get_not_found() { - let table = create_test_table(); - let snapshot_seq = 10; - - let result = ScopedSeqBoundedGet::get(&table, key("nonexistent"), snapshot_seq) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_not_found()); - } - - #[tokio::test] - async fn test_scoped_snapshot_reader_get_sequence_filter() { - let table = create_test_table(); - - // key4 has seq 10, should be visible with snapshot_seq 10 - let snapshot_seq1 = 10; - let result = ScopedSeqBoundedGet::get(&table, key("key4"), snapshot_seq1) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_normal(10, value("value4"))); - assert!(*result.internal_seq() <= snapshot_seq1); - - // key4 has seq 10, should not be visible with snapshot_seq 9 - let snapshot_seq2 = 9; - 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 = ScopedSeqBoundedGet::get(&table, key("key1"), snapshot_seq3) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_normal(1, value("value1"))); - assert!(*result.internal_seq() <= snapshot_seq3); - } - - #[tokio::test] - async fn test_scoped_snapshot_reader_mget() { - let table = create_test_table(); - let keys = vec![key("key1"), key("key2"), key("nonexistent")]; - let snapshot_seq = 10; - - let results = ScopedSeqBoundedGet::get_many(&table, keys, snapshot_seq) - .await - .unwrap(); - - assert_eq!(results.len(), 3); - assert_eq!(results[0], SeqMarked::new_normal(5, value("value1_v2"))); - assert!(*results[0].internal_seq() <= snapshot_seq); - assert_eq!(results[1], SeqMarked::new_normal(2, value("value2"))); - assert!(*results[1].internal_seq() <= snapshot_seq); - assert_eq!(results[2], SeqMarked::new_not_found()); - } - - #[tokio::test] - async fn test_scoped_snapshot_reader_mget_empty() { - let table = create_test_table(); - let keys = vec![]; - let snapshot_seq = 10; - - let results = ScopedSeqBoundedGet::get_many(&table, keys, snapshot_seq) - .await - .unwrap(); - assert_eq!(results.len(), 0); - } - - #[tokio::test] - async fn test_scoped_snapshot_reader_mget_sequence_filter() { - let table = create_test_table(); - let keys = vec![key("key1"), key("key4")]; // key1 has multiple versions, key4 seq=10 - let snapshot_seq = 5; - - let results = ScopedSeqBoundedGet::get_many(&table, keys, snapshot_seq) - .await - .unwrap(); - - assert_eq!(results.len(), 2); - assert_eq!(results[0], SeqMarked::new_normal(5, value("value1_v2"))); // Latest visible version - assert!(*results[0].internal_seq() <= snapshot_seq); - assert_eq!(results[1], SeqMarked::new_not_found()); // key4 not visible due to seq filter - } - - #[tokio::test] - async fn test_scoped_snapshot_reader_multiversion_behavior() { - let mut table = Table::new(); - - // Insert multiple versions of the same key - table.insert(key("key"), 1, value("v1")).unwrap(); - table.insert(key("key"), 3, value("v3")).unwrap(); - table.insert(key("key"), 5, value("v5")).unwrap(); - - // Test different snapshot points - let result = ScopedSeqBoundedGet::get(&table, key("key"), 2) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_normal(1, value("v1"))); - - let result = ScopedSeqBoundedGet::get(&table, key("key"), 4) - .await - .unwrap(); - assert_eq!(result, SeqMarked::new_normal(3, value("v3"))); - - 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_seq_bounded_range_iter.rs b/src/mvcc/table/impl_scoped_seq_bounded_range_iter.rs deleted file mode 100644 index 1fdc577..0000000 --- a/src/mvcc/table/impl_scoped_seq_bounded_range_iter.rs +++ /dev/null @@ -1,199 +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::ops::RangeBounds; - -use seq_marked::SeqMarked; - -use super::Table; -use crate::mvcc::scoped_seq_bounded_range_iter::ScopedSeqBoundedRangeIter; -use crate::mvcc::ViewKey; -use crate::mvcc::ViewValue; - -impl ScopedSeqBoundedRangeIter for Table -where - K: ViewKey, - V: ViewValue, -{ - fn range_iter( - &self, - range: R, - snapshot_seq: u64, - ) -> impl Iterator)> - where - R: RangeBounds + Clone + 'static, - { - self.range(range, snapshot_seq) - } -} - -impl ScopedSeqBoundedRangeIter for T -where - K: ViewKey, - V: ViewValue, - T: AsRef>, -{ - fn range_iter( - &self, - range: R, - snapshot_seq: u64, - ) -> impl Iterator)> - where - R: RangeBounds + Clone + 'static, - { - self.as_ref().range_iter(range, snapshot_seq) - } -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use seq_marked::SeqMarked; - - use super::*; - use crate::mvcc::scoped_seq_bounded_range_iter::ScopedSeqBoundedRangeIter; - - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] - 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_test_table() -> Table { - let mut table = Table::new(); - table.insert(key("key1"), 1, value("value1")).unwrap(); - table.insert(key("key2"), 2, value("value2")).unwrap(); - table.insert_tombstone(key("key3"), 3).unwrap(); - table.insert(key("key1"), 5, value("value1_v2")).unwrap(); // Multiple versions - must be after seq 3 - table.insert(key("key4"), 10, value("value4")).unwrap(); - table - } - - #[test] - fn test_scoped_snapshot_range_iter_all_as_ref() { - let table = create_test_table(); - let table = Arc::new(table); - let snapshot_seq = 10; - - let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., snapshot_seq); - - assert_eq!(iter.collect::>(), vec![ - (&key("key1"), SeqMarked::new_normal(5, &value("value1_v2"))), - (&key("key2"), SeqMarked::new_normal(2, &value("value2"))), - (&key("key3"), SeqMarked::new_tombstone(3)), - (&key("key4"), SeqMarked::new_normal(10, &value("value4"))), - ]); - } - - #[test] - fn test_scoped_snapshot_range_iter_all() { - let table = create_test_table(); - let snapshot_seq = 10; - - let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., snapshot_seq); - let results: Vec<_> = iter.collect(); - - assert_eq!(results, vec![ - (&key("key1"), SeqMarked::new_normal(5, &value("value1_v2"))), - (&key("key2"), SeqMarked::new_normal(2, &value("value2"))), - (&key("key3"), SeqMarked::new_tombstone(3)), - (&key("key4"), SeqMarked::new_normal(10, &value("value4"))), - ]); - } - - #[test] - fn test_scoped_snapshot_range_iter_bounded() { - let table = create_test_table(); - let snapshot_seq = 10; - - let iter = - ScopedSeqBoundedRangeIter::range_iter(&table, key("key1")..=key("key2"), snapshot_seq); - let results: Vec<_> = iter.collect(); - - assert_eq!(results, vec![ - (&key("key1"), SeqMarked::new_normal(5, &value("value1_v2"))), - (&key("key2"), SeqMarked::new_normal(2, &value("value2"))), - ]); - } - - #[test] - fn test_scoped_snapshot_range_iter_sequence_filter() { - let table = create_test_table(); - let snapshot_seq = 5; - - let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., snapshot_seq); - let results: Vec<_> = iter.collect(); - - assert_eq!(results, vec![ - (&key("key1"), SeqMarked::new_normal(5, &value("value1_v2"))), - (&key("key2"), SeqMarked::new_normal(2, &value("value2"))), - (&key("key3"), SeqMarked::new_tombstone(3)), - ]); - } - - #[test] - fn test_scoped_snapshot_range_iter_empty_table() { - let table: Table = Table::new(); - let snapshot_seq = 10; - - let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., snapshot_seq); - let results: Vec<_> = iter.collect(); - - assert_eq!(results.len(), 0); - } - - #[test] - fn test_scoped_snapshot_range_iter_multiversion_behavior() { - let mut table = Table::new(); - - // Insert multiple versions of the same key - table.insert(key("key"), 1, value("v1")).unwrap(); - table.insert(key("key"), 3, value("v3")).unwrap(); - table.insert(key("key"), 5, value("v5")).unwrap(); - - // Test different snapshot points - let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., 2); - let results: Vec<_> = iter.collect(); - - assert_eq!(results, vec![( - &key("key"), - SeqMarked::new_normal(1, &value("v1")) - ),]); - - let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., 4); - let results: Vec<_> = iter.collect(); - - assert_eq!(results, vec![( - &key("key"), - SeqMarked::new_normal(3, &value("v3")) - ),]); - - let iter = ScopedSeqBoundedRangeIter::range_iter(&table, .., 10); - let results: Vec<_> = iter.collect(); - - assert_eq!(results, vec![( - &key("key"), - SeqMarked::new_normal(5, &value("v5")) - ),]); - } -} diff --git a/src/mvcc/table/mod.rs b/src/mvcc/table/mod.rs index fcf17f2..c0325f0 100644 --- a/src/mvcc/table/mod.rs +++ b/src/mvcc/table/mod.rs @@ -13,10 +13,9 @@ // limitations under the License. pub mod errors; -mod impl_scoped_seq_bounded_get; -mod impl_scoped_seq_bounded_range_iter; mod merge; pub mod range_iter; +#[cfg(test)] mod table_snapshot; use std::cmp::Reverse; @@ -29,8 +28,6 @@ use errors::InsertError; use futures::Stream; use futures_util::TryStreamExt; use range_iter::RangeIter; -pub use table_snapshot::Tables; -pub use table_snapshot::TablesSnapshot; use crate::SeqMarked; @@ -59,8 +56,8 @@ pub struct Table { /// Tracks the highest sequence number in this table. /// - /// Note: The last inserted record may be a tombstone, as tombstone insertions - /// can increment the sequence number depending on namespace configuration. + /// Note: The last inserted record may be a tombstone. Multiple records may share a + /// sequence when a view does not increment it for a secondary change. pub last_seq: SeqMarked<()>, } @@ -197,7 +194,7 @@ impl Table { seq_marked: SeqMarked<()>, value: Option, ) -> Result<(), InsertError> { - // If a namespace disable seq incr, same InternalSeq may be inserted + // Multiple records may use the same sequence. if *seq_marked.internal_seq() >= *self.last_seq.internal_seq() { // ok diff --git a/src/mvcc/table/table_snapshot.rs b/src/mvcc/table/table_snapshot.rs index aa1178f..b25eea8 100644 --- a/src/mvcc/table/table_snapshot.rs +++ b/src/mvcc/table/table_snapshot.rs @@ -12,37 +12,17 @@ // 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; + use super::super::Table; + use crate::mvcc::snapshot::Snapshot; + use crate::MapKey; - // 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 - } - } + type TablesSnapshot = Snapshot::V>>; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] struct TestKey(String); @@ -50,7 +30,10 @@ mod tests { #[derive(Debug, Clone, PartialEq, Eq)] struct TestValue(String); - // Helper functions + impl MapKey for TestKey { + type V = TestValue; + } + fn key(s: &str) -> TestKey { TestKey(s.to_string()) } @@ -73,26 +56,19 @@ mod tests { // 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); + let view = TablesSnapshot::new(InternalSeq::new(5), create_test_table()); 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 view = TablesSnapshot::new(InternalSeq::new(10), create_test_table()); let keys = vec![ key("k1"), @@ -102,7 +78,7 @@ mod tests { key("k5"), key("k6"), ]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); + let result = view.get_many(keys).await.unwrap(); assert_eq!(result.len(), 6); assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); @@ -115,10 +91,7 @@ mod tests { #[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 view = TablesSnapshot::new(InternalSeq::new(6), create_test_table()); let keys = vec![ key("k1"), @@ -128,7 +101,7 @@ mod tests { key("k5"), key("k6"), ]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); + let result = view.get_many(keys).await.unwrap(); assert_eq!(result.len(), 6); assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); @@ -141,10 +114,7 @@ mod tests { #[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 view = TablesSnapshot::new(InternalSeq::new(8), create_test_table()); let keys = vec![ key("k1"), @@ -154,7 +124,7 @@ mod tests { key("k5"), key("k6"), ]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); + let result = view.get_many(keys).await.unwrap(); assert_eq!(result.len(), 6); assert_eq!(result[0], SeqMarked::new_normal(1, value("v1"))); @@ -167,13 +137,10 @@ mod tests { #[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 view = TablesSnapshot::new(InternalSeq::new(10), create_test_table()); let range = key("k1")..=key("k6"); - let mut stream = view.range(TestSpace::Space1, range).await.unwrap(); + let mut stream = view.range(range).await.unwrap(); let mut results = Vec::new(); while let Some(result) = stream.next().await { diff --git a/src/mvcc/value.rs b/src/mvcc/value.rs deleted file mode 100644 index a6cbbbf..0000000 --- a/src/mvcc/value.rs +++ /dev/null @@ -1,34 +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::fmt; - -/// Trait for types that can be stored as values in MVCC operations. -/// -/// # 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 0173fce..139c3eb 100644 --- a/src/mvcc/view.rs +++ b/src/mvcc/view.rs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::BTreeMap; +//! A read-write MVCC view over one key-value space. + use std::fmt; use std::io; use std::ops::RangeBounds; @@ -25,140 +26,34 @@ use stream_more::KMerge; use stream_more::StreamMore; use crate::compact::compact_seq_marked_pair; -use crate::mvcc::commit::Commit; -use crate::mvcc::key::ViewKey; -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::GetAtSeq; +use crate::mvcc::RangeAtSeq; +use crate::mvcc::Snapshot; +use crate::mvcc::Table; +use crate::mvcc::ViewGet; +use crate::mvcc::ViewRange; +use crate::mvcc::ViewSet; use crate::util; use crate::IOResultStream; +use crate::MapKey; -/// A read-write view that includes uncommitted changes from the current transaction. -/// -/// Unlike `ViewRo`, this view's `get()` and `range()` methods return data that includes -/// any modifications made within this transaction. -/// -/// # Isolation Level: Read Committed -/// -/// This MVCC implementation provides **Read Committed** isolation level with the following guarantees: -/// -/// ## ✅ Guarantees -/// - **Dirty Read Prevention**: Reads never see uncommitted data from other transactions -/// - **Write Consistency**: All writes within a transaction are atomic on commit -/// - **Snapshot Isolation**: Each transaction sees a consistent snapshot at start time -/// - **Staged Changes**: Local modifications are visible within the same transaction -/// -/// ## ❌ Limitations -/// - **Phantom Reads**: Range queries may see different results if re-executed -/// - **Non-Repeatable Reads**: Point queries may return different values if re-executed -/// - **Not Serializable**: Concurrent transactions may produce results impossible in any serial execution -/// -/// ## ⚠️ Tombstone Anomaly -/// **Critical**: Due to historical implementation (tombstone insertions reuse sequence numbers), -/// delete operations from concurrent transactions may be visible within the same sequence boundary. -/// This can cause unexpected visibility of deletions. -/// -/// ```text -/// Transaction A at seq=100: sees key "x" = "value" -/// Transaction B at seq=100: deletes key "x" (tombstone at seq=100) -/// Transaction A may now see key "x" as deleted, violating isolation -/// ``` -/// -/// # Concurrency Guidelines -/// -/// ## ✅ Safe Patterns -/// ```rust,ignore -/// // Read-only transactions: Fully concurrent -/// let view = View::new(base_snapshot); -/// let result = view.get(space, key).await?; // Safe: no modifications -/// -/// // Independent key spaces: Can run concurrently -/// // Transaction A: modifies space1 -/// // Transaction B: modifies space2 -/// -/// // Non-overlapping key sets: Can run concurrently -/// // Transaction A: modifies keys [1..100] -/// // Transaction B: modifies keys [200..300] -/// ``` -/// -/// ## ⚠️ Requires Coordination -/// ```rust,ignore -/// // Overlapping writes: Serialize at application level -/// // Both transactions modify the same keys -/// -/// // Read-modify-write: Use optimistic locking -/// let current = view.get(space, key).await?; -/// if current.seq() == expected_seq { -/// view.set(space, key, new_value); -/// view.commit().await?; -/// } else { -/// // Retry or abort -/// } -/// -/// // Critical sections: Use external coordination (mutex, etc.) -/// ``` -/// -/// ## 🚫 Unsafe Patterns -/// ```rust,ignore -/// // DON'T: Assume repeatable reads -/// let value1 = view.get(space, key).await?; -/// // ... other operations ... -/// let value2 = view.get(space, key).await?; -/// // value1 != value2 possible! -/// -/// // DON'T: Assume phantom read protection -/// let count1 = view.range(space, ..).count(); -/// // ... other operations ... -/// let count2 = view.range(space, ..).count(); -/// // count1 != count2 possible! -/// ``` -/// -/// # Recommendations -/// -/// 1. **Serialize conflicting transactions** at the application level -/// 2. **Use optimistic locking** for read-modify-write patterns -/// 3. **Keep transactions short** to minimize contention windows -/// 4. **Prefer read-only transactions** when possible for maximum concurrency -/// 5. **Test thoroughly** with concurrent workloads to identify race conditions -/// -/// # Type Parameters -/// - `S`: the key space (AKA column family) -/// - `K`: key type -/// - `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 +/// A transaction view with a fixed base snapshot and staged changes. +pub struct View where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, - D: SeqBoundedRead + Commit, + K: MapKey, + D: GetAtSeq + RangeAtSeq, { - /// Whether to increase the seq for tombstone insertion. - /// - /// For backward compatibility, the default is false. + /// Whether deleting a key allocates a new sequence. pub(crate) increase_seq_for_tombstone: bool, - - /// The changes that are staged for commit in each key space. - pub(crate) changes: BTreeMap>, - - /// The [`InternalSeq`] of the latest update, which is stored in `changes`. - /// - /// This seq will be updated to the underlaying [`Table`] when the transaction is committed. + pub(crate) changes: Table, pub(crate) last_seq: InternalSeq, - - pub(crate) snapshot: Snapshot, + pub(crate) snapshot: Snapshot, } -impl fmt::Debug for View +impl fmt::Debug for View where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, - D: SeqBoundedRead + Commit, - D: fmt::Debug, + K: MapKey, + D: GetAtSeq + RangeAtSeq + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("View") @@ -173,19 +68,17 @@ where } } -impl View +impl View where - S: ViewNamespace, - K: ViewKey, - V: ViewValue, - D: SeqBoundedRead + Commit, + K: MapKey, + D: GetAtSeq + RangeAtSeq, { - pub fn new(snapshot: Snapshot) -> Self { - let seq = snapshot.snapshot_seq(); + pub fn new(snapshot: Snapshot) -> Self { + let last_seq = snapshot.snapshot_seq(); Self { increase_seq_for_tombstone: false, - changes: BTreeMap::new(), - last_seq: seq, + changes: Table::new(), + last_seq, snapshot, } } @@ -200,8 +93,7 @@ where self } - /// Return the reference to the snapshot this view is based on. - pub fn snapshot(&self) -> &Snapshot { + pub fn snapshot(&self) -> &Snapshot { &self.snapshot } @@ -210,19 +102,12 @@ where SeqMarked::new_normal(*self.last_seq, ()) } - /// Return the next seq for inserting a normal value. fn next_normal_seq(&mut self) -> SeqMarked<()> { self.last_seq += 1; debug!("next_normal_seq: last_seq become: {}", self.last_seq); SeqMarked::new_normal(*self.last_seq, ()) } - /// Return the next seq for inserting a tombstone. - /// - /// For historical reasons, deletions do not increment the sequence number. - /// Instead, the current sequence is converted to a tombstone marker. - /// - /// TODO: need to updated to use a new seq for tombstone. fn next_tombstone_seq(&mut self) -> SeqMarked<()> { if self.increase_seq_for_tombstone { self.last_seq += 1; @@ -232,99 +117,100 @@ where } #[deprecated(since = "0.4.2", note = "use snapshot() instead")] - pub fn base(&self) -> &Snapshot { + pub fn base(&self) -> &Snapshot { &self.snapshot } - /// Inserting a tombstone does not increase the seq, but instead, it use the last used seq. - /// This is for a historical reason, the first version does not increase seq when deleting an item. - pub fn set(&mut self, space: S, key: K, value: Option) -> SeqMarked<()> { - debug!( - "View::set: space: {:?}, key: {:?}, value: {:?}", - space, key, value - ); + /// Inserting a tombstone does not increase the seq, but instead, it uses the last used seq. + /// This is for a historical reason: the first version does not increase seq when deleting an item. + pub fn set(&mut self, key: K, value: Option) -> SeqMarked<()> { + debug!("View::set: key: {:?}, value: {:?}", key, value); + let seq = if value.is_none() { - // delete by insert a tombstone self.next_tombstone_seq() } else { - // insert - if space.increments_seq() { - self.next_normal_seq() - } else { - self.current_normal_seq() - } + self.next_normal_seq() }; let seq_num = *seq.internal_seq(); - let update_table = self.changes.entry(space).or_default(); - - if let Some(v) = value { - update_table.insert(key, seq_num, v).unwrap(); + if let Some(value) = value { + self.changes.insert(key, seq_num, value).unwrap(); } else { - update_table.insert_tombstone(key, seq_num).unwrap(); + self.changes.insert_tombstone(key, seq_num).unwrap(); } seq } - pub async fn get(&self, space: S, key: K) -> Result, io::Error> { - let last_seq = self.last_seq; - - let update_table = self.changes.get(&space); - let updated = if let Some(update_table) = update_table { - update_table.get(key.clone(), *last_seq).cloned() - } else { - SeqMarked::new_not_found() + /// Stage a change at the current sequence instead of allocating a new one. + pub fn set_without_seq_increment(&mut self, key: K, value: Option) -> SeqMarked<()> { + let Some(value) = value else { + return self.set(key, None); }; + let seq = self.current_normal_seq(); + let seq_num = *seq.internal_seq(); + self.changes.insert(key, seq_num, value).unwrap(); + + seq + } + + pub async fn get(&self, key: K) -> Result, io::Error> { + let updated = self.changes.get(key.clone(), *self.last_seq).cloned(); if !updated.is_not_found() { return Ok(updated); } - let base = self.snapshot.get(space, key).await?; - - let compacted = compact_seq_marked_pair(updated, base); - - Ok(compacted) + let base = self.snapshot.get(key).await?; + Ok(compact_seq_marked_pair(updated, base)) } - /// Convenience method to get multiple keys at once. - /// - /// This implementation calls `get` for each key, combining staged changes - /// with the base view data for each key individually. - pub async fn get_many(&self, space: S, keys: Vec) -> Result>, io::Error> { + pub async fn get_many(&self, 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); + results.push(self.get(key).await?); } Ok(results) } - /// Fetch the current value of a key and set it to a new value atomically. - /// - /// Returns a tuple of (old_value, new_value) where: - /// - `old_value` is the previous value (or `SeqMarked::new_not_found()` if key didn't exist) - /// - `new_value` is the newly set value (or `SeqMarked::new_tombstone()` if deleted) - /// - /// This is useful for atomic get-then-set operations. pub async fn fetch_and_set( &mut self, - space: S, key: K, - value: Option, - ) -> Result<(SeqMarked, SeqMarked), io::Error> { - let old_value = self.get(space, key.clone()).await?; + value: Option, + ) -> Result<(SeqMarked, SeqMarked), io::Error> { + self.fetch_and_set_with_seq_increment(key, value, true) + .await + } + /// Fetch and stage a change without allocating a new sequence for normal values. + pub async fn fetch_and_set_without_seq_increment( + &mut self, + key: K, + value: Option, + ) -> Result<(SeqMarked, SeqMarked), io::Error> { + self.fetch_and_set_with_seq_increment(key, value, false) + .await + } + + async fn fetch_and_set_with_seq_increment( + &mut self, + key: K, + value: Option, + increase_seq: bool, + ) -> Result<(SeqMarked, SeqMarked), io::Error> { + let old_value = self.get(key.clone()).await?; if old_value.is_not_found() && value.is_none() { - // No such entry at all, no need to create a tombstone for delete return Ok((old_value, SeqMarked::new_tombstone(0))); } - let order_key = self.set(space, key, value.clone()); + let order_key = if increase_seq { + self.set(key, value.clone()) + } else { + self.set_without_seq_increment(key, value.clone()) + }; let new_value = match value { - Some(v) => order_key.map(|_| v), + Some(value) => order_key.map(|_| value), None => SeqMarked::new_tombstone(*order_key.internal_seq()), }; Ok((old_value, new_value)) @@ -332,1112 +218,513 @@ where pub async fn range( &self, - space: S, range: R, - ) -> Result)>, io::Error> + ) -> Result)>, io::Error> where R: RangeBounds + Send + Sync + Clone + 'static, { - let last_seq = self.last_seq; - - let base_strm = self.snapshot.range(space, range.clone()).await?; - - let update_table = self.changes.get(&space); - let Some(update_table) = update_table else { - return Ok(base_strm); - }; - - let updated_strm = update_table - .range(range, *last_seq) - .map(|(k, v)| (k.clone(), v.cloned())) + let base = self.snapshot.range(range.clone()).await?; + let updates = self + .changes + .range(range, *self.last_seq) + .map(|(key, value)| (key.clone(), value.cloned())) .collect::>(); + let updates = futures::stream::iter(updates.into_iter().map(Ok)).boxed(); - let strm = futures::stream::iter(updated_strm).map(Ok).boxed(); + let merged = KMerge::by(util::by_key_seq).merge(base).merge(updates); + Ok(merged.coalesce(util::merge_kv_results).boxed()) + } - let kmerge = KMerge::by(util::by_key_seq); - let kmerge = kmerge.merge(base_strm).merge(strm); + /// Return the low-level reader, final sequence, and staged changes for committing. + pub fn into_parts(self) -> (D, InternalSeq, Table) { + (self.snapshot.into_data(), self.last_seq, self.changes) + } +} - let coalesce = kmerge.coalesce(util::merge_kv_results); +#[async_trait::async_trait] +impl ViewSet for View +where + K: MapKey, + D: GetAtSeq + RangeAtSeq, +{ + fn set(&mut self, key: K, value: Option) -> SeqMarked<()> { + View::set(self, key, value) + } +} - Ok(coalesce.boxed()) +#[async_trait::async_trait] +impl ViewGet for View +where + K: MapKey, + D: GetAtSeq + RangeAtSeq, +{ + async fn get(&self, key: K) -> Result, io::Error> { + View::get(self, key).await } +} - pub async fn commit(self) -> Result { - let d = self.snapshot.commit(self.last_seq, self.changes).await?; - Ok(d) +#[async_trait::async_trait] +impl ViewRange for View +where + K: MapKey, + D: GetAtSeq + RangeAtSeq, +{ + async fn range(&self, range: R) -> Result)>, io::Error> + where R: RangeBounds + Send + Sync + Clone + 'static { + View::range(self, range).await } } #[cfg(test)] mod tests { - use std::collections::BTreeMap; - - use futures::StreamExt; + use futures_util::TryStreamExt; use seq_marked::InternalSeq; use seq_marked::SeqMarked; use super::*; - 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 { - Space1, - Space2, - } - - impl ViewNamespace for TestSpace { - fn increments_seq(&self) -> bool { - true - } - } + use crate::mvcc::ViewApi; + use crate::MapKey; #[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()) + impl MapKey for TestKey { + type V = String; } - 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); - - TablesSnapshot::new(InternalSeq::new(10), tables) + fn key(value: &str) -> TestKey { + TestKey(value.to_string()) } - fn create_view( - base: TablesSnapshot, - ) -> View> { - View::new(base) + fn value(value: &str) -> String { + value.to_string() } - fn create_view_with_tombstone_seq( - 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_snapshot()); - - view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); - - assert_eq!(view.last_seq, InternalSeq::new(11)); - assert_eq!(view.changes.len(), 1); - - // Verify by reading changes - let table = &view.changes[&TestSpace::Space1]; - let result = table.get(key("k1"), 11); - assert_eq!(result, SeqMarked::new_normal(11, &value("v1"))); + fn view() -> View> { + let mut table = Table::new(); + table.insert(key("base"), 1, value("base-value")).unwrap(); + View::new(Snapshot::new(InternalSeq::new(1), table)) } - #[tokio::test] - async fn test_set_tombstone_no_seq_increase() { - let mut view = create_view(create_base_snapshot()); - - // First insert - view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); - assert_eq!(view.last_seq, InternalSeq::new(11)); - - // Delete (tombstone) - should not increase seq - view.set(TestSpace::Space1, key("k2"), None); - assert_eq!(view.last_seq, InternalSeq::new(11)); // unchanged + fn assert_view_api>(_: &T) {} - // Verify both operations by reading changes - let table = &view.changes[&TestSpace::Space1]; - let result1 = table.get(key("k1"), 11); - assert_eq!(result1, SeqMarked::new_normal(11, &value("v1"))); - let result2 = table.get(key("k2"), 11); - assert_eq!(result2, SeqMarked::new_tombstone(11)); + #[test] + fn test_view_implements_view_api() { + assert_view_api(&view()); } #[tokio::test] - async fn test_set_tombstone_with_seq_increase() { - let mut view = create_view_with_tombstone_seq(create_base_snapshot()); - - // First insert - view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); - assert_eq!(view.last_seq, InternalSeq::new(11)); - - // Delete (tombstone) - should increase seq - view.set(TestSpace::Space1, key("k2"), None); - assert_eq!(view.last_seq, InternalSeq::new(12)); // increased + async fn test_staged_value_overrides_snapshot() { + let mut view = view(); + assert_eq!( + view.get(key("base")).await.unwrap(), + SeqMarked::new_normal(1, value("base-value")) + ); - // Verify both operations by reading changes - let table = &view.changes[&TestSpace::Space1]; - let result1 = table.get(key("k1"), 12); - assert_eq!(result1, SeqMarked::new_normal(11, &value("v1"))); - let result2 = table.get(key("k2"), 12); - assert_eq!(result2, SeqMarked::new_tombstone(12)); + view.set(key("base"), Some(value("updated"))); + assert_eq!( + view.get(key("base")).await.unwrap(), + SeqMarked::new_normal(2, value("updated")) + ); } #[tokio::test] - async fn test_mget_from_base_only() { - 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(); + async fn test_snapshot_hides_future_values() { + let mut table = Table::new(); + table.insert(key("base"), 1, value("base-value")).unwrap(); + table + .insert(key("future"), 2, value("future-value")) + .unwrap(); + let view = View::new(Snapshot::new(InternalSeq::new(1), table)); - 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"))); + assert_eq!( + view.get_many(vec![key("base"), key("future")]) + .await + .unwrap(), + vec![ + SeqMarked::new_normal(1, value("base-value")), + SeqMarked::new_not_found(), + ] + ); } #[tokio::test] - async fn test_mget_from_changes_only() { - 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"))); - - // Verify write operations by reading changes directly - let table = &view.changes[&TestSpace::Space1]; + async fn test_secondary_change_reuses_sequence() { + let mut view = view(); assert_eq!( - table.get(key("new_k1"), 12), - SeqMarked::new_normal(11, &value("new_v1")) + view.set(key("primary"), Some(value("p"))), + SeqMarked::new_normal(2, ()) ); assert_eq!( - table.get(key("new_k2"), 12), - SeqMarked::new_normal(12, &value("new_v2")) + view.set_without_seq_increment(key("secondary"), Some(value("s"))), + SeqMarked::new_normal(2, ()) ); - - let keys = vec![key("new_k1"), key("new_k2")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 2); - assert_eq!(result[0], SeqMarked::new_normal(11, value("new_v1"))); - assert_eq!(result[1], SeqMarked::new_normal(12, value("new_v2"))); - } - - #[tokio::test] - async fn test_mget_merge_base_and_changes() { - 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 - view.set(TestSpace::Space1, key("new_k1"), Some(value("new_v1"))); - - // Verify write operations by reading changes directly - let table = &view.changes[&TestSpace::Space1]; + assert_eq!(view.last_seq, InternalSeq::new(2)); assert_eq!( - table.get(key("base_k1"), 12), - SeqMarked::new_normal(11, &value("updated_v1")) + view.get(key("primary")).await.unwrap(), + SeqMarked::new_normal(2, value("p")) ); assert_eq!( - table.get(key("new_k1"), 12), - SeqMarked::new_normal(12, &value("new_v1")) + view.get(key("secondary")).await.unwrap(), + SeqMarked::new_normal(2, value("s")) ); - - let keys = vec![key("base_k1"), key("base_k2"), key("new_k1")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 3); - assert_eq!(result[0], SeqMarked::new_normal(11, value("updated_v1"))); // from changes - assert_eq!(result[1], SeqMarked::new_normal(2, value("base_v2"))); // from base - assert_eq!(result[2], SeqMarked::new_normal(12, value("new_v1"))); // from changes - } - - #[tokio::test] - async fn test_mget_tombstone_override() { - let mut view = create_view(create_base_snapshot()); - // Delete base value - view.set(TestSpace::Space1, key("base_k1"), None); - - // Verify write operation by reading changes directly - let table = &view.changes[&TestSpace::Space1]; - let result = table.get(key("base_k1"), 10); - assert_eq!(result, SeqMarked::new_tombstone(10)); // tombstone uses initial last_seq (10) - - let keys = vec![key("base_k1")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 1); - assert_eq!(result[0], SeqMarked::new_tombstone(10)); // tombstone wins + assert_eq!( + view.fetch_and_set_without_seq_increment(key("tertiary"), Some(value("t"))) + .await + .unwrap(), + ( + SeqMarked::new_not_found(), + SeqMarked::new_normal(2, value("t")), + ) + ); + assert_eq!(view.last_seq, InternalSeq::new(2)); } #[tokio::test] - async fn test_mget_nonexistent_space() { - let view = create_view(create_base_snapshot()); + async fn test_delete_missing_key_does_not_stage_tombstone() { + let mut view = view(); + let (old_value, new_value) = view.fetch_and_set(key("missing"), None).await.unwrap(); - let keys = vec![key("k1")]; - let result = view.get_many(TestSpace::Space2, keys).await.unwrap(); - - assert_eq!(result.len(), 1); - assert!(result[0].is_not_found()); + assert_eq!(old_value, SeqMarked::new_not_found()); + assert_eq!(new_value, SeqMarked::new_tombstone(0)); + assert!(view.changes.inner.is_empty()); } #[tokio::test] - async fn test_range_from_base_only() { - let view = create_view(create_base_snapshot()); - - 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()); - } + async fn test_range_merges_snapshot_and_changes() { + let mut view = view(); + view.set(key("base"), None); + view.set(key("new"), Some(value("new-value"))); - assert_eq!(results.len(), 2); - 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"))) - ); + let values = view + .range(..) + .await + .unwrap() + .try_collect::>() + .await + .unwrap(); + assert_eq!(values, vec![ + (key("base"), SeqMarked::new_tombstone(1)), + (key("new"), SeqMarked::new_normal(2, value("new-value"))), + ]); } #[tokio::test] - async fn test_range_from_changes_only() { - 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"))); - - // Verify write operations by reading changes directly - let table = &view.changes[&TestSpace::Space2]; - assert_eq!( - table.get(key("new_k1"), 12), - SeqMarked::new_normal(11, &value("new_v1")) - ); + async fn test_tombstone_sequence_configuration() { + let mut stable_view = view(); assert_eq!( - table.get(key("new_k2"), 12), - SeqMarked::new_normal(12, &value("new_v2")) + stable_view.set(key("base"), None), + SeqMarked::new_tombstone(1) ); + assert_eq!(stable_view.last_seq, InternalSeq::new(1)); - let mut stream = view.range(TestSpace::Space2, ..).await.unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 2); + let mut incrementing_view = view().with_tombstone_seq_increment(true); assert_eq!( - results[0], - (key("new_k1"), SeqMarked::new_normal(11, value("new_v1"))) + incrementing_view.set(key("base"), None), + SeqMarked::new_tombstone(2) ); + assert_eq!(incrementing_view.last_seq, InternalSeq::new(2)); assert_eq!( - results[1], - (key("new_k2"), SeqMarked::new_normal(12, value("new_v2"))) + incrementing_view.get(key("base")).await.unwrap(), + SeqMarked::new_tombstone(2) ); } #[tokio::test] - async fn test_range_merge_base_and_changes() { - 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"))); + async fn test_get_many_merges_snapshot_and_changes() { + let mut view = view(); + view.set(key("base"), Some(value("updated"))); + view.set(key("new"), Some(value("new-value"))); - // Verify write operations by reading changes directly - let table = &view.changes[&TestSpace::Space1]; - assert_eq!( - table.get(key("base_k1"), 12), - SeqMarked::new_normal(11, &value("updated_v1")) - ); - assert_eq!( - table.get(key("new_k1"), 12), - SeqMarked::new_normal(12, &value("new_v1")) - ); + let values = view + .get_many(vec![key("base"), key("new"), key("missing")]) + .await + .unwrap(); + assert_eq!(values, vec![ + SeqMarked::new_normal(2, value("updated")), + SeqMarked::new_normal(3, value("new-value")), + SeqMarked::new_not_found(), + ]); + } - 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()); - } + #[tokio::test] + async fn test_tombstone_resurrection_preserves_versions() { + let mut view = view(); + view.set(key("base"), None); + view.set(key("base"), Some(value("resurrected"))); - // Should be merged and sorted by key - assert_eq!(results.len(), 3); assert_eq!( - results[0], - ( - key("base_k1"), - SeqMarked::new_normal(11, value("updated_v1")) - ) + view.changes.get(key("base"), 1), + SeqMarked::new_tombstone(1) ); assert_eq!( - results[1], - (key("base_k2"), SeqMarked::new_normal(2, value("base_v2"))) + view.changes.get(key("base"), 2), + SeqMarked::new_normal(2, &value("resurrected")) ); assert_eq!( - results[2], - (key("new_k1"), SeqMarked::new_normal(12, value("new_v1"))) + view.get(key("base")).await.unwrap(), + SeqMarked::new_normal(2, value("resurrected")) ); } #[tokio::test] - async fn test_range_nonexistent_space() { - let view = create_view(create_base_snapshot()); - - let mut stream = view.range(TestSpace::Space2, ..).await.unwrap(); - let result = stream.next().await; - - assert!(result.is_none()); - } - - #[tokio::test] - async fn test_commit() { - 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"))); - - // Verify write operations by reading changes directly - let table1 = &view.changes[&TestSpace::Space1]; - let table2 = &view.changes[&TestSpace::Space2]; - assert_eq!( - table1.get(key("k1"), 12), - SeqMarked::new_normal(11, &value("v1")) - ); - assert_eq!( - table2.get(key("k2"), 12), - SeqMarked::new_normal(12, &value("v2")) - ); - - assert_eq!(view.changes.len(), 2); - - let base_data = view.commit().await.unwrap(); - - // Verify the base view contains the committed changes - let keys = vec![key("k1")]; - 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_data - .get_many(TestSpace::Space2, keys, u64::MAX) + async fn test_range_respects_bounds_and_tombstones() { + let mut table = Table::new(); + table.insert(key("a"), 1, value("a-value")).unwrap(); + table.insert(key("b"), 2, value("b-value")).unwrap(); + table.insert(key("c"), 3, value("c-value")).unwrap(); + let mut view = View::new(Snapshot::new(InternalSeq::new(3), table)); + view.set(key("b"), None); + view.set(key("d"), Some(value("d-value"))); + + let values = view + .range(key("b")..=key("d")) .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_data - .get_many(TestSpace::Space1, keys, u64::MAX) + .unwrap() + .try_collect::>() .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"))); + assert_eq!(values, vec![ + (key("b"), SeqMarked::new_tombstone(3)), + (key("c"), SeqMarked::new_normal(3, value("c-value"))), + (key("d"), SeqMarked::new_normal(4, value("d-value"))), + ]); } #[tokio::test] - async fn test_sequence_ordering() { - 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)); - - view.set(TestSpace::Space1, key("k2"), Some(value("v2"))); - assert_eq!(view.last_seq, InternalSeq::new(12)); - - view.set(TestSpace::Space1, key("k3"), Some(value("v3"))); - assert_eq!(view.last_seq, InternalSeq::new(13)); - - // Verify sequences are assigned correctly by reading changes - let table = &view.changes[&TestSpace::Space1]; + async fn test_fetch_and_set_transitions() { + let mut view = view(); assert_eq!( - table.get(key("k1"), 13), - SeqMarked::new_normal(11, &value("v1")) + view.fetch_and_set(key("base"), Some(value("updated"))) + .await + .unwrap(), + ( + SeqMarked::new_normal(1, value("base-value")), + SeqMarked::new_normal(2, value("updated")), + ) ); assert_eq!( - table.get(key("k2"), 13), - SeqMarked::new_normal(12, &value("v2")) + view.fetch_and_set(key("base"), None).await.unwrap(), + ( + SeqMarked::new_normal(2, value("updated")), + SeqMarked::new_tombstone(2), + ) ); assert_eq!( - table.get(key("k3"), 13), - SeqMarked::new_normal(13, &value("v3")) + view.fetch_and_set(key("base"), Some(value("resurrected"))) + .await + .unwrap(), + ( + SeqMarked::new_tombstone(2), + SeqMarked::new_normal(3, value("resurrected")), + ) ); } #[tokio::test] - async fn test_multiple_spaces() { - 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"))); - - assert_eq!(view.changes.len(), 2); - assert!(view.changes.contains_key(&TestSpace::Space1)); - assert!(view.changes.contains_key(&TestSpace::Space2)); - - // Verify data in different spaces by reading changes - let space1_table = &view.changes[&TestSpace::Space1]; - let space2_table = &view.changes[&TestSpace::Space2]; - + async fn test_initial_sequence_is_respected() { + let mut view = view().with_initial_seq(InternalSeq::new(10)); assert_eq!( - space1_table.get(key("k1"), 12), - SeqMarked::new_normal(11, &value("v1")) + view.set(key("primary"), Some(value("p"))), + SeqMarked::new_normal(11, ()) ); assert_eq!( - space2_table.get(key("k2"), 12), - SeqMarked::new_normal(12, &value("v2")) + view.set_without_seq_increment(key("secondary"), Some(value("s"))), + SeqMarked::new_normal(11, ()) ); + assert_eq!(view.last_seq, InternalSeq::new(11)); } #[tokio::test] - async fn test_complex_scenario() { - let mut view = create_view(create_base_snapshot()); - - // Insert new values - view.set(TestSpace::Space1, key("new_k1"), Some(value("new_v1"))); - // Override base value - view.set( - TestSpace::Space1, - key("base_k1"), - Some(value("updated_base_v1")), - ); - // Delete base value - view.set(TestSpace::Space1, key("base_k2"), None); + async fn test_sequence_ordering() { + let mut view = view(); + view.set(key("k1"), Some(value("v1"))); + view.set(key("k2"), Some(value("v2"))); + view.set(key("k3"), Some(value("v3"))); - // Verify write operations by reading changes directly - let table = &view.changes[&TestSpace::Space1]; + assert_eq!(view.last_seq, InternalSeq::new(4)); assert_eq!( - table.get(key("new_k1"), 12), - SeqMarked::new_normal(11, &value("new_v1")) + view.changes.get(key("k1"), 4), + SeqMarked::new_normal(2, &value("v1")) ); assert_eq!( - table.get(key("base_k1"), 12), - SeqMarked::new_normal(12, &value("updated_base_v1")) + view.changes.get(key("k2"), 4), + SeqMarked::new_normal(3, &value("v2")) ); - assert_eq!(table.get(key("base_k2"), 12), SeqMarked::new_tombstone(12)); // no seq increase for tombstone - - // Test mget - let keys = vec![key("base_k1"), key("base_k2"), key("new_k1")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 3); assert_eq!( - result[0], - SeqMarked::new_normal(12, value("updated_base_v1")) + view.changes.get(key("k3"), 4), + SeqMarked::new_normal(4, &value("v3")) ); - assert_eq!(result[1], SeqMarked::new_tombstone(12)); // deleted (no seq increase) - assert_eq!(result[2], SeqMarked::new_normal(11, value("new_v1"))); } - // ===== CORNER CASE TESTS ===== - #[tokio::test] async fn test_empty_base_view() { - let tables = BTreeMap::new(); // Completely empty base - 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); + let mut view = View::new(Snapshot::new(InternalSeq::new(1), Table::new())); + view.set(key("k1"), Some(value("v1"))); + view.set(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(2, &value("v1")) + view.get_many(vec![key("k1"), key("k2")]).await.unwrap(), + vec![ + SeqMarked::new_normal(2, value("v1")), + SeqMarked::new_tombstone(2), + ] ); - 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(2, value("v1"))); - assert_eq!(result[1], SeqMarked::new_tombstone(2)); + let values = view + .range(..) + .await + .unwrap() + .try_collect::>() + .await + .unwrap(); + assert_eq!(values, vec![ + (key("k1"), SeqMarked::new_normal(2, value("v1"))), + (key("k2"), SeqMarked::new_tombstone(2)), + ]); } #[tokio::test] async fn test_zero_initial_sequence() { - let mut view = View::new(create_base_snapshot()).with_initial_seq(InternalSeq::new(0)); + let mut view = view().with_initial_seq(InternalSeq::new(0)); + view.set(key("k1"), Some(value("v1"))); + view.set(key("k2"), None); - view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); assert_eq!(view.last_seq, InternalSeq::new(1)); - - // Tombstone should use seq 1 (no increment) - view.set(TestSpace::Space1, key("k2"), None); - assert_eq!(view.last_seq, InternalSeq::new(1)); - - let table = &view.changes[&TestSpace::Space1]; assert_eq!( - table.get(key("k1"), 1), - SeqMarked::new_normal(1, &value("v1")) + view.get_many(vec![key("k1"), key("k2")]).await.unwrap(), + vec![ + SeqMarked::new_normal(1, value("v1")), + SeqMarked::new_tombstone(1), + ] ); - assert_eq!(table.get(key("k2"), 1), SeqMarked::new_tombstone(1)); } #[tokio::test] async fn test_max_sequence_boundary() { - let mut view = - View::new(create_base_snapshot()).with_initial_seq(InternalSeq::new(u64::MAX - 2)); + let mut view = view().with_initial_seq(InternalSeq::new(u64::MAX - 2)); - view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); + view.set(key("k1"), Some(value("v1"))); assert_eq!(view.last_seq, InternalSeq::new(u64::MAX - 1)); - view.set(TestSpace::Space1, key("k2"), Some(value("v2"))); + view.set(key("k2"), Some(value("v2"))); assert_eq!(view.last_seq, InternalSeq::new(u64::MAX)); - // Should panic on overflow let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - view.set(TestSpace::Space1, key("k3"), Some(value("v3"))) + view.set(key("k3"), Some(value("v3"))) })); - assert!(result.is_err()); // Should panic on sequence overflow - } - - #[tokio::test] - async fn test_tombstone_resurrection() { - let mut view = create_view(create_base_snapshot()); - - // Delete base key - view.set(TestSpace::Space1, key("base_k1"), None); - assert_eq!(view.last_seq, InternalSeq::new(10)); // No increment - - // Resurrect the key - view.set( - TestSpace::Space1, - key("base_k1"), - Some(value("resurrected")), - ); - assert_eq!(view.last_seq, InternalSeq::new(11)); // Increment for normal value - - // Verify both operations - let table = &view.changes[&TestSpace::Space1]; - let result = table.get(key("base_k1"), 11); - assert_eq!(result, SeqMarked::new_normal(11, &value("resurrected"))); // Latest wins - - // Via mget, should see resurrected value - let keys = vec![key("base_k1")]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - assert_eq!(result[0], SeqMarked::new_normal(11, value("resurrected"))); + assert!(result.is_err()); } #[tokio::test] async fn test_multiple_updates_same_key() { - let mut view = create_view(create_base_snapshot()); - - // Multiple updates to same key - view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); - view.set(TestSpace::Space1, key("k1"), Some(value("v2"))); - view.set(TestSpace::Space1, key("k1"), Some(value("v3"))); - view.set(TestSpace::Space1, key("k1"), None); // Delete - view.set(TestSpace::Space1, key("k1"), Some(value("v4"))); // Resurrect - - assert_eq!(view.last_seq, InternalSeq::new(14)); + let mut view = view(); + view.set(key("key"), Some(value("v1"))); + view.set(key("key"), Some(value("v2"))); + view.set(key("key"), Some(value("v3"))); + view.set(key("key"), None); + view.set(key("key"), Some(value("v4"))); - // Should see latest version - let table = &view.changes[&TestSpace::Space1]; - let result = table.get(key("k1"), 14); - assert_eq!(result, SeqMarked::new_normal(14, &value("v4"))); - - // All intermediate versions should be visible at their sequence points - assert_eq!( - table.get(key("k1"), 11), - SeqMarked::new_normal(11, &value("v1")) - ); + assert_eq!(view.last_seq, InternalSeq::new(5)); assert_eq!( - table.get(key("k1"), 12), - SeqMarked::new_normal(12, &value("v2")) - ); - // At seq 13, tombstone overwrites v3 since tombstone uses same seq as last operation - assert_eq!(table.get(key("k1"), 13), SeqMarked::new_tombstone(13)); - assert_eq!( - table.get(key("k1"), 14), - SeqMarked::new_normal(14, &value("v4")) - ); // Latest is resurrection - } - - #[tokio::test] - async fn test_empty_key_lists() { - let mut view = create_view(create_base_snapshot()); - view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); - - // Empty key list should return empty result - let result = view.get_many(TestSpace::Space1, vec![]).await.unwrap(); - assert_eq!(result.len(), 0); - - // Empty key list on non-existent space - let result = view.get_many(TestSpace::Space2, vec![]).await.unwrap(); - assert_eq!(result.len(), 0); - } - - #[tokio::test] - async fn test_range_bounded_operations() { - 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"))); - - // Bounded range - let mut stream = view - .range(TestSpace::Space1, key("b")..key("d")) - .await - .unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 3); // base_k1, base_k2 and c (all > "b" and < "d") - 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(12, value("vc"))) + view.changes.get(key("key"), 2), + SeqMarked::new_normal(2, &value("v1")) ); - - // Inclusive range - let mut stream = view - .range(TestSpace::Space1, key("base_k1")..=key("base_k2")) - .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("base_k1"), SeqMarked::new_normal(1, value("base_v1"))) + view.changes.get(key("key"), 3), + SeqMarked::new_normal(3, &value("v2")) ); + assert_eq!(view.changes.get(key("key"), 4), SeqMarked::new_tombstone(4)); assert_eq!( - results[1], - (key("base_k2"), SeqMarked::new_normal(2, value("base_v2"))) + view.get(key("key")).await.unwrap(), + SeqMarked::new_normal(5, value("v4")) ); } #[tokio::test] - async fn test_range_with_all_tombstones() { - let mut view = create_view(create_base_snapshot()); - - // Delete all base keys and add only tombstones - view.set(TestSpace::Space1, key("base_k1"), None); - view.set(TestSpace::Space1, key("base_k2"), None); - view.set(TestSpace::Space1, key("k1"), None); - view.set(TestSpace::Space1, key("k2"), None); - - 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 see tombstones for all keys - assert_eq!(results.len(), 4); - assert_eq!(results[0], (key("base_k1"), SeqMarked::new_tombstone(10))); - assert_eq!(results[1], (key("base_k2"), SeqMarked::new_tombstone(10))); - assert_eq!(results[2], (key("k1"), SeqMarked::new_tombstone(10))); - assert_eq!(results[3], (key("k2"), SeqMarked::new_tombstone(10))); - } - - #[tokio::test] - async fn test_range_single_key() { - let view = create_view(create_base_snapshot()); - - // Single key range - let mut stream = view - .range(TestSpace::Space1, key("base_k1")..=key("base_k1")) - .await - .unwrap(); - let mut results = Vec::new(); - while let Some(result) = stream.next().await { - results.push(result.unwrap()); - } - - assert_eq!(results.len(), 1); + async fn test_empty_key_lists() { assert_eq!( - results[0], - (key("base_k1"), SeqMarked::new_normal(1, value("base_v1"))) + view().get_many(vec![]).await.unwrap(), + Vec::>::new() ); } #[tokio::test] - async fn test_range_empty_result() { - let view = create_view(create_base_snapshot()); - - // Range that matches no keys - let mut stream = view - .range(TestSpace::Space1, key("x")..key("z")) + async fn test_range_from_base_only() { + let values = view() + .range(..) .await - .unwrap(); - let result = stream.next().await; - assert!(result.is_none()); - - // Single point range for non-existent key - let mut stream = view - .range(TestSpace::Space1, key("x")..=key("x")) + .unwrap() + .try_collect::>() .await .unwrap(); - let result = stream.next().await; - assert!(result.is_none()); - } - - #[tokio::test] - async fn test_view_sequence_filtering() { - let mut table = Table::new(); - table.insert(key("k1"), 10, value("v1")).unwrap(); - table.insert(key("k2"), 20, value("v2")).unwrap(); - - let mut tables = BTreeMap::new(); - tables.insert(TestSpace::Space1, table); - // Can only see k1, not k2 - let base = TablesSnapshot::new(InternalSeq::new(15), tables); - - let view = create_view(base); - 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(10, value("v1"))); // visible - assert!(result[1].is_not_found()); // filtered out by view_seq + assert_eq!(values, vec![( + key("base"), + SeqMarked::new_normal(1, value("base-value")) + )]); } #[tokio::test] - async fn test_large_key_value_operations() { - let mut view = create_view(create_base_snapshot()); - - // Large key and value - let large_key = key(&"x".repeat(1000)); - let large_value = value(&"y".repeat(1000)); - - view.set( - TestSpace::Space1, - large_key.clone(), - Some(large_value.clone()), - ); - - // Verify large data handling - let table = &view.changes[&TestSpace::Space1]; - let result = table.get(large_key.clone(), 11); - assert_eq!(result, SeqMarked::new_normal(11, &large_value)); - - // Via mget - let keys = vec![large_key]; - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - assert_eq!(result[0], SeqMarked::new_normal(11, large_value)); - } - - #[tokio::test] - async fn test_many_keys_operation() { - let mut view = create_view(create_base_snapshot()); - - // Insert many keys - for i in 0..100 { - view.set( - TestSpace::Space1, - key(&format!("k{:03}", i)), - Some(value(&format!("v{}", i))), - ); - } - - assert_eq!(view.last_seq, InternalSeq::new(110)); // 10 + 100 - - // mget many keys - let keys: Vec<_> = (0..100).map(|i| key(&format!("k{:03}", i))).collect(); - let result = view.get_many(TestSpace::Space1, keys).await.unwrap(); - - assert_eq!(result.len(), 100); - for (i, seq_marked) in result.iter().enumerate() { - let expected_seq = 11 + i as u64; - assert_eq!( - *seq_marked, - SeqMarked::new_normal(expected_seq, value(&format!("v{}", i))) - ); - } - } - - #[tokio::test] - async fn test_sequence_exact_match_boundary() { - 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 - - // mget with last_seq exactly matching - 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(11, value("v1"))); - assert_eq!(result[1], SeqMarked::new_normal(12, value("v2"))); - } - - #[tokio::test] - async fn test_cross_space_sequence_isolation() { - 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 - view.set(TestSpace::Space2, key("k2"), Some(value("v2"))); // seq 7 - view.set(TestSpace::Space1, key("k3"), Some(value("v3"))); // seq 8 - - assert_eq!(view.last_seq, InternalSeq::new(13)); - - // Verify sequences are properly assigned across spaces - let table1 = &view.changes[&TestSpace::Space1]; - let table2 = &view.changes[&TestSpace::Space2]; - - assert_eq!( - table1.get(key("k1"), 13), - SeqMarked::new_normal(11, &value("v1")) - ); - assert_eq!( - table2.get(key("k2"), 13), - SeqMarked::new_normal(12, &value("v2")) - ); - assert_eq!( - table1.get(key("k3"), 13), - SeqMarked::new_normal(13, &value("v3")) - ); - } - - #[tokio::test] - async fn test_set_return_value() { - let mut view = create_view(create_base_snapshot()); - - // Test normal value insertion - let order_key1 = view.set(TestSpace::Space1, key("k1"), Some(value("v1"))); - assert_eq!(order_key1, SeqMarked::new_normal(11, ())); - - // Test another normal value - let order_key2 = view.set(TestSpace::Space1, key("k2"), Some(value("v2"))); - assert_eq!(order_key2, SeqMarked::new_normal(12, ())); - - // Test tombstone insertion (should not increment with no seq increase) - let order_key3 = view.set(TestSpace::Space1, key("k3"), None); - assert_eq!(order_key3, SeqMarked::new_tombstone(12)); - - // Test tombstone with seq increase - let order_key4 = view.set(TestSpace::Space1, key("k4"), None); - assert_eq!(order_key4, SeqMarked::new_tombstone(12)); - } - - #[tokio::test] - async fn test_set_return_value_with_increment() { - 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"))); - assert_eq!(order_key1, SeqMarked::new_normal(11, ())); - - // Test tombstone with seq increment - let order_key2 = view.set(TestSpace::Space1, key("k2"), None); - assert_eq!(order_key2, SeqMarked::new_tombstone(12)); - - // Test another value - let order_key3 = view.set(TestSpace::Space1, key("k3"), Some(value("v3"))); - assert_eq!(order_key3, SeqMarked::new_normal(13, ())); - } - - #[tokio::test] - async fn test_fetch_and_set_nonexistent() { - let mut view = create_view(create_base_snapshot()); + async fn test_range_with_all_tombstones() { + let mut view = view(); + view.set(key("base"), None); + view.set(key("k1"), None); + view.set(key("k2"), None); - let (old_value, new_value) = view - .fetch_and_set(TestSpace::Space1, key("new_key"), Some(value("new_value"))) + let values = view + .range(..) .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 - let current_value = view.get(TestSpace::Space1, key("new_key")).await.unwrap(); - assert_eq!(current_value, SeqMarked::new_normal(11, value("new_value"))); - } - - #[tokio::test] - async fn test_fetch_and_set_existing_from_base() { - let mut view = create_view(create_base_snapshot()); - - let (old_value, new_value) = view - .fetch_and_set( - TestSpace::Space1, - key("base_k1"), - Some(value("updated_value")), - ) + .unwrap() + .try_collect::>() .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 - let current_value = view.get(TestSpace::Space1, key("base_k1")).await.unwrap(); - assert_eq!( - current_value, - SeqMarked::new_normal(11, value("updated_value")) - ); + assert_eq!(values, vec![ + (key("base"), SeqMarked::new_tombstone(1)), + (key("k1"), SeqMarked::new_tombstone(1)), + (key("k2"), SeqMarked::new_tombstone(1)), + ]); } #[tokio::test] - async fn test_fetch_and_set_existing_from_changes() { - let mut view = create_view(create_base_snapshot()); - - // First set a value to create changes - view.set( - TestSpace::Space1, - key("test_key"), - Some(value("first_value")), - ); - - let (old_value, new_value) = view - .fetch_and_set( - TestSpace::Space1, - key("test_key"), - Some(value("second_value")), - ) + async fn test_range_single_key() { + let values = view() + .range(key("base")..=key("base")) .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 current_value = view.get(TestSpace::Space1, key("test_key")).await.unwrap(); - assert_eq!( - current_value, - SeqMarked::new_normal(12, value("second_value")) - ); - } - - #[tokio::test] - async fn test_fetch_and_set_tombstone() { - let mut view = create_view(create_base_snapshot()); - - let (old_value, new_value) = view - .fetch_and_set(TestSpace::Space1, key("base_k1"), None) + .unwrap() + .try_collect::>() .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 - let current_value = view.get(TestSpace::Space1, key("base_k1")).await.unwrap(); - assert_eq!(current_value, SeqMarked::new_tombstone(10)); + assert_eq!(values, vec![( + key("base"), + SeqMarked::new_normal(1, value("base-value")) + )]); } #[tokio::test] - async fn test_fetch_and_set_cross_space() { - let mut view = create_view(create_base_snapshot()); - - // Set value in Space1 - let (old1, new1) = view - .fetch_and_set(TestSpace::Space1, key("k1"), Some(value("v1"))) + async fn test_range_empty_result() { + let values = view() + .range(key("x")..=key("x")) .await - .unwrap(); - - // Set value in Space2 - let (old2, new2) = view - .fetch_and_set(TestSpace::Space2, key("k2"), Some(value("v2"))) + .unwrap() + .try_collect::>() .await .unwrap(); - // Verify sequence ordering across spaces - 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 view's last_seq was updated correctly - assert_eq!(view.last_seq, InternalSeq::new(12)); + assert_eq!(values, Vec::<(TestKey, SeqMarked)>::new()); } - #[tokio::test] - async fn test_fetch_and_set_tombstone_resurrection() { - let mut view = create_view(create_base_snapshot()); + #[test] + fn test_into_parts_preserves_staged_changes() { + let mut view = view(); + view.set(key("new"), Some(value("new-value"))); - // First delete a base key - let (old1, new1) = view - .fetch_and_set(TestSpace::Space1, key("base_k1"), None) - .await - .unwrap(); - assert_eq!(old1, SeqMarked::new_normal(1, value("base_v1"))); - assert_eq!(new1, SeqMarked::new_tombstone(10)); - - // Then resurrect it - let (old2, new2) = view - .fetch_and_set( - TestSpace::Space1, - key("base_k1"), - Some(value("resurrected")), - ) - .await - .unwrap(); - assert_eq!(old2, SeqMarked::new_tombstone(10)); - assert_eq!(new2, SeqMarked::new_normal(11, value("resurrected"))); - - // Verify final state - let current_value = view.get(TestSpace::Space1, key("base_k1")).await.unwrap(); + let (_data, last_seq, changes) = view.into_parts(); + assert_eq!(last_seq, InternalSeq::new(2)); assert_eq!( - current_value, - SeqMarked::new_normal(11, value("resurrected")) + changes.get(key("new"), 2), + SeqMarked::new_normal(2, &value("new-value")) ); } - - #[tokio::test] - async fn test_fetch_and_set_delete_nonexistent() { - let mut view = create_view(create_base_snapshot()); - - // Try to delete a key that doesn't exist - let (old_value, new_value) = view - .fetch_and_set(TestSpace::Space1, key("nonexistent_key"), None) - .await - .unwrap(); - - // Should return not_found for old value - assert_eq!(old_value, SeqMarked::new_not_found()); - // Should return tombstone with seq 0 (no tombstone created) - assert_eq!(new_value, SeqMarked::new_tombstone(0)); - - // Verify no tombstone was actually created in the changes - let key_exists_in_table = view - .changes - .get(&TestSpace::Space1) - .map(|table| { - table - .inner - .keys() - .any(|(k, _)| k == &key("nonexistent_key")) - }) - .unwrap_or(false); - assert!(!key_exists_in_table); - - // Verify the key still doesn't exist - let current_value = view - .get(TestSpace::Space1, key("nonexistent_key")) - .await - .unwrap(); - assert_eq!(current_value, SeqMarked::new_not_found()); - - // Verify last_seq was not incremented - assert_eq!(view.last_seq, InternalSeq::new(10)); - } } diff --git a/src/mvcc/seq_bounded_read.rs b/src/mvcc/view_api.rs similarity index 51% rename from src/mvcc/seq_bounded_read.rs rename to src/mvcc/view_api.rs index eab5852..4349e00 100644 --- a/src/mvcc/seq_bounded_read.rs +++ b/src/mvcc/view_api.rs @@ -12,29 +12,27 @@ // 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; +use crate::mvcc::ViewGet; +use crate::mvcc::ViewRange; +use crate::mvcc::ViewSet; +use crate::MapKey; -#[async_trait::async_trait] -pub trait SeqBoundedRead +/// Combined MVCC API for a view that owns its sequence boundary. +/// +/// This trait combines point reads, range reads, and writes over one key-value space. +/// +/// # Type Parameters +/// - `K`: Key type satisfying [`MapKey`] constraints +pub trait ViewApi where - Self: Send + Sync, - Self: SeqBoundedGet + SeqBoundedRange, - S: ViewNamespace, - K: ViewKey, - V: ViewValue, + K: MapKey, + Self: ViewGet + ViewRange + ViewSet, { } -impl SeqBoundedRead for T +impl ViewApi for T where - T: Send + Sync, - T: SeqBoundedGet + SeqBoundedRange, - S: ViewNamespace, - K: ViewKey, - V: ViewValue, + K: MapKey, + T: ViewGet + ViewRange + ViewSet, { } diff --git a/src/mvcc/scoped_read.rs b/src/mvcc/view_get.rs similarity index 55% rename from src/mvcc/scoped_read.rs rename to src/mvcc/view_get.rs index e441fab..58f03bc 100644 --- a/src/mvcc/scoped_read.rs +++ b/src/mvcc/view_get.rs @@ -12,26 +12,24 @@ // 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; +use std::io; +use seq_marked::SeqMarked; + +use crate::MapKey; + +/// Point read operations for a view that owns its sequence boundary. #[async_trait::async_trait] -pub trait ScopedRead -where - K: ViewKey, - V: ViewValue, - Self: ScopedGet, - Self: ScopedRange, +pub trait ViewGet: Send + Sync +where K: MapKey { -} + async fn get(&self, key: K) -> Result, io::Error>; -impl ScopedRead for T -where - K: ViewKey, - V: ViewValue, - T: ScopedGet, - T: ScopedRange, -{ + async fn get_many(&self, keys: Vec) -> Result>, io::Error> { + let mut values = Vec::with_capacity(keys.len()); + for key in keys { + values.push(self.get(key).await?); + } + Ok(values) + } } diff --git a/src/mvcc/view_namespace.rs b/src/mvcc/view_namespace.rs deleted file mode 100644 index b66adbc..0000000 --- a/src/mvcc/view_namespace.rs +++ /dev/null @@ -1,64 +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::fmt; - -/// Trait for namespace identifiers in MVCC operations. -/// -/// Namespaces partition data into logical domains and control sequence number allocation behavior. -/// Each namespace can independently decide whether operations increment the global sequence counter. -/// -/// # Type Requirements -/// - **Copy + Clone**: Efficient parameter passing and storage -/// - **Ord**: Deterministic ordering for consistent iteration -/// - **Debug**: Troubleshooting and logging support -/// - **Send + Sync**: Thread-safe concurrent access -/// - **Unpin + 'static**: Async compatibility and stable lifetime -/// -/// # Sequence Number Strategy -/// -/// The [`increments_seq()`] method controls whether insertions in this namespace -/// advance the global sequence counter. This enables related data to share sequence -/// numbers for consistency. -/// -/// # Examples -/// ```rust,ignore -/// #[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)] -/// enum MyNamespace { -/// PrimaryData, // increments_seq() = true -/// SecondaryIndex, // increments_seq() = false (shares seq with primary) -/// } -/// -/// impl ViewNamespace for MyNamespace { -/// fn increments_seq(&self) -> bool { -/// matches!(self, MyNamespace::PrimaryData) -/// } -/// } -/// ``` -/// -/// [`increments_seq()`]: Self::increments_seq -pub trait ViewNamespace -where Self: Clone + Copy + Ord + fmt::Debug + Send + Sync + Unpin + 'static -{ - /// Whether insertions in this namespace increment the global sequence number. - /// - /// Primary data typically returns `true` to advance the sequence, while related - /// data (like secondary indices) can return `false` to share the sequence number - /// with their associated primary record. - /// - /// # Example Relationship - /// - Primary record: `(key, seq=5) = "value"` - /// - Index record: `(index_key, seq=5) = key` (shares sequence with primary) - fn increments_seq(&self) -> bool; -} diff --git a/src/mvcc/scoped_range.rs b/src/mvcc/view_range.rs similarity index 61% rename from src/mvcc/scoped_range.rs rename to src/mvcc/view_range.rs index 4b0fc54..280f121 100644 --- a/src/mvcc/scoped_range.rs +++ b/src/mvcc/view_range.rs @@ -17,24 +17,15 @@ use std::ops::RangeBounds; use seq_marked::SeqMarked; -use crate::mvcc::ViewKey; -use crate::mvcc::ViewValue; use crate::IOResultStream; +use crate::MapKey; -/// 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. +/// Range read operations for a view that owns its sequence boundary. #[async_trait::async_trait] -pub trait ScopedRange -where - Self: Send + Sync, - K: ViewKey, - V: ViewValue, +pub trait ViewRange: Send + Sync +where K: MapKey { - /// Returns an async stream of key-value pairs within the specified range, consuming self. - async fn range(&self, range: R) -> Result)>, io::Error> + /// Returns an async stream of key-value pairs within the specified range. + async fn range(&self, range: R) -> Result)>, io::Error> where R: RangeBounds + Send + Sync + Clone + 'static; } diff --git a/src/mvcc/scoped_set.rs b/src/mvcc/view_set.rs similarity index 75% rename from src/mvcc/scoped_set.rs rename to src/mvcc/view_set.rs index a647daf..6d5d987 100644 --- a/src/mvcc/scoped_set.rs +++ b/src/mvcc/view_set.rs @@ -16,25 +16,20 @@ use std::io; use seq_marked::SeqMarked; -use crate::mvcc::ScopedGet; -use crate::mvcc::ViewKey; -use crate::mvcc::ViewValue; +use crate::mvcc::ViewGet; +use crate::MapKey; -/// A view bound to a specific namespace. +/// A view that owns its sequence boundary. /// -/// This trait provides read-write access to data within a namespace that is embedded -/// in the view implementation, eliminating the need to specify namespace -/// parameters for each operation. +/// This trait provides writes to one key-value space without requiring a sequence +/// boundary for each operation. /// -/// Extends [`ScopedGet`] with write operations: -/// - [`set`](Self::set) - Set or delete values within the scoped namespace +/// Provides write operations: +/// - [`set`](Self::set) - Set or delete values /// - [`fetch_and_set`](Self::fetch_and_set) - Atomically get old value and set new value, returning both #[async_trait::async_trait] -pub trait ScopedSet -where - K: ViewKey, - V: ViewValue, - Self: ScopedGet, +pub trait ViewSet: ViewGet +where K: MapKey { /// Fetch the current value of a key and set it to a new value atomically. /// @@ -42,33 +37,31 @@ where /// - `old_value` is the previous value (or `SeqMarked::new_not_found()` if key didn't exist) /// - `new_value` is the newly set value (or `SeqMarked::new_tombstone()` if deleted) /// - /// This is useful for atomic get-then-set operations within the scoped namespace. + /// This is useful for atomic get-then-set operations. async fn fetch_and_set( &mut self, key: K, - value: Option, - ) -> Result<(SeqMarked, SeqMarked), io::Error> { + value: Option, + ) -> Result<(SeqMarked, SeqMarked), io::Error> { let old_value = self.get(key.clone()).await?; - if old_value.is_not_found() && value.is_none() { - // No such entry at all, no need to create a tombstone for delete return Ok((old_value, SeqMarked::new_tombstone(0))); } - let order_key = self.set(key.clone(), value.clone()); + let order_key = self.set(key, value.clone()); let new_value = match value { - Some(v) => order_key.map(|_| v), + Some(value) => order_key.map(|_| value), None => SeqMarked::new_tombstone(*order_key.internal_seq()), }; Ok((old_value, new_value)) } - /// Set or delete a value within the scoped namespace. + /// Set or delete a value. /// /// Returns the sequence marker of the newly set value. /// /// For atomic get-then-set operations, see [`fetch_and_set`](Self::fetch_and_set). - fn set(&mut self, key: K, value: Option) -> SeqMarked<()>; + fn set(&mut self, key: K, value: Option) -> SeqMarked<()>; } #[cfg(test)] @@ -79,6 +72,7 @@ mod tests { use seq_marked::SeqMarked; use super::*; + use crate::MapKey; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] struct TestKey(String); @@ -86,6 +80,10 @@ mod tests { #[derive(Debug, Clone, PartialEq, Eq)] struct TestValue(String); + impl MapKey for TestKey { + type V = TestValue; + } + fn key(s: &str) -> TestKey { TestKey(s.to_string()) } @@ -94,11 +92,11 @@ mod tests { } // Mock implementation for testing - struct MockScopedView { + struct MockView { data: BTreeMap>, } - impl MockScopedView { + impl MockView { fn new() -> Self { let mut data = BTreeMap::new(); data.insert( @@ -108,10 +106,7 @@ mod tests { Self { data } } - } - #[async_trait::async_trait] - impl ScopedGet for MockScopedView { async fn get(&self, key: TestKey) -> Result, io::Error> { match self.data.get(&key) { Some(value) => Ok(value.clone()), @@ -121,7 +116,13 @@ mod tests { } #[async_trait::async_trait] - impl ScopedSet for MockScopedView { + impl ViewGet for MockView { + async fn get(&self, key: TestKey) -> Result, io::Error> { + MockView::get(self, key).await + } + } + + impl ViewSet for MockView { fn set(&mut self, key: TestKey, value: Option) -> SeqMarked<()> { match value { Some(v) => { @@ -137,34 +138,34 @@ mod tests { } #[tokio::test] - async fn test_scoped_view_trait_set_value() { - let mut view = MockScopedView::new(); + async fn test_view_set_value() { + let mut view = MockView::new(); let order_key = view.set(key("new_key"), Some(value("new_value"))); // Should return the order key assert_eq!(order_key, SeqMarked::new_normal(2, ())); - // Verify through the read-only interface + // Verify the stored value let result = view.get(key("new_key")).await.unwrap(); assert_eq!(result, SeqMarked::new_normal(2, value("new_value"))); } #[tokio::test] - async fn test_scoped_view_trait_set_tombstone() { - let mut view = MockScopedView::new(); + async fn test_view_set_tombstone() { + let mut view = MockView::new(); let order_key = view.set(key("tombstone_key"), None); // Should return the order key for tombstone assert_eq!(order_key, SeqMarked::new_tombstone(2)); - // Verify through the read-only interface + // Verify the stored value let result = view.get(key("tombstone_key")).await.unwrap(); assert_eq!(result, SeqMarked::new_tombstone(2)); } #[tokio::test] - async fn test_scoped_view_trait_overwrite_existing() { - let mut view = MockScopedView::new(); + async fn test_view_set_overwrite_existing() { + let mut view = MockView::new(); // Verify initial value let initial = view.get(key("initial_key")).await.unwrap(); @@ -182,22 +183,8 @@ mod tests { } #[tokio::test] - async fn test_scoped_view_trait_inherits_readonly() { - let mut view = MockScopedView::new(); - - // Test that ScopedView implements ScopedViewReadonly - let order_key = view.set(key("test_key"), Some(value("test_value"))); - - // Should return the order key - assert_eq!(order_key, SeqMarked::new_normal(2, ())); - - 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_fetch_and_set_nonexistent() { - let mut view = MockScopedView::new(); + async fn test_view_set_fetch_and_set_nonexistent() { + let mut view = MockView::new(); let (old_value, new_value) = view .fetch_and_set(key("new_key"), Some(value("new_value"))) @@ -215,8 +202,8 @@ mod tests { } #[tokio::test] - async fn test_scoped_view_trait_fetch_and_set_existing() { - let mut view = MockScopedView::new(); + async fn test_view_set_fetch_and_set_existing() { + let mut view = MockView::new(); let (old_value, new_value) = view .fetch_and_set(key("initial_key"), Some(value("updated_value"))) @@ -237,8 +224,8 @@ mod tests { } #[tokio::test] - async fn test_scoped_view_trait_fetch_and_set_tombstone() { - let mut view = MockScopedView::new(); + async fn test_view_set_fetch_and_set_tombstone() { + let mut view = MockView::new(); let (old_value, new_value) = view.fetch_and_set(key("initial_key"), None).await.unwrap(); @@ -253,8 +240,8 @@ mod tests { } #[tokio::test] - async fn test_scoped_view_trait_fetch_and_set_tombstone_to_value() { - let mut view = MockScopedView::new(); + async fn test_view_set_fetch_and_set_tombstone_to_value() { + let mut view = MockView::new(); // First delete the key view.set(key("initial_key"), None); @@ -278,8 +265,8 @@ mod tests { } #[tokio::test] - async fn test_scoped_view_trait_fetch_and_set_multiple_operations() { - let mut view = MockScopedView::new(); + async fn test_view_set_fetch_and_set_multiple_operations() { + let mut view = MockView::new(); // First operation on existing key let (old1, new1) = view @@ -303,8 +290,8 @@ mod tests { } #[tokio::test] - async fn test_scoped_view_trait_fetch_and_set_delete_nonexistent() { - let mut view = MockScopedView::new(); + async fn test_view_set_fetch_and_set_delete_nonexistent() { + let mut view = MockView::new(); // Try to delete a key that doesn't exist let (old_value, new_value) = view