reth_cli_commands/db/
list.rs

1use super::tui::DbListTUI;
2use alloy_primitives::hex;
3use clap::Parser;
4use eyre::WrapErr;
5use reth_chainspec::EthereumHardforks;
6use reth_db::DatabaseEnv;
7use reth_db_api::{database::Database, table::Table, RawValue, TableViewer, Tables};
8use reth_db_common::{DbTool, ListFilter};
9use reth_node_builder::{NodeTypesWithDBAdapter, NodeTypesWithEngine};
10use std::{cell::RefCell, sync::Arc};
11use tracing::error;
12
13#[derive(Parser, Debug)]
14/// The arguments for the `reth db list` command
15pub struct Command {
16    /// The table name
17    table: Tables,
18    /// Skip first N entries
19    #[arg(long, short, default_value_t = 0)]
20    skip: usize,
21    /// Reverse the order of the entries. If enabled last table entries are read.
22    #[arg(long, short, default_value_t = false)]
23    reverse: bool,
24    /// How many items to take from the walker
25    #[arg(long, short, default_value_t = 5)]
26    len: usize,
27    /// Search parameter for both keys and values. Prefix it with `0x` to search for binary data,
28    /// and text otherwise.
29    ///
30    /// ATTENTION! For compressed tables (`Transactions` and `Receipts`), there might be
31    /// missing results since the search uses the raw uncompressed value from the database.
32    #[arg(long)]
33    search: Option<String>,
34    /// Minimum size of row in bytes
35    #[arg(long, default_value_t = 0)]
36    min_row_size: usize,
37    /// Minimum size of key in bytes
38    #[arg(long, default_value_t = 0)]
39    min_key_size: usize,
40    /// Minimum size of value in bytes
41    #[arg(long, default_value_t = 0)]
42    min_value_size: usize,
43    /// Returns the number of rows found.
44    #[arg(long, short)]
45    count: bool,
46    /// Dump as JSON instead of using TUI.
47    #[arg(long, short)]
48    json: bool,
49    /// Output bytes instead of human-readable decoded value
50    #[arg(long)]
51    raw: bool,
52}
53
54impl Command {
55    /// Execute `db list` command
56    pub fn execute<N: NodeTypesWithEngine<ChainSpec: EthereumHardforks>>(
57        self,
58        tool: &DbTool<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>,
59    ) -> eyre::Result<()> {
60        self.table.view(&ListTableViewer { tool, args: &self })
61    }
62
63    /// Generate [`ListFilter`] from command.
64    pub fn list_filter(&self) -> ListFilter {
65        let search = self
66            .search
67            .as_ref()
68            .map(|search| {
69                if let Some(search) = search.strip_prefix("0x") {
70                    return hex::decode(search).unwrap()
71                }
72                search.as_bytes().to_vec()
73            })
74            .unwrap_or_default();
75
76        ListFilter {
77            skip: self.skip,
78            len: self.len,
79            search,
80            min_row_size: self.min_row_size,
81            min_key_size: self.min_key_size,
82            min_value_size: self.min_value_size,
83            reverse: self.reverse,
84            only_count: self.count,
85        }
86    }
87}
88
89struct ListTableViewer<'a, N: NodeTypesWithEngine> {
90    tool: &'a DbTool<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>,
91    args: &'a Command,
92}
93
94impl<N: NodeTypesWithEngine> TableViewer<()> for ListTableViewer<'_, N> {
95    type Error = eyre::Report;
96
97    fn view<T: Table>(&self) -> Result<(), Self::Error> {
98        self.tool.provider_factory.db_ref().view(|tx| {
99            let table_db = tx.inner.open_db(Some(self.args.table.name())).wrap_err("Could not open db.")?;
100            let stats = tx.inner.db_stat(&table_db).wrap_err(format!("Could not find table: {}", stringify!($table)))?;
101            let total_entries = stats.entries();
102            let final_entry_idx = total_entries.saturating_sub(1);
103            if self.args.skip > final_entry_idx {
104                error!(
105                    target: "reth::cli",
106                    "Start index {start} is greater than the final entry index ({final_entry_idx}) in the table {table}",
107                    start = self.args.skip,
108                    final_entry_idx = final_entry_idx,
109                    table = self.args.table.name()
110                );
111                return Ok(())
112            }
113
114
115            let list_filter = self.args.list_filter();
116
117            if self.args.json || self.args.count {
118                let (list, count) = self.tool.list::<T>(&list_filter)?;
119
120                if self.args.count {
121                    println!("{count} entries found.")
122                } else if self.args.raw {
123                    let list = list.into_iter().map(|row| (row.0, RawValue::new(row.1).into_value())).collect::<Vec<_>>();
124                    println!("{}", serde_json::to_string_pretty(&list)?);
125                } else {
126                    println!("{}", serde_json::to_string_pretty(&list)?);
127                }
128                Ok(())
129            } else {
130                let list_filter = RefCell::new(list_filter);
131                DbListTUI::<_, T>::new(|skip, len| {
132                    list_filter.borrow_mut().update_page(skip, len);
133                    self.tool.list::<T>(&list_filter.borrow()).unwrap().0
134                }, self.args.skip, self.args.len, total_entries, self.args.raw).run()
135            }
136        })??;
137
138        Ok(())
139    }
140}