Skip to content

Commit 40849ef

Browse files
committed
refactor: propagate iter changes from libmdbx
1 parent f0e78c4 commit 40849ef

File tree

7 files changed

+228
-10
lines changed

7 files changed

+228
-10
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ signet-storage = { version = "0.0.1", path = "./crates/storage" }
4242
signet-storage-types = { version = "0.0.1", path = "./crates/types" }
4343

4444
# External, in-house
45-
signet-libmdbx = { version = "0.7.0" }
45+
signet-libmdbx = { version = "0.8.0" }
4646

4747
signet-zenith = "0.16.0-rc.5"
4848

crates/hot-mdbx/src/cursor.rs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use crate::{FixedSizeInfo, MdbxError};
44
use signet_hot::{
55
MAX_FIXED_VAL_SIZE, MAX_KEY_SIZE,
66
model::{
7-
DualKeyTraverse, DualKeyTraverseMut, KvTraverse, KvTraverseMut, RawDualKeyValue,
8-
RawKeyValue, RawValue,
7+
DualKeyItem, DualKeyTraverse, DualKeyTraverseMut, KvTraverse, KvTraverseMut,
8+
RawDualKeyItem, RawDualKeyValue, RawKeyValue, RawValue,
99
},
1010
};
11-
use signet_libmdbx::{Ro, Rw, RwSync, TransactionKind, tx::WriteMarker};
11+
use signet_libmdbx::{DupItem, Ro, Rw, RwSync, TransactionKind, tx::WriteMarker};
1212
use std::{
1313
borrow::Cow,
1414
ops::{Deref, DerefMut},
@@ -504,6 +504,61 @@ where
504504
None => Ok(None),
505505
}
506506
}
507+
508+
fn iter_items(
509+
&mut self,
510+
) -> Result<impl Iterator<Item = Result<RawDualKeyItem<'_>, MdbxError>> + '_, MdbxError> {
511+
if !self.fsi.is_dupsort() {
512+
return Err(MdbxError::NotDupSort);
513+
}
514+
515+
let key2_size = self.fsi.key2_size().ok_or(MdbxError::UnknownFixedSize)?;
516+
517+
// Use iter_dup_start which yields DupItem::NewKey/SameKey
518+
let iter_dup = self.inner.iter_dup_start::<Cow<'_, [u8]>, Cow<'_, [u8]>>()?;
519+
Ok(MdbxDualKeyItemIter { iter_dup, key2_size })
520+
}
521+
}
522+
523+
/// Iterator adapter that converts mdbx's [`DupItem`] to [`DualKeyItem`].
524+
///
525+
/// This iterator returns owned data to avoid lifetime complexities between
526+
/// the transaction lifetime and the iterator borrow lifetime.
527+
struct MdbxDualKeyItemIter<'tx, 'cur, K: TransactionKind> {
528+
iter_dup: signet_libmdbx::tx::iter::IterDup<'tx, 'cur, K, Cow<'tx, [u8]>, Cow<'tx, [u8]>>,
529+
key2_size: usize,
530+
}
531+
532+
impl<'a, K: TransactionKind> Iterator for MdbxDualKeyItemIter<'_, 'a, K> {
533+
type Item = Result<RawDualKeyItem<'a>, MdbxError>;
534+
535+
fn next(&mut self) -> Option<Self::Item> {
536+
match self.iter_dup.borrow_next() {
537+
Ok(Some(item)) => {
538+
let result = match item {
539+
DupItem::NewKey(k1, v) => {
540+
let (k2, val) = split_cow_at_owned(v, self.key2_size);
541+
DualKeyItem::NewK1(Cow::Owned(k1.into_owned()), k2, val)
542+
}
543+
DupItem::SameKey(v) => {
544+
let (k2, val) = split_cow_at_owned(v, self.key2_size);
545+
DualKeyItem::SameK1(k2, val)
546+
}
547+
};
548+
Some(Ok(result))
549+
}
550+
Ok(None) => None,
551+
Err(e) => Some(Err(MdbxError::from(e))),
552+
}
553+
}
554+
}
555+
556+
/// Splits a [`Cow`] at the given index and returns owned [`Cow`]s.
557+
#[inline]
558+
fn split_cow_at_owned(cow: Cow<'_, [u8]>, at: usize) -> (Cow<'static, [u8]>, Cow<'static, [u8]>) {
559+
let vec = cow.into_owned();
560+
let (left, right) = vec.split_at(at);
561+
(Cow::Owned(left.to_vec()), Cow::Owned(right.to_vec()))
507562
}
508563

