reth_cli_commands/db/
list.rs1use super::tui::DbListTUI;
2use alloy_primitives::hex;
3use clap::{builder::RangedU64ValueParser, 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)]
14pub struct Command {
16 table: Tables,
18 #[arg(long, short, default_value_t = 0)]
20 skip: usize,
21 #[arg(long, short, default_value_t = false)]
23 reverse: bool,
24 #[arg(long, short, default_value_t = 5, value_parser = RangedU64ValueParser::<usize>::new().range(1..))]
26 len: usize,
27 #[arg(long)]
33 search: Option<String>,
34 #[arg(long, default_value_t = 0)]
36 min_row_size: usize,
37 #[arg(long, default_value_t = 0)]
39 min_key_size: usize,
40 #[arg(long, default_value_t = 0)]
42 min_value_size: usize,
43 #[arg(long, short)]
45 count: bool,
46 #[arg(long, short)]
48 json: bool,
49 #[arg(long)]
51 raw: bool,
52}
53
54impl Command {
55 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 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> ListTableViewer<'_, N> {
97 fn list(
100 &self,
101 json: &dyn Fn(&ListFilter) -> eyre::Result<(String, usize)>,
102 page: &dyn Fn(&ListFilter) -> Vec<(String, String)>,
103 ) -> eyre::Result<()> {
104 self.tool.provider_factory.db_ref().view(|tx| {
105 tx.disable_long_read_transaction_safety();
107
108 let table_db = tx.inner().open_db(Some(self.args.table.name())).wrap_err("Could not open db.")?;
109 let stats = tx.inner().db_stat(table_db.dbi()).wrap_err(format!("Could not find table: {}", self.args.table.name()))?;
110 let total_entries = stats.entries();
111 let final_entry_idx = total_entries.saturating_sub(1);
112 if self.args.skip > final_entry_idx {
113 error!(
114 target: "reth::cli",
115 "Start index {start} is greater than the final entry index ({final_entry_idx}) in the table {table}",
116 start = self.args.skip,
117 final_entry_idx = final_entry_idx,
118 table = self.args.table.name()
119 );
120 return Ok(())
121 }
122
123
124 let list_filter = self.args.list_filter()?;
125
126 if self.args.json || self.args.count {
127 let (list, count) = json(&list_filter)?;
128
129 if self.args.count {
130 println!("{count} entries found.")
131 } else {
132 println!("{list}");
133 }
134 Ok(())
135 } else {
136 let list_filter = RefCell::new(list_filter);
137 DbListTUI::new(Box::new(|skip, len| {
138 list_filter.borrow_mut().update_page(skip, len);
139 page(&list_filter.borrow())
140 }), self.args.skip, self.args.len, total_entries).run()
141 }
142 })??;
143
144 Ok(())
145 }
146}
147
148impl<N: NodeTypes> TableViewer<()> for ListTableViewer<'_, N> {
149 type Error = eyre::Report;
150
151 fn view<T: Table>(&self) -> Result<(), Self::Error> {
152 self.list(
153 &|filter| {
154 let (list, count) = self.tool.list::<T>(filter)?;
155
156 let list = if self.args.raw {
157 let list = list
158 .into_iter()
159 .map(|row| (row.0, RawValue::new(row.1).into_value()))
160 .collect::<Vec<_>>();
161 serde_json::to_string_pretty(&list)?
162 } else {
163 serde_json::to_string_pretty(&list)?
164 };
165 Ok((list, count))
166 },
167 &|filter| {
168 self.tool
169 .list::<T>(filter)
170 .unwrap()
171 .0
172 .into_iter()
173 .map(|(key, value)| {
174 let value = if self.args.raw {
175 serde_json::to_string(RawValue::<T::Value>::from(value).raw_value())
176 } else {
177 serde_json::to_string_pretty(&value)
178 };
179 (
180 format!("{key:?}"),
181 value.unwrap_or_else(|error| {
182 format!("Error serializing value: {error}")
183 }),
184 )
185 })
186 .collect()
187 },
188 )
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn command_rejects_zero_len() {
198 let result = Command::try_parse_from(["list", "Headers", "--len", "0"]);
199
200 assert!(result.is_err());
201 }
202
203 #[test]
204 fn command_accepts_positive_len() {
205 let command = Command::try_parse_from(["list", "Headers", "--len", "10"]).unwrap();
206
207 assert_eq!(command.len, 10);
208 }
209}