Skip to main content

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::{transaction::DbTx, DatabaseEnv};
7use reth_db_api::{database::Database, table::Table, RawValue, TableViewer, Tables};
8use reth_db_common::{DbTool, ListFilter};
9use reth_node_builder::{NodeTypes, NodeTypesWithDBAdapter};
10use std::cell::RefCell;
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: NodeTypes<ChainSpec: EthereumHardforks>>(
57        self,
58        tool: &DbTool<NodeTypesWithDBAdapter<N, 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) -> eyre::Result<ListFilter> {
65        let search = match self.search.as_deref() {
66            Some(search) => {
67                if let Some(search) = search.strip_prefix("0x") {
68                    hex::decode(search).wrap_err(
69                        "Invalid hex content after 0x prefix in --search (expected valid hex like 0xdeadbeef).",
70                    )?
71                } else {
72                    search.as_bytes().to_vec()
73                }
74            }
75            None => Vec::new(),
76        };
77
78        Ok(ListFilter {
79            skip: self.skip,
80            len: self.len,
81            search,
82            min_row_size: self.min_row_size,
83            min_key_size: self.min_key_size,
84            min_value_size: self.min_value_size,
85            reverse: self.reverse,
86            only_count: self.count,
87        })
88    }
89}
90
91struct ListTableViewer<'a, N: NodeTypes> {
92    tool: &'a DbTool<NodeTypesWithDBAdapter<N, DatabaseEnv>>,
93    args: &'a Command,
94}
95
96impl<N: NodeTypes> TableViewer<()> for ListTableViewer<'_, N> {
97    type Error = eyre::Report;
98
99    fn view<T: Table>(&self) -> Result<(), Self::Error> {
100        self.tool.provider_factory.db_ref().view(|tx| {
101            // We may be using the tui for a long time
102            tx.disable_long_read_transaction_safety();
103
104            let table_db = tx.inner().open_db(Some(self.args.table.name())).wrap_err("Could not open db.")?;
105                    let stats = tx.inner().db_stat(table_db.dbi()).wrap_err(format!("Could not find table: {}", self.args.table.name()))?;
106            let total_entries = stats.entries();
107            let final_entry_idx = total_entries.saturating_sub(1);
108            if self.args.skip > final_entry_idx {
109                error!(
110                    target: "reth::cli",
111                    "Start index {start} is greater than the final entry index ({final_entry_idx}) in the table {table}",
112                    start = self.args.skip,
113                    final_entry_idx = final_entry_idx,
114                    table = self.args.table.name()
115                );
116                return Ok(())
117            }
118
119
120            let list_filter = self.args.list_filter()?;
121
122            if self.args.json || self.args.count {
123                let (list, count) = self.tool.list::<T>(&list_filter)?;
124
125                if self.args.count {
126                    println!("{count} entries found.")
127                } else if self.args.raw {
128                    let list = list.into_iter().map(|row| (row.0, RawValue::new(row.1).into_value())).collect::<Vec<_>>();
129                    println!("{}", serde_json::to_string_pretty(&list)?);
130                } else {
131                    println!("{}", serde_json::to_string_pretty(&list)?);
132                }
133                Ok(())
134            } else {
135                let list_filter = RefCell::new(list_filter);
136                DbListTUI::<_, T>::new(|skip, len| {
137                    list_filter.borrow_mut().update_page(skip, len);
138                    self.tool.list::<T>(&list_filter.borrow()).unwrap().0
139                }, self.args.skip, self.args.len, total_entries, self.args.raw).run()
140            }
141        })??;
142
143        Ok(())
144    }
145}