509564
impl<K: TransactionKind + WriteMarker> DualKeyTraverseMut<MdbxError> for Cursor<'_, K> {

crates/hot/src/mem.rs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
66
use crate::{
77
model::{
8-
DualKeyTraverse, DualKeyTraverseMut, HotKv, HotKvError, HotKvRead, HotKvReadError,
9-
HotKvWrite, KvTraverse, KvTraverseMut, RawDualKeyValue, RawKeyValue, RawValue,
8+
DualKeyItem, DualKeyTraverse, DualKeyTraverseMut, HotKv, HotKvError, HotKvRead,
9+
HotKvReadError, HotKvWrite, KvTraverse, KvTraverseMut, RawDualKeyItem, RawDualKeyValue,
10+
RawKeyValue, RawValue,
1011
},
1112
ser::{DeserError, MAX_KEY_SIZE},
1213
};
@@ -514,6 +515,47 @@ impl<'a> DualKeyTraverse<MemKvError> for MemKvCursor<'a> {
514515
self.set_current_key(*found_key);
515516
Ok(Some((found_k1, found_k2, Cow::Borrowed(value.as_ref()))))
516517
}
518+
519+
fn iter_items(
520+
&mut self,
521+
) -> Result<impl Iterator<Item = Result<RawDualKeyItem<'_>, MemKvError>> + '_, MemKvError> {
522+
DualKeyTraverse::first(self)?;
523+
Ok(MemDualKeyItemIter { cursor: self, prev_k1: None })
524+
}
525+
}
526+
527+
/// Iterator that yields [`DualKeyItem`] for read-only memory cursors.
528+
struct MemDualKeyItemIter<'a, 'b> {
529+
cursor: &'a mut MemKvCursor<'b>,
530+
prev_k1: Option<Vec<u8>>,
531+
}
532+
533+
impl<'a> Iterator for MemDualKeyItemIter<'a, '_> {
534+
type Item = Result<RawDualKeyItem<'a>, MemKvError>;
535+
536+
fn next(&mut self) -> Option<Self::Item> {
537+
let result = DualKeyTraverse::read_next(self.cursor);
538+
match result {
539+
Ok(Some((k1, k2, v))) => {
540+
let k1_vec = k1.as_ref().to_vec();
541+
let is_new_k1 = self.prev_k1.as_ref() != Some(&k1_vec);
542+
self.prev_k1 = Some(k1_vec);
543+
544+
let item = if is_new_k1 {
545+
DualKeyItem::NewK1(
546+
Cow::Owned(k1.into_owned()),
547+
Cow::Owned(k2.into_owned()),
548+
Cow::Owned(v.into_owned()),
549+
)
550+
} else {
551+
DualKeyItem::SameK1(Cow::Owned(k2.into_owned()), Cow::Owned(v.into_owned()))
552+
};
553+
Some(Ok(item))
554+
}
555+
Ok(None) => None,
556+
Err(e) => Some(Err(e)),
557+
}
558+
}
517559
}
518560

