reth_cli_commands/db/
mod.rs

1use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
2use clap::{Parser, Subcommand};
3use reth_chainspec::{EthChainSpec, EthereumHardforks};
4use reth_cli::chainspec::ChainSpecParser;
5use reth_db::version::{get_db_version, DatabaseVersionError, DB_VERSION};
6use reth_db_common::DbTool;
7use std::io::{self, Write};
8
9mod checksum;
10mod clear;
11mod diff;
12mod get;
13mod list;
14mod stats;
15/// DB List TUI
16mod tui;
17
18/// `reth db` command
19#[derive(Debug, Parser)]
20pub struct Command<C: ChainSpecParser> {
21    #[command(flatten)]
22    env: EnvironmentArgs<C>,
23
24    #[command(subcommand)]
25    command: Subcommands,
26}
27
28#[derive(Subcommand, Debug)]
29/// `reth db` subcommands
30pub enum Subcommands {
31    /// Lists all the tables, their entry count and their size
32    Stats(stats::Command),
33    /// Lists the contents of a table
34    List(list::Command),
35    /// Calculates the content checksum of a table
36    Checksum(checksum::Command),
37    /// Create a diff between two database tables or two entire databases.
38    Diff(diff::Command),
39    /// Gets the content of a table for the given key
40    Get(get::Command),
41    /// Deletes all database entries
42    Drop {
43        /// Bypasses the interactive confirmation and drops the database directly
44        #[arg(short, long)]
45        force: bool,
46    },
47    /// Deletes all table entries
48    Clear(clear::Command),
49    /// Lists current and local database versions
50    Version,
51    /// Returns the full database path
52    Path,
53}
54
55/// `db_ro_exec` opens a database in read-only mode, and then execute with the provided command
56macro_rules! db_ro_exec {
57    ($env:expr, $tool:ident, $N:ident, $command:block) => {
58        let Environment { provider_factory, .. } = $env.init::<$N>(AccessRights::RO)?;
59
60        let $tool = DbTool::new(provider_factory.clone())?;
61        $command;
62    };
63}
64
65impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C> {
66    /// Execute `db` command
67    pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(self) -> eyre::Result<()> {
68        let data_dir = self.env.datadir.clone().resolve_datadir(self.env.chain.chain());
69        let db_path = data_dir.db();
70        let static_files_path = data_dir.static_files();
71        let exex_wal_path = data_dir.exex_wal();
72
73        // ensure the provided datadir exist
74        eyre::ensure!(
75            data_dir.data_dir().is_dir(),
76            "Datadir does not exist: {:?}",
77            data_dir.data_dir()
78        );
79
80        // ensure the provided database exist
81        eyre::ensure!(db_path.is_dir(), "Database does not exist: {:?}", db_path);
82
83        match self.command {
84            // TODO: We'll need to add this on the DB trait.
85            Subcommands::Stats(command) => {
86                db_ro_exec!(self.env, tool, N, {
87                    command.execute(data_dir, &tool)?;
88                });
89            }
90            Subcommands::List(command) => {
91                db_ro_exec!(self.env, tool, N, {
92                    command.execute(&tool)?;
93                });
94            }
95            Subcommands::Checksum(command) => {
96                db_ro_exec!(self.env, tool, N, {
97                    command.execute(&tool)?;
98                });
99            }
100            Subcommands::Diff(command) => {
101                db_ro_exec!(self.env, tool, N, {
102                    command.execute(&tool)?;
103                });
104            }
105            Subcommands::Get(command) => {
106                db_ro_exec!(self.env, tool, N, {
107                    command.execute(&tool)?;
108                });
109            }
110            Subcommands::Drop { force } => {
111                if !force {
112                    // Ask for confirmation
113                    print!("Are you sure you want to drop the database at {data_dir}? This cannot be undone. (y/N): ");
114                    // Flush the buffer to ensure the message is printed immediately
115                    io::stdout().flush().unwrap();
116
117                    let mut input = String::new();
118                    io::stdin().read_line(&mut input).expect("Failed to read line");
119
120                    if !input.trim().eq_ignore_ascii_case("y") {
121                        println!("Database drop aborted!");
122                        return Ok(())
123                    }
124                }
125
126                let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
127                let tool = DbTool::new(provider_factory)?;
128                tool.drop(db_path, static_files_path, exex_wal_path)?;
129            }
130            Subcommands::Clear(command) => {
131                let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
132                command.execute(provider_factory)?;
133            }
134            Subcommands::Version => {
135                let local_db_version = match get_db_version(&db_path) {
136                    Ok(version) => Some(version),
137                    Err(DatabaseVersionError::MissingFile) => None,
138                    Err(err) => return Err(err.into()),
139                };
140
141                println!("Current database version: {DB_VERSION}");
142
143                if let Some(version) = local_db_version {
144                    println!("Local database version: {version}");
145                } else {
146                    println!("Local database is uninitialized");
147                }
148            }
149            Subcommands::Path => {
150                println!("{}", db_path.display());
151            }
152        }
153
154        Ok(())
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use reth_ethereum_cli::chainspec::{EthereumChainSpecParser, SUPPORTED_CHAINS};
162    use std::path::Path;
163
164    #[test]
165    fn parse_stats_globals() {
166        let path = format!("../{}", SUPPORTED_CHAINS[0]);
167        let cmd = Command::<EthereumChainSpecParser>::try_parse_from([
168            "reth",
169            "--datadir",
170            &path,
171            "stats",
172        ])
173        .unwrap();
174        assert_eq!(cmd.env.datadir.resolve_datadir(cmd.env.chain.chain).as_ref(), Path::new(&path));
175    }
176}