1use super::utils::*;
4use crate::{
5 metrics::{Operation, TableOperationMetrics},
6 DatabaseError,
7};
8use reth_db_api::{
9 common::{PairResult, ValueOnlyResult},
10 cursor::{
11 DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW, DupWalker, RangeWalker,
12 ReverseWalker, Walker,
13 },
14 table::{Compress, Decode, Decompress, DupSort, Encode, IntoVec, Table},
15};
16use reth_libmdbx::{Error as MDBXError, TransactionKind, WriteFlags, RO, RW};
17use reth_storage_errors::db::{DatabaseErrorInfo, DatabaseWriteError, DatabaseWriteOperation};
18use std::{borrow::Cow, collections::Bound, marker::PhantomData, ops::RangeBounds};
19
20pub type CursorRO<T> = Cursor<RO, T>;
22pub type CursorRW<T> = Cursor<RW, T>;
24
25#[derive(Debug)]
27pub struct Cursor<K: TransactionKind, T: Table> {
28 pub(crate) inner: reth_libmdbx::Cursor<K>,
30 buf: Vec<u8>,
32 metrics: Option<TableOperationMetrics>,
34 _dbi: PhantomData<T>,
36 #[cfg(target_os = "linux")]
38 prefetch: bool,
39}
40
41impl<K: TransactionKind, T: Table> Cursor<K, T> {
42 pub(crate) const fn new_with_metrics(
43 inner: reth_libmdbx::Cursor<K>,
44 metrics: Option<TableOperationMetrics>,
45 #[cfg(target_os = "linux")] prefetch: bool,
46 ) -> Self {
47 Self {
48 inner,
49 buf: Vec::new(),
50 metrics,
51 _dbi: PhantomData,
52 #[cfg(target_os = "linux")]
53 prefetch,
54 }
55 }
56
57 fn execute_with_operation_metric<R>(
62 &mut self,
63 operation: Operation,
64 value_size: Option<usize>,
65 f: impl FnOnce(&mut Self) -> R,
66 ) -> R {
67 if let Some(metrics) = self.metrics.clone() {
68 metrics[operation.index()].record(value_size, || f(self))
69 } else {
70 f(self)
71 }
72 }
73}
74
75#[expect(clippy::type_complexity)]
77pub fn decode<T>(
78 res: Result<Option<(Cow<'_, [u8]>, Cow<'_, [u8]>)>, impl Into<DatabaseErrorInfo>>,
79) -> PairResult<T>
80where
81 T: Table,
82 T::Key: Decode,
83 T::Value: Decompress,
84{
85 res.map_err(|e| DatabaseError::Read(e.into()))?.map(decoder::<T>).transpose()
86}
87
88macro_rules! compress_to_buf_or_ref {
91 ($self:expr, $value:expr) => {
92 if let Some(value) = $value.uncompressable_ref() {
93 Some(value)
94 } else {
95 $self.buf.clear();
96 $value.compress_to_buf(&mut $self.buf);
97 None
98 }
99 };
100}
101
102macro_rules! read_pair {
104 ($self:ident, $method:ident($($arg:expr),* $(,)?)) => {{
105 #[cfg(target_os = "linux")]
106 if $self.prefetch {
107 $self.inner.$method::<Cow<'_, [u8]>, super::value_prefetch::PrefetchValue<'_>>($($arg),*)
108 .map(|entry| entry.map(|(key, value)| (key, value.0)))
109 } else {
110 $self.inner.$method($($arg),*)
111 }
112 #[cfg(not(target_os = "linux"))]
113 $self.inner.$method($($arg),*)
114 }};
115}
116
117macro_rules! read_value {
119 ($self:ident, $method:ident($($arg:expr),* $(,)?)) => {{
120 #[cfg(target_os = "linux")]
121 if $self.prefetch {
122 $self.inner.$method::<super::value_prefetch::PrefetchValue<'_>>($($arg),*)
123 .map(|entry| entry.map(|value| value.0))
124 } else {
125 $self.inner.$method($($arg),*)
126 }
127 #[cfg(not(target_os = "linux"))]
128 $self.inner.$method($($arg),*)
129 }};
130}
131
132impl<K: TransactionKind, T: Table> DbCursorRO<T> for Cursor<K, T> {
133 fn first(&mut self) -> PairResult<T> {
134 decode::<T>(read_pair!(self, first()))
135 }
136
137 fn seek_exact(&mut self, key: <T as Table>::Key) -> PairResult<T> {
138 decode::<T>(read_pair!(self, set_key(key.encode().as_ref())))
139 }
140
141 fn seek(&mut self, key: <T as Table>::Key) -> PairResult<T> {
142 decode::<T>(read_pair!(self, set_range(key.encode().as_ref())))
143 }
144
145 fn next(&mut self) -> PairResult<T> {
146 decode::<T>(read_pair!(self, next()))
147 }
148
149 fn prev(&mut self) -> PairResult<T> {
150 decode::<T>(read_pair!(self, prev()))
151 }
152
153 fn last(&mut self) -> PairResult<T> {
154 decode::<T>(read_pair!(self, last()))
155 }
156
157 fn current(&mut self) -> PairResult<T> {
158 decode::<T>(read_pair!(self, get_current()))
159 }
160
161 fn walk(&mut self, start_key: Option<T::Key>) -> Result<Walker<'_, T, Self>, DatabaseError> {
162 let start = if let Some(start_key) = start_key {
163 self.seek(start_key).transpose()
164 } else {
165 self.first().transpose()
166 };
167
168 Ok(Walker::new(self, start))
169 }
170
171 fn walk_range(
172 &mut self,
173 range: impl RangeBounds<T::Key>,
174 ) -> Result<RangeWalker<'_, T, Self>, DatabaseError> {
175 let start = match range.start_bound().cloned() {
176 Bound::Included(key) => self.seek(key),
177 Bound::Excluded(_key) => {
178 unreachable!("Rust doesn't allow for Bound::Excluded in starting bounds");
179 }
180 Bound::Unbounded => self.first(),
181 };
182 let start = start.transpose();
183 Ok(RangeWalker::new(self, start, range.end_bound().cloned()))
184 }
185
186 fn walk_back(
187 &mut self,
188 start_key: Option<T::Key>,
189 ) -> Result<ReverseWalker<'_, T, Self>, DatabaseError> {
190 let start =
191 if let Some(start_key) = start_key { self.seek(start_key) } else { self.last() }
192 .transpose();
193
194 Ok(ReverseWalker::new(self, start))
195 }
196
197 #[cfg(target_os = "linux")]
198 fn prefetch(&mut self, enabled: bool) -> &mut Self {
199 self.prefetch = enabled;
200 self
201 }
202}
203
204impl<K: TransactionKind, T: DupSort> DbDupCursorRO<T> for Cursor<K, T> {
205 fn prev_dup(&mut self) -> PairResult<T> {
207 decode::<T>(read_pair!(self, prev_dup()))
208 }
209
210 fn next_dup(&mut self) -> PairResult<T> {
212 decode::<T>(read_pair!(self, next_dup()))
213 }
214
215 fn last_dup(&mut self) -> ValueOnlyResult<T> {
217 read_value!(self, last_dup())
218 .map_err(|e| DatabaseError::Read(e.into()))?
219 .map(decode_one::<T>)
220 .transpose()
221 }
222
223 fn next_no_dup(&mut self) -> PairResult<T> {
225 decode::<T>(read_pair!(self, next_nodup()))
226 }
227
228 fn next_dup_val(&mut self) -> ValueOnlyResult<T> {
230 read_pair!(self, next_dup())
231 .map_err(|e| DatabaseError::Read(e.into()))?
232 .map(decode_value::<T>)
233 .transpose()
234 }
235
236 fn seek_by_key_subkey(
237 &mut self,
238 key: <T as Table>::Key,
239 subkey: <T as DupSort>::SubKey,
240 ) -> ValueOnlyResult<T> {
241 read_value!(self, get_both_range(key.encode().as_ref(), subkey.encode().as_ref()))
242 .map_err(|e| DatabaseError::Read(e.into()))?
243 .map(decode_one::<T>)
244 .transpose()
245 }
246
247 fn walk_dup(
253 &mut self,
254 key: Option<T::Key>,
255 subkey: Option<T::SubKey>,
256 ) -> Result<DupWalker<'_, T, Self>, DatabaseError> {
257 let start = match (key, subkey) {
258 (Some(key), Some(subkey)) => {
259 let encoded_key = key.encode();
260 read_value!(self, get_both_range(encoded_key.as_ref(), subkey.encode().as_ref()))
261 .map_err(|e| DatabaseError::Read(e.into()))?
262 .map(|val| decoder::<T>((Cow::Borrowed(encoded_key.as_ref()), val)))
263 }
264 (Some(key), None) => {
265 let encoded_key = key.encode();
266 read_value!(self, set(encoded_key.as_ref()))
267 .map_err(|e| DatabaseError::Read(e.into()))?
268 .map(|val| decoder::<T>((Cow::Borrowed(encoded_key.as_ref()), val)))
269 }
270 (None, Some(subkey)) => {
271 if let Some((key, _)) = self.first()? {
272 let encoded_key = key.encode();
273 read_value!(
274 self,
275 get_both_range(encoded_key.as_ref(), subkey.encode().as_ref())
276 )
277 .map_err(|e| DatabaseError::Read(e.into()))?
278 .map(|val| decoder::<T>((Cow::Borrowed(encoded_key.as_ref()), val)))
279 } else {
280 Some(Err(DatabaseError::Read(MDBXError::NotFound.into())))
281 }
282 }
283 (None, None) => self.first().transpose(),
284 };
285
286 Ok(DupWalker::<'_, T, Self> { cursor: self, start })
287 }
288}
289
290impl<T: Table> DbCursorRW<T> for Cursor<RW, T> {
291 fn upsert(&mut self, key: T::Key, value: &T::Value) -> Result<(), DatabaseError> {
299 let key = key.encode();
300 let value = compress_to_buf_or_ref!(self, value);
301 self.execute_with_operation_metric(
302 Operation::CursorUpsert,
303 Some(value.unwrap_or(&self.buf).len()),
304 |this| {
305 this.inner
306 .put(key.as_ref(), value.unwrap_or(&this.buf), WriteFlags::UPSERT)
307 .map_err(|e| {
308 DatabaseWriteError {
309 info: e.into(),
310 operation: DatabaseWriteOperation::CursorUpsert,
311 table_name: T::NAME,
312 key: key.into_vec(),
313 }
314 .into()
315 })
316 },
317 )
318 }
319
320 fn insert(&mut self, key: T::Key, value: &T::Value) -> Result<(), DatabaseError> {
321 let key = key.encode();
322 let value = compress_to_buf_or_ref!(self, value);
323 self.execute_with_operation_metric(
324 Operation::CursorInsert,
325 Some(value.unwrap_or(&self.buf).len()),
326 |this| {
327 this.inner
328 .put(key.as_ref(), value.unwrap_or(&this.buf), WriteFlags::NO_OVERWRITE)
329 .map_err(|e| {
330 DatabaseWriteError {
331 info: e.into(),
332 operation: DatabaseWriteOperation::CursorInsert,
333 table_name: T::NAME,
334 key: key.into_vec(),
335 }
336 .into()
337 })
338 },
339 )
340 }
341
342 fn append(&mut self, key: T::Key, value: &T::Value) -> Result<(), DatabaseError> {
345 let key = key.encode();
346 let value = compress_to_buf_or_ref!(self, value);
347 self.execute_with_operation_metric(
348 Operation::CursorAppend,
349 Some(value.unwrap_or(&self.buf).len()),
350 |this| {
351 this.inner
352 .put(key.as_ref(), value.unwrap_or(&this.buf), WriteFlags::APPEND)
353 .map_err(|e| {
354 DatabaseWriteError {
355 info: e.into(),
356 operation: DatabaseWriteOperation::CursorAppend,
357 table_name: T::NAME,
358 key: key.into_vec(),
359 }
360 .into()
361 })
362 },
363 )
364 }
365
366 fn delete_current(&mut self) -> Result<(), DatabaseError> {
367 self.execute_with_operation_metric(Operation::CursorDeleteCurrent, None, |this| {
368 this.inner.del(WriteFlags::CURRENT).map_err(|e| DatabaseError::Delete(e.into()))
369 })
370 }
371}
372
373impl<T: DupSort> DbDupCursorRW<T> for Cursor<RW, T> {
374 fn delete_current_duplicates(&mut self) -> Result<(), DatabaseError> {
375 self.execute_with_operation_metric(Operation::CursorDeleteCurrentDuplicates, None, |this| {
376 this.inner.del(WriteFlags::NO_DUP_DATA).map_err(|e| DatabaseError::Delete(e.into()))
377 })
378 }
379
380 fn append_dup(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError> {
381 let key = key.encode();
382 let value = compress_to_buf_or_ref!(self, value);
383 self.execute_with_operation_metric(
384 Operation::CursorAppendDup,
385 Some(value.unwrap_or(&self.buf).len()),
386 |this| {
387 this.inner
388 .put(key.as_ref(), value.unwrap_or(&this.buf), WriteFlags::APPEND_DUP)
389 .map_err(|e| {
390 DatabaseWriteError {
391 info: e.into(),
392 operation: DatabaseWriteOperation::CursorAppendDup,
393 table_name: T::NAME,
394 key: key.into_vec(),
395 }
396 .into()
397 })
398 },
399 )
400 }
401}
402
403#[cfg(test)]
404mod tests {
405
406 use crate::{
407 mdbx::{DatabaseArguments, DatabaseEnv, DatabaseEnvKind},
408 tables::StorageChangeSets,
409 Database,
410 };
411 use alloy_primitives::{Address, B256, U256};
412 use reth_db_api::{
413 cursor::{DbCursorRO, DbDupCursorRW},
414 models::{BlockNumberAddress, ClientVersion},
415 table::TableImporter,
416 transaction::{DbTx, DbTxMut},
417 };
418 use reth_primitives_traits::StorageEntry;
419 use tempfile::TempDir;
420
421 fn create_test_db() -> DatabaseEnv {
422 let path = TempDir::new().unwrap();
423 let mut db = DatabaseEnv::open(
424 path.path(),
425 DatabaseEnvKind::RW,
426 DatabaseArguments::new(ClientVersion::default()),
427 )
428 .unwrap();
429 db.create_tables().unwrap();
430 db
431 }
432
433 #[test]
434 fn test_import_table_with_range_works_on_dupsort() {
435 let addr1 = Address::with_last_byte(1);
436 let addr2 = Address::with_last_byte(2);
437 let addr3 = Address::with_last_byte(3);
438 let source_db = create_test_db();
439 let target_db = create_test_db();
440 let test_data = vec![
441 (
442 BlockNumberAddress((100, addr1)),
443 StorageEntry { key: B256::with_last_byte(1), value: U256::from(100) },
444 ),
445 (
446 BlockNumberAddress((100, addr1)),
447 StorageEntry { key: B256::with_last_byte(2), value: U256::from(200) },
448 ),
449 (
450 BlockNumberAddress((100, addr1)),
451 StorageEntry { key: B256::with_last_byte(3), value: U256::from(300) },
452 ),
453 (
454 BlockNumberAddress((101, addr1)),
455 StorageEntry { key: B256::with_last_byte(1), value: U256::from(400) },
456 ),
457 (
458 BlockNumberAddress((101, addr2)),
459 StorageEntry { key: B256::with_last_byte(1), value: U256::from(500) },
460 ),
461 (
462 BlockNumberAddress((101, addr2)),
463 StorageEntry { key: B256::with_last_byte(2), value: U256::from(600) },
464 ),
465 (
466 BlockNumberAddress((102, addr3)),
467 StorageEntry { key: B256::with_last_byte(1), value: U256::from(700) },
468 ),
469 ];
470
471 let tx = source_db.tx_mut().unwrap();
473 {
474 let mut cursor = tx.cursor_dup_write::<StorageChangeSets>().unwrap();
475 for (key, value) in &test_data {
476 cursor.append_dup(*key, *value).unwrap();
477 }
478 }
479 tx.commit().unwrap();
480
481 let source_tx = source_db.tx().unwrap();
483 let target_tx = target_db.tx_mut().unwrap();
484
485 target_tx
486 .import_table_with_range::<StorageChangeSets, _>(
487 &source_tx,
488 Some(BlockNumberAddress((100, Address::ZERO))),
489 BlockNumberAddress((102, Address::repeat_byte(0xff))),
490 )
491 .unwrap();
492 target_tx.commit().unwrap();
493
494 let verify_tx = target_db.tx().unwrap();
496 let mut cursor = verify_tx.cursor_dup_read::<StorageChangeSets>().unwrap();
497 let copied: Vec<_> = cursor.walk(None).unwrap().collect::<Result<Vec<_>, _>>().unwrap();
498
499 assert_eq!(copied.len(), test_data.len(), "Should copy all entries including duplicates");
501 for ((copied_key, copied_value), (expected_key, expected_value)) in
502 copied.iter().zip(test_data.iter())
503 {
504 assert_eq!(copied_key, expected_key);
505 assert_eq!(copied_value, expected_value);
506 }
507 }
508}