519561
/// Memory cursor for read-write operations
@@ -1037,6 +1079,47 @@ impl<'a> DualKeyTraverse<MemKvError> for MemKvCursorMut<'a> {
10371079
Cow::Owned(value.to_vec()),
10381080
)))
10391081
}
1082+
1083+
fn iter_items(
1084+
&mut self,
1085+
) -> Result<impl Iterator<Item = Result<RawDualKeyItem<'_>, MemKvError>> + '_, MemKvError> {
1086+
DualKeyTraverse::first(self)?;
1087+
Ok(MemDualKeyItemIterMut { cursor: self, prev_k1: None })
1088+
}
1089+
}
1090+
1091+
/// Iterator that yields [`DualKeyItem`] for read-write memory cursors.
1092+
struct MemDualKeyItemIterMut<'a, 'b> {
1093+
cursor: &'a mut MemKvCursorMut<'b>,
1094+
prev_k1: Option<Vec<u8>>,
1095+
}
1096+
1097+
impl<'a> Iterator for MemDualKeyItemIterMut<'a, '_> {
1098+
type Item = Result<RawDualKeyItem<'a>, MemKvError>;
1099+
1100+
fn next(&mut self) -> Option<Self::Item> {
1101+
let result = DualKeyTraverse::read_next(self.cursor);
1102+
match result {
1103+
Ok(Some((k1, k2, v))) => {
1104+
let k1_vec = k1.as_ref().to_vec();
1105+
let is_new_k1 = self.prev_k1.as_ref() != Some(&k1_vec);
1106+
self.prev_k1 = Some(k1_vec);
1107+
1108+
let item = if is_new_k1 {
1109+
DualKeyItem::NewK1(
1110+
Cow::Owned(k1.into_owned()),
1111+
Cow::Owned(k2.into_owned()),
1112+
Cow::Owned(v.into_owned()),
1113+
)
1114+
} else {
1115+
DualKeyItem::SameK1(Cow::Owned(k2.into_owned()), Cow::Owned(v.into_owned()))
1116+
};
1117+
Some(Ok(item))
1118+
}
1119+
Ok(None) => None,
1120+
Err(e) => Some(Err(e)),
1121+
}
1122+
}
10401123
}
10411124

10421125
impl DualKeyTraverseMut<MemKvError> for MemKvCursorMut<'_> {

crates/hot/src/model/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ pub use traits::{HotKv, HotKvRead, HotKvWrite};
4545

4646
mod traverse;
4747
pub use traverse::{
48-
DualKeyTraverse, DualKeyTraverseMut, DualTableCursor, DualTableTraverse, DualTableTraverseMut,
49-
K2Value, KvTraverse, KvTraverseMut, RawK2Value, TableCursor, TableTraverse, TableTraverseMut,
48+
DualKeyItem, DualKeyTraverse, DualKeyTraverseMut, DualTableCursor, DualTableTraverse,
49+
DualTableTraverseMut, K2Value, KvTraverse, KvTraverseMut, RawDualKeyItem, RawK2Value,
50+
TableCursor, TableTraverse, TableTraverseMut,
5051
};
5152

5253
use crate::tables::{DualKey, Table};

crates/hot/src/model/traverse.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,72 @@ pub type RawK2Value<'a> = (Cow<'a, [u8]>, RawValue<'a>);
1818
/// Typed k2-value pair for a dual-keyed table.
1919
pub type K2Value<T> = (<T as DualKey>::Key2, <T as crate::tables::Table>::Value);
2020

