diff --git a/bin/reth/src/db/mod.rs b/bin/reth/src/db/mod.rs index c9c2865d85..5634b2e0ef 100644 --- a/bin/reth/src/db/mod.rs +++ b/bin/reth/src/db/mod.rs @@ -72,9 +72,12 @@ pub enum Subcommands { pub struct ListArgs { /// The table name table: String, // TODO: Convert to enum - /// Where to start iterating + /// Skip first N entries #[arg(long, short, default_value = "0")] - start: usize, + skip: usize, + /// Reverse the order of the entries. If enabled last table entries are read. + #[arg(long, short, default_value = "false")] + reverse: bool, /// How many items to take from the walker #[arg(long, short, default_value = DEFAULT_NUM_ITEMS)] len: usize, @@ -168,12 +171,12 @@ impl Command { } if args.json { - let list_result = tool.list::(args.start, args.len)?.into_iter().collect::>(); + let list_result = tool.list::(args.skip, args.len,args.reverse)?.into_iter().collect::>(); println!("{}", serde_json::to_string_pretty(&list_result)?); Ok(()) } else { - tui::DbListTUI::<_, tables::$table>::new(|start, count| { - tool.list::(start, count).unwrap() + tui::DbListTUI::<_, tables::$table>::new(|skip, count| { + tool.list::(skip, count, args.reverse).unwrap() }, $start, $len, total_entries).run() } })?? @@ -186,7 +189,7 @@ impl Command { } } - table_tui!(args.table.as_str(), args.start, args.len => [ + table_tui!(args.table.as_str(), args.skip, args.len => [ CanonicalHeaders, HeaderTD, HeaderNumbers, diff --git a/bin/reth/src/db/tui.rs b/bin/reth/src/db/tui.rs index 5c0d7d18da..36072a039e 100644 --- a/bin/reth/src/db/tui.rs +++ b/bin/reth/src/db/tui.rs @@ -5,7 +5,6 @@ use crossterm::{ }; use reth_db::table::Table; use std::{ - collections::BTreeMap, io, time::{Duration, Instant}, }; @@ -46,15 +45,15 @@ pub(crate) enum ViewMode { #[derive(Default)] pub(crate) struct DbListTUI where - F: FnMut(usize, usize) -> BTreeMap, + F: FnMut(usize, usize) -> Vec<(T::Key, T::Value)>, { /// Fetcher for the next page of items. /// /// The fetcher is passed the index of the first item to fetch, and the number of items to /// fetch from that item. fetch: F, - /// The starting index of the key list in the DB. - start: usize, + /// Skip N indices of the key list in the DB. + skip: usize, /// The amount of entries to show per page count: usize, /// The total number of entries in the database @@ -66,24 +65,24 @@ where /// The state of the key list. list_state: ListState, /// Entries to show in the TUI. - entries: BTreeMap, + entries: Vec<(T::Key, T::Value)>, } impl DbListTUI where - F: FnMut(usize, usize) -> BTreeMap, + F: FnMut(usize, usize) -> Vec<(T::Key, T::Value)>, { /// Create a new database list TUI - pub(crate) fn new(fetch: F, start: usize, count: usize, total_entries: usize) -> Self { + pub(crate) fn new(fetch: F, skip: usize, count: usize, total_entries: usize) -> Self { Self { fetch, - start, + skip, count, total_entries, mode: ViewMode::Normal, input: String::new(), list_state: ListState::default(), - entries: BTreeMap::new(), + entries: Vec::new(), } } @@ -123,33 +122,33 @@ where /// Fetch the next page of items fn next_page(&mut self) { - if self.start + self.count >= self.total_entries { + if self.skip + self.count >= self.total_entries { return } - self.start += self.count; + self.skip += self.count; self.fetch_page(); } /// Fetch the previous page of items fn previous_page(&mut self) { - if self.start == 0 { + if self.skip == 0 { return } - self.start -= self.count; + self.skip = self.skip.saturating_sub(self.count); self.fetch_page(); } /// Go to a specific page. fn go_to_page(&mut self, page: usize) { - self.start = (self.count * page).min(self.total_entries - self.count); + self.skip = (self.count * page).min(self.total_entries - self.count); self.fetch_page(); } /// Fetch the current page fn fetch_page(&mut self) { - self.entries = (self.fetch)(self.start, self.count); + self.entries = (self.fetch)(self.skip, self.count); self.reset(); } @@ -189,7 +188,7 @@ fn event_loop( tick_rate: Duration, ) -> io::Result<()> where - F: FnMut(usize, usize) -> BTreeMap, + F: FnMut(usize, usize) -> Vec<(T::Key, T::Value)>, { let mut last_tick = Instant::now(); let mut running = true; @@ -217,7 +216,7 @@ where /// Handle incoming events fn handle_event(app: &mut DbListTUI, event: Event) -> io::Result where - F: FnMut(usize, usize) -> BTreeMap, + F: FnMut(usize, usize) -> Vec<(T::Key, T::Value)>, { if app.mode == ViewMode::GoToPage { if let Event::Key(key) = event { @@ -283,7 +282,7 @@ where /// Render the UI fn ui(f: &mut Frame<'_, B>, app: &mut DbListTUI) where - F: FnMut(usize, usize) -> BTreeMap, + F: FnMut(usize, usize) -> Vec<(T::Key, T::Value)>, { let outer_chunks = Layout::default() .direction(Direction::Vertical) @@ -297,21 +296,23 @@ where .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(outer_chunks[0]); - let key_length = format!("{}", app.start + app.count - 1).len(); - let formatted_keys = app - .entries - .keys() + let key_length = format!("{}", app.skip + app.count - 1).len(); + + let entries: Vec<_> = app.entries.iter().map(|(k, _)| k).collect(); + + let formatted_keys = entries + .into_iter() .enumerate() .map(|(i, k)| { - ListItem::new(format!("[{:0>width$}]: {k:?}", i + app.start, width = key_length)) + ListItem::new(format!("[{:0>width$}]: {k:?}", i + app.skip, width = key_length)) }) .collect::>>(); let key_list = List::new(formatted_keys) .block(Block::default().borders(Borders::ALL).title(format!( "Keys (Showing entries {}-{} out of {} entries)", - app.start, - app.start + app.entries.len() - 1, + app.skip, + app.skip + app.entries.len() - 1, app.total_entries ))) .style(Style::default().fg(Color::White)) @@ -320,7 +321,8 @@ where .start_corner(Corner::TopLeft); f.render_stateful_widget(key_list, inner_chunks[0], &mut app.list_state); - let values = app.entries.values().collect::>(); + let values = app.entries.iter().map(|(_, v)| v).collect::>(); + let value_display = Paragraph::new( app.list_state .selected() diff --git a/bin/reth/src/utils.rs b/bin/reth/src/utils.rs index 92a49f8d28..943c2724ff 100644 --- a/bin/reth/src/utils.rs +++ b/bin/reth/src/utils.rs @@ -2,7 +2,7 @@ use eyre::{Result, WrapErr}; use reth_db::{ - cursor::{DbCursorRO, Walker}, + cursor::DbCursorRO, database::Database, table::Table, transaction::{DbTx, DbTxMut}, @@ -12,7 +12,7 @@ use reth_interfaces::p2p::{ priority::Priority, }; use reth_primitives::{BlockHashOrNumber, HeadersDirection, SealedHeader}; -use std::{collections::BTreeMap, path::Path, time::Duration}; +use std::{path::Path, time::Duration}; use tracing::info; /// Get a single header from network @@ -67,22 +67,21 @@ impl<'a, DB: Database> DbTool<'a, DB> { /// entries into a [`HashMap`][std::collections::HashMap]. pub fn list( &mut self, - start: usize, + skip: usize, len: usize, - ) -> Result> { + reverse: bool, + ) -> Result> { let data = self.db.view(|tx| { let mut cursor = tx.cursor_read::().expect("Was not able to obtain a cursor."); - // TODO: Upstream this in the DB trait. - let start_walker = cursor.current().transpose(); - let walker = Walker::new(&mut cursor, start_walker); - - walker.skip(start).take(len).collect::>() + if reverse { + cursor.walk_back(None)?.skip(skip).take(len).collect::>() + } else { + cursor.walk(None)?.skip(skip).take(len).collect::>() + } })?; - data.into_iter() - .collect::, _>>() - .map_err(|e| eyre::eyre!(e)) + data.map_err(|e| eyre::eyre!(e)) } /// Grabs the content of the table for the given key