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