21+
/// An item from a dual-key iterator.
22+
///
23+
/// This enum avoids cloning k1 for every value when iterating
24+
/// over dual-keyed tables. K1 is only provided when it changes.
25+
#[derive(Debug, Clone, PartialEq, Eq)]
26+
pub enum DualKeyItem<K1, K2, V> {
27+
/// First entry for a new k1.
28+
NewK1(K1, K2, V),
29+
/// Additional k2/value for the current k1.
30+
SameK1(K2, V),
31+
}
32+
33+
impl<K1, K2, V> DualKeyItem<K1, K2, V> {
34+
/// Returns the value, consuming self.
35+
pub fn into_value(self) -> V {
36+
match self {
37+
Self::NewK1(_, _, v) | Self::SameK1(_, v) => v,
38+
}
39+
}
40+
41+
/// Returns a reference to the value.
42+
pub const fn value(&self) -> &V {
43+
match self {
44+
Self::NewK1(_, _, v) | Self::SameK1(_, v) => v,
45+
}
46+
}
47+
48+
/// Returns the k2, consuming self.
49+
pub fn into_k2(self) -> K2 {
50+
match self {
51+
Self::NewK1(_, k2, _) | Self::SameK1(k2, _) => k2,
52+
}
53+
}
54+
55+
/// Returns a reference to k2.
56+
pub const fn k2(&self) -> &K2 {
57+
match self {
58+
Self::NewK1(_, k2, _) | Self::SameK1(k2, _) => k2,
59+
}
60+
}
61+
62+
/// Returns k1 if this is a NewK1 entry.
63+
pub const fn k1(&self) -> Option<&K1> {
64+
match self {
65+
Self::NewK1(k1, _, _) => Some(k1),
66+
Self::SameK1(_, _) => None,
67+
}
68+
}
69+
70+
/// Returns true if this item represents a new k1.
71+
pub const fn is_new_k1(&self) -> bool {
72+
matches!(self, Self::NewK1(..))
73+
}
74+
75+
/// Convert to a tuple, requiring k1 to be provided for SameK1 variants.
76+
pub fn into_tuple(self, current_k1: K1) -> (K1, K2, V) {
77+
match self {
78+
Self::NewK1(k1, k2, v) => (k1, k2, v),
79+
Self::SameK1(k2, v) => (current_k1, k2, v),
80+
}
81+
}
82+
}
83+
84+
/// Raw dual-key item: (k1?, k2, value) where k1 is only present on NewK1.
85+
pub type RawDualKeyItem<'a> = DualKeyItem<Cow<'a, [u8]>, Cow<'a, [u8]>, Cow<'a, [u8]>>;
86+
2187
/// Trait for traversing key-value pairs in the database.
2288
pub trait KvTraverse<E: HotKvReadError> {
2389
/// Set position to the first key-value pair in the database, and return
@@ -246,6 +312,19 @@ pub trait DualKeyTraverse<E: HotKvReadError> {
246312
let done = found_k1.as_ref() != k1;
247313
Ok(RawDualKeyK2Iter { cursor: self, done, _marker: PhantomData })
248314
}
315+
316+
/// Position at first entry and return iterator yielding [`DualKeyItem`].
317+
///
318+
/// This is more efficient than [`Self::iter()`] as it avoids cloning k1 for
319+
/// every entry within the same k1 group. The iterator yields
320+
/// [`DualKeyItem::NewK1`] when k1 changes and [`DualKeyItem::SameK1`] for
321+
/// subsequent entries with the same k1.
322+
///
323+
/// Backends implement this natively to leverage efficient "new key"
324+
/// notifications from the underlying database.
325+
fn iter_items(&mut self) -> Result<impl Iterator<Item = Result<RawDualKeyItem<'_>, E>> + '_, E>
326+
where
327+
Self: Sized;
249328
}
250329

251330
/// Trait for traversing dual-keyed key-value pairs with mutation capabilities.

crates/hot/src/ser/traits.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub const MAX_FIXED_VAL_SIZE: usize = 64;
1717
///
1818
/// In practice, keys are often hashes, addresses, numbers, or composites
1919
/// of these.
20-
pub trait KeySer: PartialOrd + Ord + Sized + Clone + core::fmt::Debug {
20+
pub trait KeySer: PartialOrd + Ord + Sized + Clone + Copy + core::fmt::Debug {
2121
/// The fixed size of the serialized key in bytes.
2222
/// Must satisfy `SIZE <= MAX_KEY_SIZE`.
2323
const SIZE: usize;

crates/types/src/sharded.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/// `Address | 200` -> data is from block 0 to 200.
55
///
66
/// `Address | 300` -> data is from block 201 to 300.
7-
#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
7+
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
88
pub struct ShardedKey<T> {
99
/// The key for this type.
1010
pub key: T,

0 commit comments

Comments
 (0)