reth_db_common/db_tool/
mod.rs

1//! Common db operations
2
3use boyer_moore_magiclen::BMByte;
4use eyre::Result;
5use reth_db::{RawTable, TableRawRow};
6use reth_db_api::{
7    cursor::{DbCursorRO, DbDupCursorRO},
8    database::Database,
9    table::{Decode, Decompress, DupSort, Table, TableRow},
10    transaction::{DbTx, DbTxMut},
11    DatabaseError,
12};
13use reth_fs_util as fs;
14use reth_node_types::NodeTypesWithDB;
15use reth_provider::{providers::ProviderNodeTypes, ChainSpecProvider, DBProvider, ProviderFactory};
16use std::{path::Path, rc::Rc, sync::Arc};
17use tracing::info;
18
19/// Wrapper over DB that implements many useful DB queries.
20#[derive(Debug)]
21pub struct DbTool<N: NodeTypesWithDB> {
22    /// The provider factory that the db tool will use.
23    pub provider_factory: ProviderFactory<N>,
24}
25
26impl<N: NodeTypesWithDB> DbTool<N> {
27    /// Get an [`Arc`] to the underlying chainspec.
28    pub fn chain(&self) -> Arc<N::ChainSpec> {
29        self.provider_factory.chain_spec()
30    }
31
32    /// Grabs the contents of the table within a certain index range and places the
33    /// entries into a [`HashMap`][std::collections::HashMap].
34    ///
35    /// [`ListFilter`] can be used to further
36    /// filter down the desired results. (eg. List only rows which include `0xd3adbeef`)
37    pub fn list<T: Table>(&self, filter: &ListFilter) -> Result<(Vec<TableRow<T>>, usize)> {
38        let bmb = Rc::new(BMByte::from(&filter.search));
39        if bmb.is_none() && filter.has_search() {
40            eyre::bail!("Invalid search.")
41        }
42
43        let mut hits = 0;
44
45        let data = self.provider_factory.db_ref().view(|tx| {
46            let mut cursor =
47                tx.cursor_read::<RawTable<T>>().expect("Was not able to obtain a cursor.");
48
49            let map_filter = |row: Result<TableRawRow<T>, _>| {
50                if let Ok((k, v)) = row {
51                    let (key, value) = (k.into_key(), v.into_value());
52
53                    if key.len() + value.len() < filter.min_row_size {
54                        return None
55                    }
56                    if key.len() < filter.min_key_size {
57                        return None
58                    }
59                    if value.len() < filter.min_value_size {
60                        return None
61                    }
62
63                    let result = || {
64                        if filter.only_count {
65                            return None
66                        }
67                        Some((
68                            <T as Table>::Key::decode(&key).unwrap(),
69                            <T as Table>::Value::decompress(&value).unwrap(),
70                        ))
71                    };
72
73                    match &*bmb {
74                        Some(searcher) => {
75                            if searcher.find_first_in(&value).is_some() ||
76                                searcher.find_first_in(&key).is_some()
77                            {
78                                hits += 1;
79                                return result()
80                            }
81                        }
82                        None => {
83                            hits += 1;
84                            return result()
85                        }
86                    }
87                }
88                None
89            };
90
91            if filter.reverse {
92                Ok(cursor
93                    .walk_back(None)?
94                    .skip(filter.skip)
95                    .filter_map(map_filter)
96                    .take(filter.len)
97                    .collect::<Vec<(_, _)>>())
98            } else {
99                Ok(cursor
100                    .walk(None)?
101                    .skip(filter.skip)
102                    .filter_map(map_filter)
103                    .take(filter.len)
104                    .collect::<Vec<(_, _)>>())
105            }
106        })?;
107
108        Ok((data.map_err(|e: DatabaseError| eyre::eyre!(e))?, hits))
109    }
110}
111
112impl<N: ProviderNodeTypes> DbTool<N> {
113    /// Takes a DB where the tables have already been created.
114    pub fn new(provider_factory: ProviderFactory<N>) -> eyre::Result<Self> {
115        // Disable timeout because we are entering a TUI which might read for a long time. We
116        // disable on the [`DbTool`] level since it's only used in the CLI.
117        provider_factory.provider()?.disable_long_read_transaction_safety();
118        Ok(Self { provider_factory })
119    }
120
121    /// Grabs the content of the table for the given key
122    pub fn get<T: Table>(&self, key: T::Key) -> Result<Option<T::Value>> {
123        self.provider_factory.db_ref().view(|tx| tx.get::<T>(key))?.map_err(|e| eyre::eyre!(e))
124    }
125
126    /// Grabs the content of the `DupSort` table for the given key and subkey
127    pub fn get_dup<T: DupSort>(&self, key: T::Key, subkey: T::SubKey) -> Result<Option<T::Value>> {
128        self.provider_factory
129            .db_ref()
130            .view(|tx| tx.cursor_dup_read::<T>()?.seek_by_key_subkey(key, subkey))?
131            .map_err(|e| eyre::eyre!(e))
132    }
133
134    /// Drops the database and the static files at the given path.
135    pub fn drop(
136        &self,
137        db_path: impl AsRef<Path>,
138        static_files_path: impl AsRef<Path>,
139    ) -> Result<()> {
140        let db_path = db_path.as_ref();
141        info!(target: "reth::cli", "Dropping database at {:?}", db_path);
142        fs::remove_dir_all(db_path)?;
143
144        let static_files_path = static_files_path.as_ref();
145        info!(target: "reth::cli", "Dropping static files at {:?}", static_files_path);
146        fs::remove_dir_all(static_files_path)?;
147        fs::create_dir_all(static_files_path)?;
148
149        Ok(())
150    }
151
152    /// Drops the provided table from the database.
153    pub fn drop_table<T: Table>(&self) -> Result<()> {
154        self.provider_factory.db_ref().update(|tx| tx.clear::<T>())??;
155        Ok(())
156    }
157}
158
159/// Filters the results coming from the database.
160#[derive(Debug)]
161pub struct ListFilter {
162    /// Skip first N entries.
163    pub skip: usize,
164    /// Take N entries.
165    pub len: usize,
166    /// Sequence of bytes that will be searched on values and keys from the database.
167    pub search: Vec<u8>,
168    /// Minimum row size.
169    pub min_row_size: usize,
170    /// Minimum key size.
171    pub min_key_size: usize,
172    /// Minimum value size.
173    pub min_value_size: usize,
174    /// Reverse order of entries.
175    pub reverse: bool,
176    /// Only counts the number of filtered entries without decoding and returning them.
177    pub only_count: bool,
178}
179
180impl ListFilter {
181    /// If `search` has a list of bytes, then filter for rows that have this sequence.
182    pub fn has_search(&self) -> bool {
183        !self.search.is_empty()
184    }
185
186    /// Updates the page with new `skip` and `len` values.
187    pub fn update_page(&mut self, skip: usize, len: usize) {
188        self.skip = skip;
189        self.len = len;
190    }
191}