From 39d0f8654db4165e59c20e9bda9fc79db8b7b04a Mon Sep 17 00:00:00 2001 From: qwp0905 Date: Fri, 8 May 2026 14:28:48 +0900 Subject: [PATCH 1/5] add entry view --- src/cursor/entry_view.rs | 125 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 src/cursor/entry_view.rs diff --git a/src/cursor/entry_view.rs b/src/cursor/entry_view.rs new file mode 100644 index 0000000..d40f6e4 --- /dev/null +++ b/src/cursor/entry_view.rs @@ -0,0 +1,125 @@ +use crate::{ + disk::{Page, Pointer}, + serialize::{Deserializable, SerializeType, TypedObject, Viewable}, + wal::TxId, + Error, +}; + +pub enum RecordDataView { + Data(usize, usize), + Chunked(Vec), + Tombstone, +} + +pub struct VersionRecordView { + pub owner: TxId, + pub version: TxId, + pub data: RecordDataView, +} +impl VersionRecordView { + pub fn new(owner: TxId, version: TxId, data: RecordDataView) -> Self { + Self { + owner, + version, + data, + } + } +} + +pub struct DataEntryView { + next: Option, + versions: Vec, +} +impl DataEntryView { + pub fn find

(&self, predicate: P) -> Option<&VersionRecordView> + where + P: FnMut(&&VersionRecordView) -> bool, + { + self.versions.iter().find(predicate) + } + + pub fn is_empty(&self) -> bool { + if self.versions.is_empty() { + return true; + } + if self.versions.len() > 1 { + return false; + } + if let RecordDataView::Tombstone = self.versions[0].data { + return true; + } + false + } + + pub const fn get_next(&self) -> Option { + self.next + } +} +impl TypedObject for DataEntryView { + fn get_type() -> SerializeType { + SerializeType::DataEntry + } +} +impl Deserializable for DataEntryView { + fn read_from(reader: &mut crate::disk::PageScanner) -> crate::Result { + let next = reader.read_u64()?; + let len = reader.read_u16()? as usize; + let mut versions = Vec::with_capacity(len); + for _ in 0..len { + let version = reader.read_u64()?; + let owner = reader.read_u64()?; + let data = match reader.read()? { + 0 => { + let l = reader.read_u16()? as usize; + let offset = reader.advance(l)?; + RecordDataView::Data(offset, offset + l) + } + 1 => RecordDataView::Tombstone, + 2 => { + let l = reader.read()? as usize; + let mut pointers = Vec::with_capacity(l); + for _ in 0..l { + pointers.push(reader.read_u64()?); + } + RecordDataView::Chunked(pointers) + } + _ => return Err(Error::InvalidFormat("invalid type for data version record")), + }; + versions.push(VersionRecordView::new(owner, version, data)) + } + Ok(Self { + versions, + next: (next != 0).then_some(next), + }) + } +} + +pub struct DataChunkView<'a> { + page: &'a Page, + start: usize, + end: usize, +} +impl<'a> DataChunkView<'a> { + pub fn get_data(&self) -> &[u8] { + self.page.range(self.start..self.end) + } +} +impl<'a> TypedObject for DataChunkView<'a> { + fn get_type() -> SerializeType { + SerializeType::DataChunk + } +} +impl<'a> Viewable<'a> for DataChunkView<'a> { + fn read_from( + page: &'a Page, + reader: &mut crate::disk::PageScanner<'a>, + ) -> crate::Result { + let len = reader.read_u16()? as usize; + let offset = reader.advance(len)?; + Ok(Self { + page, + start: offset, + end: offset + len, + }) + } +} From b5abc1d16881f93dc2a32423462b82907d162997 Mon Sep 17 00:00:00 2001 From: qwp0905 Date: Fri, 8 May 2026 14:29:14 +0900 Subject: [PATCH 2/5] add vec ref to avoid clone vector --- src/cursor/types.rs | 80 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/src/cursor/types.rs b/src/cursor/types.rs index 8cf8d7d..9c663fe 100644 --- a/src/cursor/types.rs +++ b/src/cursor/types.rs @@ -1,2 +1,78 @@ -pub type Key = Vec; -pub type KeyRef<'a> = &'a [u8]; +use std::ops::Deref; + +use crate::cache::ReadonlySlot; + +pub type StaticKey = Vec; +pub type StaticKeyRef<'a> = &'a [u8]; + +enum Type { + Refed(ReadonlySlot, usize, usize), + Copied(Vec), +} + +pub struct VecRef(Type); +impl VecRef { + pub fn refed(slot: &ReadonlySlot, start: usize, end: usize) -> Self { + Self(Type::Refed(slot.clone(), start, end)) + } + pub fn copied(data: Vec) -> Self { + Self(Type::Copied(data)) + } + + pub fn into_vec(self) -> Vec { + match self.0 { + Type::Refed(slot, s, e) => slot.as_ref().copy_range(s..e), + Type::Copied(data) => data, + } + } +} +impl Deref for VecRef { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + match &self.0 { + Type::Refed(slot, s, e) => slot.as_ref().range(*s..*e), + Type::Copied(data) => data.as_slice(), + } + } +} +impl std::fmt::Debug for VecRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.deref().fmt(f) + } +} +impl AsRef<[u8]> for VecRef { + fn as_ref(&self) -> &[u8] { + self.deref() + } +} +impl PartialEq<[u8]> for VecRef { + fn eq(&self, other: &[u8]) -> bool { + self.deref().eq(other) + } +} +impl> PartialEq for VecRef { + fn eq(&self, other: &T) -> bool { + self.eq(other.as_ref()) + } +} +impl Eq for VecRef {} + +impl PartialOrd for VecRef { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.deref().cmp(other.deref())) + } +} +impl Ord for VecRef { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.deref().cmp(other.deref()) + } +} +impl Clone for VecRef { + fn clone(&self) -> Self { + match &self.0 { + Type::Refed(slot, s, e) => Self::refed(slot, *s, *e), + Type::Copied(data) => Self::copied(data.clone()), + } + } +} From 82b2f6b72e791ace78e088980b0ce909bbb0773e Mon Sep 17 00:00:00 2001 From: qwp0905 Date: Fri, 8 May 2026 14:38:10 +0900 Subject: [PATCH 3/5] remove token from readonly slot --- src/cache/slot.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/cache/slot.rs b/src/cache/slot.rs index 560f756..bff621a 100644 --- a/src/cache/slot.rs +++ b/src/cache/slot.rs @@ -38,14 +38,9 @@ impl<'a> CacheSlot<'a> { } } #[inline] - pub fn for_read<'b>(self) -> ReadonlySlot<'b> - where - 'a: 'b, - { - ReadonlySlot { - page: self.block.load_page(), - _token: self.token, - } + pub fn for_read(self) -> ReadonlySlot { + let page = self.block.load_page(); + ReadonlySlot { page } } #[inline] @@ -110,16 +105,22 @@ impl<'a> WritableSlot<'a> { } } -pub struct ReadonlySlot<'a> { +pub struct ReadonlySlot { page: Arc>, - _token: SharedToken<'a>, } -impl<'a> AsRef> for ReadonlySlot<'a> { +impl AsRef> for ReadonlySlot { #[inline] fn as_ref(&self) -> &Page { &self.page } } +impl Clone for ReadonlySlot { + fn clone(&self) -> Self { + Self { + page: self.page.clone(), + } + } +} impl<'a> Drop for WritableSlot<'a> { fn drop(&mut self) { From 5bee75c065789c37bcd97bc05f2a8be8f54e57d3 Mon Sep 17 00:00:00 2001 From: qwp0905 Date: Fri, 8 May 2026 14:38:48 +0900 Subject: [PATCH 4/5] apply vecref and entry view --- src/cursor/btree.rs | 126 ++++++++++++++++++------------------ src/cursor/cursor.rs | 16 +++-- src/cursor/entry.rs | 28 +------- src/cursor/gc.rs | 4 +- src/cursor/internal.rs | 24 ++++--- src/cursor/leaf.rs | 34 +++++----- src/cursor/mod.rs | 5 +- src/cursor/tests/entry.rs | 3 - src/cursor/tests/node.rs | 3 +- src/disk/page.rs | 8 ++- src/disk/tests/page.rs | 2 +- src/lib.rs | 2 +- src/transaction/recorder.rs | 6 +- tests/e2e.rs | 66 ++++++++++++------- 14 files changed, 163 insertions(+), 164 deletions(-) diff --git a/src/cursor/btree.rs b/src/cursor/btree.rs index 0782789..095b82e 100644 --- a/src/cursor/btree.rs +++ b/src/cursor/btree.rs @@ -7,9 +7,10 @@ use crate::{ use crossbeam::epoch::{pin, Guard}; use super::{ - BTreeNode, BTreeNodeView, CreatablePolicy, DataChunk, DataEntry, InternalNode, Key, - KeyRef, LeafNode, NodeFindResult, ReadonlyPolicy, RecordData, TreeHeader, - VersionRecord, WritablePolicy, CHUNK_SIZE, HEADER_POINTER, LARGE_VALUE, + BTreeNode, BTreeNodeView, CreatablePolicy, DataChunk, DataChunkView, DataEntry, + DataEntryView, InternalNode, LeafNode, NodeFindResult, ReadonlyPolicy, RecordData, + RecordDataView, StaticKey, StaticKeyRef, TreeHeader, VecRef, VersionRecord, + WritablePolicy, CHUNK_SIZE, HEADER_POINTER, LARGE_VALUE, }; pub struct BTreeIndex(Policy); @@ -21,7 +22,7 @@ impl BTreeIndex { impl BTreeIndex { fn get_entry( &self, - key: KeyRef, + key: StaticKeyRef, table: &Arc, ) -> Result> { let mut ptr = self @@ -52,26 +53,23 @@ impl BTreeIndex { policy: &Policy, pointers: &[Pointer], table: &Arc, - ) -> Result> { + ) -> Result { let mut data = Vec::new(); for &ptr in pointers { - let chunk: DataChunk = policy - .fetch_slot(ptr, table)? - .for_read() - .as_ref() - .deserialize()?; + let slot = policy.fetch_slot(ptr, table)?.for_read(); + let chunk: DataChunkView = slot.as_ref().view()?; data.extend_from_slice(chunk.get_data()); } - Ok(data) + Ok(VecRef::copied(data)) } pub fn get( &self, - key: KeyRef, + key: StaticKeyRef, table: &Arc, - ) -> Result>>> { + ) -> Result>> { let (mut _guard, ptr) = match self.get_entry(key, table)? { Some(v) => v, None => return Ok(None), @@ -80,22 +78,18 @@ impl BTreeIndex { let mut next = Some(ptr); while let Some(ptr) = next.take() { let new_guard = pin(); - let entry: DataEntry = self - .0 - .fetch_slot(ptr, table)? - .for_read() - .as_ref() - .deserialize()?; + let slot = self.0.fetch_slot(ptr, table)?.for_read(); + let entry: DataEntryView = slot.as_ref().deserialize()?; if let Some(record) = entry.find(|&record| self.0.is_visible(record.owner, record.version)) { return Ok(Some(match &record.data { - RecordData::Data(data) => Some(data.to_vec()), - RecordData::Chunked(pointers) => { + RecordDataView::Data(s, e) => Some(VecRef::refed(&slot, *s, *e)), + RecordDataView::Chunked(pointers) => { Some(Self::read_chunk(&self.0, pointers, table)?) } - RecordData::Tombstone => None, + RecordDataView::Tombstone => None, })); } @@ -106,7 +100,7 @@ impl BTreeIndex { Ok(None) } - pub fn contains(&self, key: KeyRef, table: &Arc) -> Result { + pub fn contains(&self, key: StaticKeyRef, table: &Arc) -> Result { let (mut _guard, ptr) = match self.get_entry(key, table)? { Some(v) => v, None => return Ok(false), @@ -115,7 +109,7 @@ impl BTreeIndex { let mut next = Some(ptr); while let Some(ptr) = next.take() { let new_guard = pin(); - let entry: DataEntry = self + let entry: DataEntryView = self .0 .fetch_slot(ptr, table)? .for_read() @@ -126,8 +120,8 @@ impl BTreeIndex { entry.find(|&record| self.0.is_visible(record.owner, record.version)) { return Ok(match &record.data { - RecordData::Chunked(_) | RecordData::Data(_) => true, - RecordData::Tombstone => false, + RecordDataView::Chunked(_) | RecordDataView::Data(_, _) => true, + RecordDataView::Tombstone => false, }); }; @@ -140,7 +134,7 @@ impl BTreeIndex { fn find_leaf_stack( &self, - key: KeyRef, + key: StaticKeyRef, table: &Arc, ) -> Result<(Pointer, Vec)> { let (mut ptr, height) = { @@ -174,8 +168,8 @@ impl BTreeIndex { pub fn scan( &self, table: &Arc, - start: &Bound, - end: &Bound, + start: &Bound, + end: &Bound, ) -> Result> { BTreeIterator::open(&self.0, table, start, end) } @@ -197,11 +191,11 @@ impl BTreeIndex { fn apply_split( &self, - evicted_key: Key, + evicted_key: StaticKey, evicted_ptr: Pointer, current: Pointer, table: &Arc, - ) -> Result> { + ) -> Result> { let mut ptr = current; let (mut slot, mut internal) = loop { @@ -235,13 +229,13 @@ impl BTreeIndex { fn create_entry( &self, - key: KeyRef, + key: StaticKeyRef, pos: usize, slot: &mut WritableSlot, mut node: LeafNode, table: &Arc, create_record: F, - ) -> Result> + ) -> Result> where F: FnOnce() -> VersionRecord, { @@ -269,7 +263,7 @@ impl BTreeIndex { fn propagate_split( &self, - mut split_key: Key, + mut split_key: StaticKey, mut split_pointer: Pointer, mut stack: Vec, table: &Arc, @@ -374,7 +368,7 @@ impl BTreeIndex { let record = VersionRecord::new( snapshot.owner, snapshot.version, - self.create_record(snapshot.value, table)?, + self.create_record(snapshot.value.into_vec(), table)?, ); let (mut ptr, stack) = self.find_leaf_stack(&key, table)?; @@ -406,7 +400,7 @@ where { pub fn insert_record( &self, - key: Key, + key: StaticKey, record: RecordData, table: &Arc, ) -> Result { @@ -433,10 +427,15 @@ where self.propagate_split(mid_key, right_ptr, stack, table)?; Ok(()) } - pub fn insert(&self, key: Key, data: Vec, table: &Arc) -> Result { + pub fn insert( + &self, + key: StaticKey, + data: Vec, + table: &Arc, + ) -> Result { self.insert_record(key, self.create_record(data, table)?, table) } - pub fn remove(&self, key: KeyRef, table: &Arc) -> Result { + pub fn remove(&self, key: StaticKeyRef, table: &Arc) -> Result { self.insert_record_if_matched(key, RecordData::Tombstone, table) } @@ -482,7 +481,7 @@ where pub fn insert_if_matched( &self, - key: KeyRef, + key: StaticKeyRef, data: Vec, table: &Arc, ) -> Result { @@ -491,7 +490,7 @@ where fn insert_record_if_matched( &self, - key: KeyRef, + key: StaticKeyRef, record: RecordData, table: &Arc, ) -> Result { @@ -526,13 +525,13 @@ where } enum Buffered { - Data(Vec), + Data(VecRef), Chunked(Vec), } pub struct KVSnapshot { - key: Key, - value: Vec, + key: VecRef, + value: VecRef, owner: TxId, version: TxId, } @@ -540,9 +539,9 @@ pub struct KVSnapshot { pub struct BTreeIterator<'a, Policy> { policy: &'a Policy, table: Arc, - buffered: VecDeque<(Key, Option<(Buffered, TxId, TxId)>)>, + buffered: VecDeque<(VecRef, Option<(Buffered, TxId, TxId)>)>, next: Option, - end: Bound, + end: Bound, closed: bool, } impl<'a, Policy> BTreeIterator<'a, Policy> @@ -552,8 +551,8 @@ where pub fn open( policy: &'a Policy, table: &Arc, - start: &Bound, - end: &Bound, + start: &Bound, + end: &Bound, ) -> Result { let mut ptr = policy .fetch_slot(HEADER_POINTER, table)? @@ -595,11 +594,11 @@ where }; let mut count = 0; - for (k, p) in node.get_entries_while(end).skip(pos) { + for (s, e, p) in node.get_entries_while(end).skip(pos) { count += 1; if let Some(found) = Self::__find(policy, p, table)? { - buffered.push_back((k.to_vec(), found)); + buffered.push_back((VecRef::refed(&slot, s, e), found)); } } @@ -629,25 +628,24 @@ where let mut next = Some(ptr); while let Some(ptr) = next.take() { - let entry: DataEntry = policy - .fetch_slot(ptr, table)? - .for_read() - .as_ref() - .deserialize()?; + let slot = policy.fetch_slot(ptr, table)?.for_read(); + let entry: DataEntryView = slot.as_ref().deserialize()?; if let Some(record) = entry.find(|record| policy.is_visible(record.owner, record.version)) { return Ok(Some(match &record.data { - RecordData::Data(data) => { - Some((Buffered::Data(data.to_vec()), record.owner, record.version)) - } - RecordData::Chunked(pointers) => Some(( + RecordDataView::Data(s, e) => Some(( + Buffered::Data(VecRef::refed(&slot, *s, *e)), + record.owner, + record.version, + )), + RecordDataView::Chunked(pointers) => Some(( Buffered::Chunked(pointers.to_vec()), record.owner, record.version, )), - RecordData::Tombstone => None, + RecordDataView::Tombstone => None, })); } @@ -677,10 +675,10 @@ where let node = slot.as_ref().view::()?.as_leaf()?; let mut count = 0; - for (k, p) in node.get_entries_while(&self.end) { + for (s, e, p) in node.get_entries_while(&self.end) { count += 1; if let Some(found) = self.find_value(p)? { - self.buffered.push_back((k.to_vec(), found)) + self.buffered.push_back((VecRef::refed(&slot, s, e), found)) } } @@ -690,7 +688,7 @@ where Ok(()) } - fn next_record(&mut self) -> Result, TxId, TxId)>)>> { + fn next_record(&mut self) -> Result)>> { loop { if self.closed { return Ok(None); @@ -732,7 +730,7 @@ where } } - pub fn next_kv_skip_tombstone(&mut self) -> Result)>> { + pub fn next_kv_skip_tombstone(&mut self) -> Result> { loop { match self.next_record()? { Some((key, Some((value, _, _)))) => return Ok(Some((key, value))), @@ -742,7 +740,7 @@ where } } - pub fn next_kv(&mut self) -> Result>)>> { + pub fn next_kv(&mut self) -> Result)>> { match self.next_record()? { Some((k, v)) => Ok(Some((k, v.map(|v| v.0)))), None => Ok(None), diff --git a/src/cursor/cursor.rs b/src/cursor/cursor.rs index 49dd097..81e992b 100644 --- a/src/cursor/cursor.rs +++ b/src/cursor/cursor.rs @@ -4,7 +4,9 @@ use std::{ sync::Arc, }; -use super::{BTreeIndex, BTreeIterator, Key, RecordData, MAX_KEY, MAX_VALUE}; +use super::{ + BTreeIndex, BTreeIterator, RecordData, StaticKey, VecRef, MAX_KEY, MAX_VALUE, +}; use crate::{ metrics::MetricsRegistry, table::TableHandle, transaction::TxContext, Error, Result, }; @@ -46,7 +48,7 @@ impl<'a> Cursor<'a> { } } - pub fn get>(&self, key: &K) -> Result>> { + pub fn get>(&self, key: &K) -> Result> { if !self.context.is_available() { return Err(Error::TransactionClosed); } @@ -140,9 +142,9 @@ pub struct CursorIterator<'a> { table: BTreeIterator<'a, &'a TxContext<'a>>, compaction: Option<( BTreeIterator<'a, &'a TxContext<'a>>, - Option<(Vec, Option>)>, + Option<(VecRef, Option)>, )>, - buffered: Option<(Vec, Option>)>, + buffered: Option<(VecRef, Option)>, } impl<'a> CursorIterator<'a> { pub fn new( @@ -150,8 +152,8 @@ impl<'a> CursorIterator<'a> { table: &'a Arc, compaction: Option<&'a Arc>, index: &'a BTreeIndex<&'a TxContext<'a>>, - start: Bound, - end: Bound, + start: Bound, + end: Bound, ) -> Result { let compaction = match compaction { Some(table) => Some((index.scan(table, &start, &end)?, None)), @@ -165,7 +167,7 @@ impl<'a> CursorIterator<'a> { buffered: None, }) } - pub fn try_next(&mut self) -> Result)>> { + pub fn try_next(&mut self) -> Result> { if !self.context.is_available() { return Err(Error::TransactionClosed); } diff --git a/src/cursor/entry.rs b/src/cursor/entry.rs index fc6a69b..b45f792 100644 --- a/src/cursor/entry.rs +++ b/src/cursor/entry.rs @@ -84,7 +84,7 @@ impl DataEntry { } } pub fn init(version: VersionRecord) -> Self { - let mut versions = VecDeque::new(); + let mut versions = VecDeque::with_capacity(1); versions.push_front(version); Self { next: None, @@ -106,13 +106,6 @@ impl DataEntry { self.versions.iter() } - pub fn find

(&self, predicate: P) -> Option<&VersionRecord> - where - P: FnMut(&&VersionRecord) -> bool, - { - self.versions.iter().find(predicate) - } - pub fn get_last_owner(&self) -> Option { self.versions.front().map(|v| v.owner) } @@ -133,19 +126,6 @@ impl DataEntry { POINTER_BYTES + 2 + self.versions.iter().map(|v| v.byte_len()).sum::(); record.byte_len() + byte_len <= SERIALIZABLE_BYTES } - - pub fn is_empty(&self) -> bool { - if self.versions.is_empty() { - return true; - } - if self.versions.len() > 1 { - return false; - } - if let RecordData::Tombstone = self.versions[0].data { - return true; - } - false - } } impl TypedObject for DataEntry { fn get_type() -> SerializeType { @@ -183,7 +163,7 @@ impl Deserializable for DataEntry { fn read_from(reader: &mut crate::disk::PageScanner) -> crate::Result { let next = reader.read_u64()?; let len = reader.read_u16()? as usize; - let mut versions = VecDeque::with_capacity(len); + let mut versions = VecDeque::with_capacity(len + 1); for _ in 0..len { let version = reader.read_u64()?; let owner = reader.read_u64()?; @@ -219,10 +199,6 @@ impl DataChunk { pub const fn new(chunk: Vec) -> Self { Self { chunk } } - - pub fn get_data(&self) -> &[u8] { - &self.chunk - } } impl TypedObject for DataChunk { fn get_type() -> SerializeType { diff --git a/src/cursor/gc.rs b/src/cursor/gc.rs index 166629b..fb139db 100644 --- a/src/cursor/gc.rs +++ b/src/cursor/gc.rs @@ -7,7 +7,7 @@ use std::{ use crossbeam::epoch; -use super::{DataEntry, RecordData, VersionRecord}; +use super::{DataEntry, DataEntryView, RecordData, VersionRecord}; use crate::{ cache::{BlockCache, WritableSlot}, debug, @@ -304,7 +304,7 @@ const fn run_check( .read(pointer, table)? .for_read() .as_ref() - .deserialize::()? + .deserialize::()? .is_empty(), ) } diff --git a/src/cursor/internal.rs b/src/cursor/internal.rs index 6f454c8..d85b6f8 100644 --- a/src/cursor/internal.rs +++ b/src/cursor/internal.rs @@ -1,4 +1,4 @@ -use super::{Key, KeyRef}; +use super::{StaticKey, StaticKeyRef}; use crate::{ disk::{Page, PageScanner, PageWriter, Pointer, POINTER_BYTES}, serialize::SERIALIZABLE_BYTES, @@ -13,9 +13,9 @@ use crate::{ */ #[derive(Debug)] pub struct InternalNode { - keys: Vec, + keys: Vec, children: Vec, - right: Option<(Pointer, Key)>, + right: Option<(Pointer, StaticKey)>, } impl InternalNode { pub fn from_scanner(scanner: &mut PageScanner) -> Result { @@ -60,13 +60,13 @@ impl InternalNode { } Ok(()) } - pub fn initialize(key: Key, left: Pointer, right: Pointer) -> Self { + pub fn initialize(key: StaticKey, left: Pointer, right: Pointer) -> Self { Self::new(vec![key], vec![left, right], None) } pub const fn new( - keys: Vec, + keys: Vec, children: Vec, - right: Option<(Pointer, Key)>, + right: Option<(Pointer, StaticKey)>, ) -> Self { Self { keys, @@ -77,7 +77,7 @@ impl InternalNode { pub fn insert_or_next( &mut self, - key: &Key, + key: &StaticKey, pointer: Pointer, ) -> std::result::Result<(), Pointer> { if let Some((right, high)) = &self.right { @@ -109,7 +109,7 @@ impl InternalNode { + self.keys.len() * 2 } - pub fn split_if_needed(&mut self) -> Option<(InternalNode, Key)> { + pub fn split_if_needed(&mut self) -> Option<(InternalNode, StaticKey)> { let bytes_len = self.bytes_len(); if bytes_len <= SERIALIZABLE_BYTES { return None; @@ -133,7 +133,11 @@ impl InternalNode { )) } - pub fn set_right(&mut self, key: &Key, ptr: Pointer) -> Option<(Pointer, Key)> { + pub fn set_right( + &mut self, + key: &StaticKey, + ptr: Pointer, + ) -> Option<(Pointer, StaticKey)> { self.right.replace((ptr, key.clone())) } } @@ -182,7 +186,7 @@ impl<'a> InternalNodeView<'a> { right, } } - pub fn find(&self, key: KeyRef) -> std::result::Result { + pub fn find(&self, key: StaticKeyRef) -> std::result::Result { if let Some((right, s, e)) = &self.right { if self.page.range(*s..*e) <= key { return Err(*right); diff --git a/src/cursor/leaf.rs b/src/cursor/leaf.rs index 45a1f77..c491273 100644 --- a/src/cursor/leaf.rs +++ b/src/cursor/leaf.rs @@ -1,6 +1,6 @@ use std::ops::Bound; -use super::{Key, KeyRef}; +use super::{StaticKey, StaticKeyRef}; use crate::{ disk::{Page, PageScanner, PageWriter, Pointer, POINTER_BYTES}, serialize::SERIALIZABLE_BYTES, @@ -24,11 +24,11 @@ pub enum NodeFindResult { */ #[derive(Debug)] pub struct LeafNode { - entries: Vec<(Key, Pointer)>, + entries: Vec<(StaticKey, Pointer)>, next: Option, } impl LeafNode { - pub const fn new(entries: Vec<(Key, Pointer)>, next: Option) -> Self { + pub const fn new(entries: Vec<(StaticKey, Pointer)>, next: Option) -> Self { Self { entries, next } } @@ -60,11 +60,11 @@ impl LeafNode { self.entries.len() } - pub fn drain(&mut self) -> impl Iterator + '_ { + pub fn drain(&mut self) -> impl Iterator + '_ { self.entries.drain(..) } - pub fn set_entries(&mut self, entries: Vec<(Key, Pointer)>) { + pub fn set_entries(&mut self, entries: Vec<(StaticKey, Pointer)>) { self.entries = entries; } @@ -86,7 +86,7 @@ impl LeafNode { pub fn insert_and_split( &mut self, index: usize, - key: Key, + key: StaticKey, pointer: Pointer, ) -> Option { self.entries.insert(index, (key, pointer)); @@ -106,7 +106,7 @@ impl LeafNode { Some(Self::new(self.entries.split_off(mid), self.next.take())) } - pub fn top(&self) -> &Key { + pub fn top(&self) -> &StaticKey { &self.entries[0].0 } } @@ -143,13 +143,14 @@ impl<'a> LeafNodeView<'a> { } pub fn writable(self) -> LeafNode { - LeafNode { - entries: self.get_entries().map(|(k, p)| (k.to_vec(), p)).collect(), - next: self.next, + let mut entries = Vec::with_capacity(self.entries.len() + 1); + for (s, e, p) in self.entries { + entries.push((self.page.copy_range(s..e), p)) } + LeafNode::new(entries, self.next) } - pub fn find(&self, key: KeyRef) -> NodeFindResult { + pub fn find(&self, key: StaticKeyRef) -> NodeFindResult { match self.binary_search(key) { Ok(i) => NodeFindResult::Found(i, self.entries[i].2), Err(i) => { @@ -165,7 +166,7 @@ impl<'a> LeafNodeView<'a> { } #[inline] - fn binary_search(&self, key: KeyRef) -> std::result::Result { + fn binary_search(&self, key: StaticKeyRef) -> std::result::Result { self .entries .binary_search_by(|(s, e, _)| (self.page.range(*s..*e)).cmp(key)) @@ -173,17 +174,18 @@ impl<'a> LeafNodeView<'a> { pub fn get_entries_while<'b: 'a>( &self, - end: &'b Bound, - ) -> impl Iterator, Pointer)> + '_ { + end: &'b Bound, + ) -> impl Iterator + '_ { let e = match end { Bound::Included(k) => self.binary_search(k).map(|i| i + 1).unwrap_or_else(|i| i), Bound::Excluded(k) => self.binary_search(k).unwrap_or_else(|i| i), Bound::Unbounded => self.len(), }; - self.get_entries().take(e) + self.entries.iter().map(|(s, e, p)| (*s, *e, *p)).take(e) } - pub fn get_entries(&self) -> impl Iterator, Pointer)> + '_ { + #[allow(unused)] + pub fn get_entries(&self) -> impl Iterator, Pointer)> + '_ { self .entries .iter() diff --git a/src/cursor/mod.rs b/src/cursor/mod.rs index 9984fe8..9d6f336 100644 --- a/src/cursor/mod.rs +++ b/src/cursor/mod.rs @@ -7,6 +7,9 @@ use node::*; mod entry; use entry::*; +mod entry_view; +use entry_view::*; + mod cursor; pub use cursor::*; @@ -14,7 +17,7 @@ mod gc; pub use gc::*; mod types; -use types::*; +pub use types::*; mod tree_manager; pub use tree_manager::*; diff --git a/src/cursor/tests/entry.rs b/src/cursor/tests/entry.rs index 26afe5c..4617e90 100644 --- a/src/cursor/tests/entry.rs +++ b/src/cursor/tests/entry.rs @@ -13,7 +13,6 @@ fn test_entry_with_data_roundtrip() { page.serialize_from(&entry).expect("serialize error"); let decoded: DataEntry = page.deserialize().expect("deserialize error"); - assert!(!decoded.is_empty()); assert_eq!(decoded.get_last_owner(), Some(1)); let records: Vec<_> = decoded.get_versions().collect(); @@ -34,7 +33,6 @@ fn test_entry_with_tombstone_roundtrip() { page.serialize_from(&entry).expect("serialize error"); let decoded: DataEntry = page.deserialize().expect("deserialize error"); - assert!(decoded.is_empty()); assert_eq!(decoded.get_last_owner(), Some(2)); let records: Vec<_> = decoded.get_versions().collect(); @@ -92,7 +90,6 @@ fn test_entry_multiple_versions_roundtrip() { page.serialize_from(&entry).expect("serialize error"); let decoded: DataEntry = page.deserialize().expect("deserialize error"); - assert!(!decoded.is_empty()); assert_eq!(decoded.get_last_owner(), Some(1)); assert_eq!(decoded.get_next(), None); } diff --git a/src/cursor/tests/node.rs b/src/cursor/tests/node.rs index 98f5fc8..fffaa58 100644 --- a/src/cursor/tests/node.rs +++ b/src/cursor/tests/node.rs @@ -8,8 +8,7 @@ fn test_serialize_internal() { let children = vec![10]; let next = None; let mut page = Page::new(); - let node = - BTreeNode::Internal(InternalNode::new(keys.clone(), children.clone(), next)); + let node = BTreeNode::Internal(InternalNode::new(keys.clone(), children.clone(), next)); page.serialize_from(&node).expect("serialize error"); let d = match page.view::().expect("deserialize error") { diff --git a/src/disk/page.rs b/src/disk/page.rs index 2484bcc..1e8242f 100644 --- a/src/disk/page.rs +++ b/src/disk/page.rs @@ -43,9 +43,11 @@ impl Page { unsafe { copy_nonoverlapping(data.as_ptr(), self.0, len) }; } #[inline] - pub fn copy_n(&mut self, byte_len: usize) -> Vec { - let mut data = vec![0; byte_len]; - unsafe { copy_nonoverlapping(self.0, data.as_mut_ptr(), byte_len) }; + pub fn copy_range(&self, range: Range) -> Vec { + let len = range.end - range.start; + let mut data = Vec::with_capacity(len); + unsafe { copy_nonoverlapping(self.0.add(range.start), data.as_mut_ptr(), len) }; + unsafe { data.set_len(len) }; data } #[inline] diff --git a/src/disk/tests/page.rs b/src/disk/tests/page.rs index 0b8eb69..39a769d 100644 --- a/src/disk/tests/page.rs +++ b/src/disk/tests/page.rs @@ -184,7 +184,7 @@ fn test_page_copy() { writer.write(&test_data).unwrap(); // Create copy and verify data - let copied = page.copy_n(5); + let copied = page.copy_range(0..5); for (i, e) in copied.iter().enumerate() { assert_eq!(e, &test_data[i]); } diff --git a/src/lib.rs b/src/lib.rs index ffcd646..ead6638 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ mod builder; pub use builder::*; mod cursor; -pub use cursor::{Cursor, CursorIterator}; +pub use cursor::{Cursor, CursorIterator, VecRef}; mod error; pub use error::*; diff --git a/src/transaction/recorder.rs b/src/transaction/recorder.rs index 0971b39..cec79bc 100644 --- a/src/transaction/recorder.rs +++ b/src/transaction/recorder.rs @@ -40,7 +40,7 @@ impl PageRecorder { let byte_len = page.serialize_from(data)?; self .wal - .append_insert(tx_id, table_id, ptr, page.copy_n(byte_len)) + .append_insert(tx_id, table_id, ptr, page.copy_range(0..byte_len)) } pub fn log_multi( @@ -59,12 +59,12 @@ impl PageRecorder { let ptr1 = slot1.get_pointer(); let page = slot1.as_mut(); let byte_len = page.serialize_from(data1)?; - let data1 = page.copy_n(byte_len); + let data1 = page.copy_range(0..byte_len); let ptr2 = slot2.get_pointer(); let page = slot2.as_mut(); let byte_len = page.serialize_from(data2)?; - let data2 = page.copy_n(byte_len); + let data2 = page.copy_range(0..byte_len); self .wal diff --git a/tests/e2e.rs b/tests/e2e.rs index 4a4c81f..b4dbbc5 100644 --- a/tests/e2e.rs +++ b/tests/e2e.rs @@ -10,7 +10,7 @@ use std::{ }; use crossbeam::channel::{unbounded, Sender}; -use lfdb::{Engine, EngineBuilder, Error}; +use lfdb::{Engine, EngineBuilder, Error, VecRef}; use log::Log; use rand::rngs::StdRng; use rand::{rng, seq::IteratorRandom}; @@ -70,7 +70,7 @@ fn test_basic_crud() { // read within same tx let val = table.get(&b"key1".to_vec()).unwrap(); - assert_eq!(val, Some(b"value1".to_vec())); + assert_eq!(val.as_deref(), Some(b"value1".as_slice())); // remove table.remove(&b"key1".to_vec()).unwrap(); @@ -102,7 +102,7 @@ fn test_commit_visibility() { .unwrap() .get(&b"hello".to_vec()) .unwrap(); - assert_eq!(val, Some(b"world".to_vec())); + assert_eq!(val.as_deref(), Some(b"world".as_slice())); } // ============================================================ @@ -263,8 +263,10 @@ fn test_scan_range() { results.push((k, v)); } assert_eq!(results.len(), 4); // keys 3,4,5,6 - assert_eq!(results[0], (vec![3], vec![30])); - assert_eq!(results[3], (vec![6], vec![60])); + assert_eq!(results[0].0, vec![3]); + assert_eq!(results[0].1, vec![30]); + assert_eq!(results[3].0, vec![6]); + assert_eq!(results[3].1, vec![60]); } #[test] @@ -289,7 +291,8 @@ fn test_scan_all() { } assert_eq!(results.len(), 5); for i in 0u8..5 { - assert_eq!(results[i as usize], (vec![i], vec![i])); + assert_eq!(results[i as usize].0, vec![i]); + assert_eq!(results[i as usize].1, vec![i]); } } @@ -315,7 +318,7 @@ fn test_overwrite() { let tx3 = engine.new_tx().unwrap(); let t3 = tx3.table(TEST_TABLE).unwrap(); let val = t3.get(&b"key".to_vec()).unwrap(); - assert_eq!(val, Some(b"v2".to_vec())); + assert_eq!(val.as_deref(), Some(b"v2".as_slice())); } // ============================================================ @@ -428,7 +431,7 @@ fn test_snapshot_isolation() { let tx3 = engine.new_tx().unwrap(); let t3 = tx3.table(TEST_TABLE).unwrap(); let val = t3.get(&b"after".to_vec()).unwrap(); - assert_eq!(val, Some(b"should-not-see".to_vec())); + assert_eq!(val.as_deref(), Some(b"should-not-see".as_slice())); } // ============================================================ @@ -516,7 +519,7 @@ fn test_btree_node_split_and_recovery() { let table = tx.table(TEST_TABLE).unwrap(); let mut iter = table.scan::<[_]>(..).unwrap(); let mut count = 0; - let mut prev_key: Option> = None; + let mut prev_key: Option = None; while let Some((k, v)) = iter.try_next().unwrap() { // keys should be in sorted order if let Some(ref pk) = prev_key { @@ -563,7 +566,7 @@ fn test_btree_node_split_and_recovery() { let mut iter = table.scan::<[_]>(..).unwrap(); let mut count = 0; - let mut prev_key: Option> = None; + let mut prev_key: Option = None; while let Some((k, v)) = iter.try_next().unwrap() { if let Some(ref pk) = prev_key { assert!(k > *pk, "keys not sorted after recovery"); @@ -725,8 +728,8 @@ fn test_process_crash_recovery() { let expected = format!("val-{:06}", key_idx).into_bytes(); let val = table.get(&key).unwrap(); assert_eq!( - val, - Some(expected), + val.as_deref(), + Some(expected.as_slice()), "committed key {} in {} missing after crash recovery", key_idx, table_name @@ -804,7 +807,7 @@ fn test_hard_workload() { let tx = engine.new_tx().unwrap(); for (table, key) in &keys { let table = tx.table(table).unwrap(); - assert_eq!(table.get(key).unwrap(), Some(key.to_vec())) + assert_eq!(table.get(key).unwrap().as_deref(), Some(key.as_slice())) } // verify each table @@ -893,7 +896,7 @@ fn test_heavy_gc_single_key() { let val = table.get(&key).unwrap(); assert!(val.is_some(), "key should exist after heavy writes"); let bytes = val.unwrap(); - let stored = u32::from_le_bytes(bytes.try_into().unwrap()); + let stored = u32::from_le_bytes(bytes.as_ref().try_into().unwrap()); assert_eq!(stored, final_val, "final value mismatch"); } } @@ -911,7 +914,7 @@ fn test_heavy_gc_single_key() { let val = table.get(&key).unwrap(); assert!(val.is_some(), "key should survive restart"); let bytes = val.unwrap(); - let stored = u32::from_le_bytes(bytes.try_into().unwrap()); + let stored = u32::from_le_bytes(bytes.as_ref().try_into().unwrap()); assert_eq!(stored, final_val, "value mismatch after restart"); } @@ -1164,7 +1167,10 @@ fn test_large_key_value_gc() { let mut tx = engine.new_tx().unwrap(); let table = tx.table(TEST_TABLE).unwrap(); for i in (count / 2)..count { - assert_eq!(table.get(&keys[i]).unwrap(), Some(values[i].clone())); + assert_eq!( + table.get(&keys[i]).unwrap().as_deref(), + Some(values[i].as_slice()) + ); } tx.commit().unwrap(); } @@ -1275,7 +1281,7 @@ fn test_drop_then_abort_keeps_table() { // data must remain intact let tx = engine.new_tx().unwrap(); let t = tx.table(TEST_TABLE).unwrap(); - assert_eq!(t.get(&b"k".to_vec()).unwrap(), Some(b"v".to_vec())); + assert_eq!(t.get(b"k").unwrap().as_deref(), Some(b"v".as_slice())); } // ============================================================ @@ -1335,8 +1341,8 @@ fn test_reopen_after_drop_starts_fresh() { let tx = engine.new_tx().unwrap(); let t = tx.table(TEST_TABLE).unwrap(); - assert_eq!(t.get(&b"k".to_vec()).unwrap(), None); - assert_eq!(t.get(&b"k2".to_vec()).unwrap(), Some(b"v2".to_vec())); + assert_eq!(t.get(b"k").unwrap(), None); + assert_eq!(t.get(b"k2").unwrap().as_deref(), Some(b"v2".as_slice())); } // ============================================================ @@ -1426,8 +1432,8 @@ fn test_drop_and_recreate_many_tables() { let t = tx.table(name).unwrap(); for (k, v) in kvs.iter() { assert_eq!( - t.get(k).unwrap(), - Some(v.clone()), + t.get(k).unwrap().as_deref(), + Some(v.as_slice()), "table {} key {k:?} should hold the second-generation value", name, ); @@ -1548,10 +1554,16 @@ fn test_compaction() { let table = tx.table(TEST_TABLE).unwrap(); for i in 0..remove_count { - assert_eq!(table.get(&keys[i]).unwrap(), Some(new_values[i].clone())); + assert_eq!( + table.get(&keys[i]).unwrap().as_deref(), + Some(new_values[i].as_slice()) + ); } for i in remove_count..count { - assert_eq!(table.get(&keys[i]).unwrap(), Some(values[i].clone())); + assert_eq!( + table.get(&keys[i]).unwrap().as_deref(), + Some(values[i].as_slice()) + ); } } } @@ -1681,7 +1693,7 @@ fn test_auto_compaction() { let table = tx.table(name).unwrap(); for (k, v) in data.iter() { - assert_eq!(table.get(k).unwrap(), Some(v.clone())) + assert_eq!(table.get(k).unwrap().as_deref(), Some(v.as_slice())) } } } @@ -1697,7 +1709,11 @@ fn test_auto_compaction() { let mut iter = table.scan::<[_]>(..).unwrap(); while let Some((k, v)) = iter.try_next().unwrap() { - assert_eq!(data.get(&k), Some(&v), "table {name} key {k:?} not matched"); + assert_eq!( + data.get(&k as &[_]).map(|v| v as &[_]), + Some(&v as &[_]), + "table {name} key {k:?} not matched" + ); c += 1 } From d099ba33edbe54050f72a588e1a3e2154dcfc0c8 Mon Sep 17 00:00:00 2001 From: qwp0905 Date: Fri, 8 May 2026 15:07:55 +0900 Subject: [PATCH 5/5] fix typo --- src/disk/page.rs | 6 +++--- src/wal/buffer.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/disk/page.rs b/src/disk/page.rs index 1e8242f..8d6d7b0 100644 --- a/src/disk/page.rs +++ b/src/disk/page.rs @@ -33,8 +33,8 @@ impl Page { Self(unsafe { alloc_zeroed(Self::LAYOUT) }, PhantomData) } #[inline(always)] - pub const fn as_ptr(&self) -> *const u8 { - self.0 as *const u8 + pub const fn as_ptr(&self) -> *mut u8 { + self.0 } #[inline] pub fn copy_from>(&mut self, data: V) { @@ -88,7 +88,7 @@ impl From<&[u8]> for Page { fn from(value: &[u8]) -> Self { let page = Self::new(); let len = value.len().min(T); - unsafe { copy_nonoverlapping(value.as_ptr(), page.as_ptr() as *mut u8, len) }; + unsafe { copy_nonoverlapping(value.as_ptr(), page.as_ptr(), len) }; page } } diff --git a/src/wal/buffer.rs b/src/wal/buffer.rs index 1293b6f..a8985a3 100644 --- a/src/wal/buffer.rs +++ b/src/wal/buffer.rs @@ -139,7 +139,7 @@ impl LogBuffer { self.write_at(&((count & U16_MASK) as u16).to_le_bytes(), 0) } pub fn write_at(&self, record: &[u8], offset: usize) { - let ptr = self.entry.as_ref().as_ptr() as *mut u8; + let ptr = self.entry.as_ptr(); let len = record.len(); unsafe { copy_nonoverlapping(record.as_ptr(), ptr.add(offset), len) }; }