mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(bin): --raw and min size arguments for db commands (#5113)
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
use crate::utils::DbTool;
|
||||
use clap::Parser;
|
||||
|
||||
use reth_db::{database::Database, table::Table, TableType, TableViewer, Tables};
|
||||
use reth_db::{database::Database, table::Table, RawKey, RawTable, TableType, TableViewer, Tables};
|
||||
use tracing::error;
|
||||
|
||||
/// The arguments for the `reth db get` command
|
||||
@ -12,9 +12,13 @@ pub struct Command {
|
||||
/// NOTE: The dupsort tables are not supported now.
|
||||
pub table: Tables,
|
||||
|
||||
/// The key to get content for
|
||||
/// The key to get content for
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
pub key: String,
|
||||
|
||||
/// Output bytes instead of human-readable decoded value
|
||||
#[clap(long)]
|
||||
pub raw: bool,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
@ -51,9 +55,17 @@ impl<DB: Database> TableViewer<()> for GetValueViewer<'_, DB> {
|
||||
// get a key for given table
|
||||
let key = self.args.table_key::<T>()?;
|
||||
|
||||
match self.tool.get::<T>(key)? {
|
||||
let content = if self.args.raw {
|
||||
self.tool
|
||||
.get::<RawTable<T>>(RawKey::from(key))?
|
||||
.map(|content| format!("{:?}", content.raw_value()))
|
||||
} else {
|
||||
self.tool.get::<T>(key)?.as_ref().map(serde_json::to_string_pretty).transpose()?
|
||||
};
|
||||
|
||||
match content {
|
||||
Some(content) => {
|
||||
println!("{}", serde_json::to_string_pretty(&content)?);
|
||||
println!("{}", content);
|
||||
}
|
||||
None => {
|
||||
error!(target: "reth::cli", "No content for the given table key.");
|
||||
|
||||
@ -2,7 +2,7 @@ use super::tui::DbListTUI;
|
||||
use crate::utils::{DbTool, ListFilter};
|
||||
use clap::Parser;
|
||||
use eyre::WrapErr;
|
||||
use reth_db::{database::Database, table::Table, DatabaseEnvRO, TableViewer, Tables};
|
||||
use reth_db::{database::Database, table::Table, DatabaseEnvRO, RawValue, TableViewer, Tables};
|
||||
use reth_primitives::hex;
|
||||
use std::cell::RefCell;
|
||||
use tracing::error;
|
||||
@ -28,12 +28,24 @@ pub struct Command {
|
||||
/// missing results since the search uses the raw uncompressed value from the database.
|
||||
#[arg(long)]
|
||||
search: Option<String>,
|
||||
/// Minimum size of row in bytes
|
||||
#[arg(long, default_value_t = 0)]
|
||||
min_row_size: usize,
|
||||
/// Minimum size of key in bytes
|
||||
#[arg(long, default_value_t = 0)]
|
||||
min_key_size: usize,
|
||||
/// Minimum size of value in bytes
|
||||
#[arg(long, default_value_t = 0)]
|
||||
min_value_size: usize,
|
||||
/// Returns the number of rows found.
|
||||
#[arg(long, short)]
|
||||
count: bool,
|
||||
/// Dump as JSON instead of using TUI.
|
||||
#[arg(long, short)]
|
||||
json: bool,
|
||||
/// Output bytes instead of human-readable decoded value
|
||||
#[arg(long)]
|
||||
raw: bool,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
@ -59,6 +71,9 @@ impl Command {
|
||||
skip: self.skip,
|
||||
len: self.len,
|
||||
search,
|
||||
min_row_size: self.min_row_size,
|
||||
min_key_size: self.min_key_size,
|
||||
min_value_size: self.min_value_size,
|
||||
reverse: self.reverse,
|
||||
only_count: self.count,
|
||||
}
|
||||
@ -97,17 +112,19 @@ impl TableViewer<()> for ListTableViewer<'_> {
|
||||
|
||||
if self.args.count {
|
||||
println!("{count} entries found.")
|
||||
} else if self.args.raw {
|
||||
let list = list.into_iter().map(|row| (row.0, RawValue::new(row.1).into_value())).collect::<Vec<_>>();
|
||||
println!("{}", serde_json::to_string_pretty(&list)?);
|
||||
} else {
|
||||
println!("{}", serde_json::to_string_pretty(&list)?);
|
||||
}
|
||||
Ok(())
|
||||
|
||||
} else {
|
||||
let list_filter = RefCell::new(list_filter);
|
||||
DbListTUI::<_, T>::new(|skip, len| {
|
||||
list_filter.borrow_mut().update_page(skip, len);
|
||||
self.tool.list::<T>(&list_filter.borrow()).unwrap().0
|
||||
}, self.args.skip, self.args.len, total_entries).run()
|
||||
}, self.args.skip, self.args.len, total_entries, self.args.raw).run()
|
||||
}
|
||||
})??;
|
||||
|
||||
|
||||
@ -3,7 +3,10 @@ use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use reth_db::table::{Table, TableRow};
|
||||
use reth_db::{
|
||||
table::{Table, TableRow},
|
||||
RawValue,
|
||||
};
|
||||
use std::{
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
@ -42,7 +45,70 @@ pub(crate) enum ViewMode {
|
||||
GoToPage,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum Entries<T: Table> {
|
||||
/// Pairs of [Table::Key] and [RawValue<Table::Value>]
|
||||
RawValues(Vec<(T::Key, RawValue<T::Value>)>),
|
||||
/// Pairs of [Table::Key] and [Table::Value]
|
||||
Values(Vec<TableRow<T>>),
|
||||
}
|
||||
|
||||
impl<T: Table> Entries<T> {
|
||||
/// Creates new empty [Entries] as [Entries::RawValues] if `raw_values == true` and as
|
||||
/// [Entries::Values] if `raw == false`.
|
||||
fn new_with_raw_values(raw_values: bool) -> Self {
|
||||
if raw_values {
|
||||
Self::RawValues(Vec::new())
|
||||
} else {
|
||||
Self::Values(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the internal entries [Vec], converting the [Table::Value] into [RawValue<Table::Value>]
|
||||
/// if needed.
|
||||
fn set(&mut self, new_entries: Vec<TableRow<T>>) {
|
||||
match self {
|
||||
Entries::RawValues(old_entries) => {
|
||||
*old_entries =
|
||||
new_entries.into_iter().map(|(key, value)| (key, value.into())).collect()
|
||||
}
|
||||
Entries::Values(old_entries) => *old_entries = new_entries,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the length of internal [Vec].
|
||||
fn len(&self) -> usize {
|
||||
match self {
|
||||
Entries::RawValues(entries) => entries.len(),
|
||||
Entries::Values(entries) => entries.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over keys of the internal [Vec]. For both [Entries::RawValues] and
|
||||
/// [Entries::Values], this iterator will yield [Table::Key].
|
||||
fn iter_keys(&self) -> EntriesKeyIter<'_, T> {
|
||||
EntriesKeyIter { entries: self, index: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
struct EntriesKeyIter<'a, T: Table> {
|
||||
entries: &'a Entries<T>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a, T: Table> Iterator for EntriesKeyIter<'a, T> {
|
||||
type Item = &'a T::Key;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let item = match self.entries {
|
||||
Entries::RawValues(values) => values.get(self.index).map(|(key, _)| key),
|
||||
Entries::Values(values) => values.get(self.index).map(|(key, _)| key),
|
||||
};
|
||||
self.index += 1;
|
||||
|
||||
item
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DbListTUI<F, T: Table>
|
||||
where
|
||||
F: FnMut(usize, usize) -> Vec<TableRow<T>>,
|
||||
@ -65,7 +131,7 @@ where
|
||||
/// The state of the key list.
|
||||
list_state: ListState,
|
||||
/// Entries to show in the TUI.
|
||||
entries: Vec<TableRow<T>>,
|
||||
entries: Entries<T>,
|
||||
}
|
||||
|
||||
impl<F, T: Table> DbListTUI<F, T>
|
||||
@ -73,7 +139,13 @@ where
|
||||
F: FnMut(usize, usize) -> Vec<TableRow<T>>,
|
||||
{
|
||||
/// Create a new database list TUI
|
||||
pub(crate) fn new(fetch: F, skip: usize, count: usize, total_entries: usize) -> Self {
|
||||
pub(crate) fn new(
|
||||
fetch: F,
|
||||
skip: usize,
|
||||
count: usize,
|
||||
total_entries: usize,
|
||||
raw: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
fetch,
|
||||
skip,
|
||||
@ -82,7 +154,7 @@ where
|
||||
mode: ViewMode::Normal,
|
||||
input: String::new(),
|
||||
list_state: ListState::default(),
|
||||
entries: Vec::new(),
|
||||
entries: Entries::new_with_raw_values(raw),
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +220,7 @@ where
|
||||
|
||||
/// Fetch the current page
|
||||
fn fetch_page(&mut self) {
|
||||
self.entries = (self.fetch)(self.skip, self.count);
|
||||
self.entries.set((self.fetch)(self.skip, self.count));
|
||||
self.reset();
|
||||
}
|
||||
|
||||
@ -298,10 +370,9 @@ where
|
||||
|
||||
let key_length = format!("{}", (app.skip + app.count).saturating_sub(1)).len();
|
||||
|
||||
let entries: Vec<_> = app.entries.iter().map(|(k, _)| k).collect();
|
||||
|
||||
let formatted_keys = entries
|
||||
.into_iter()
|
||||
let formatted_keys = app
|
||||
.entries
|
||||
.iter_keys()
|
||||
.enumerate()
|
||||
.map(|(i, k)| {
|
||||
ListItem::new(format!("[{:0>width$}]: {k:?}", i + app.skip, width = key_length))
|
||||
@ -321,7 +392,22 @@ where
|
||||
.start_corner(Corner::TopLeft);
|
||||
f.render_stateful_widget(key_list, inner_chunks[0], &mut app.list_state);
|
||||
|
||||
let values = app.entries.iter().map(|(_, v)| v).collect::<Vec<_>>();
|
||||
let values: Vec<_> = match &app.entries {
|
||||
Entries::RawValues(entries) => entries
|
||||
.iter()
|
||||
.map(|(_, v)| {
|
||||
serde_json::to_string_pretty(v)
|
||||
.unwrap_or(String::from("Error serializing value"))
|
||||
})
|
||||
.collect(),
|
||||
Entries::Values(entries) => entries
|
||||
.iter()
|
||||
.map(|(_, v)| {
|
||||
serde_json::to_string_pretty(v)
|
||||
.unwrap_or(String::from("Error serializing value"))
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let value_display = Paragraph::new(
|
||||
app.list_state
|
||||
|
||||
@ -130,6 +130,16 @@ impl<'a, DB: Database> DbTool<'a, DB> {
|
||||
if let Ok((k, v)) = row {
|
||||
let (key, value) = (k.into_key(), v.into_value());
|
||||
|
||||
if key.len() + value.len() < filter.min_row_size {
|
||||
return None
|
||||
}
|
||||
if key.len() < filter.min_key_size {
|
||||
return None
|
||||
}
|
||||
if value.len() < filter.min_value_size {
|
||||
return None
|
||||
}
|
||||
|
||||
let result = || {
|
||||
if filter.only_count {
|
||||
return None
|
||||
@ -213,6 +223,12 @@ pub struct ListFilter {
|
||||
pub len: usize,
|
||||
/// Sequence of bytes that will be searched on values and keys from the database.
|
||||
pub search: Vec<u8>,
|
||||
/// Minimum row size.
|
||||
pub min_row_size: usize,
|
||||
/// Minimum key size.
|
||||
pub min_key_size: usize,
|
||||
/// Minimum value size.
|
||||
pub min_value_size: usize,
|
||||
/// Reverse order of entries.
|
||||
pub reverse: bool,
|
||||
/// Only counts the number of filtered entries without decoding and returning them.
|
||||
@ -220,11 +236,6 @@ pub struct ListFilter {
|
||||
}
|
||||
|
||||
impl ListFilter {
|
||||
/// Creates a new [`ListFilter`].
|
||||
pub fn new(skip: usize, len: usize, search: Vec<u8>, reverse: bool, only_count: bool) -> Self {
|
||||
ListFilter { skip, len, search, reverse, only_count }
|
||||
}
|
||||
|
||||
/// If `search` has a list of bytes, then filter for rows that have this sequence.
|
||||
pub fn has_search(&self) -> bool {
|
||||
!self.search.is_empty()
|
||||
|
||||
@ -129,6 +129,12 @@ impl<V: Value> RawValue<V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Value> From<V> for RawValue<V> {
|
||||
fn from(value: V) -> Self {
|
||||
RawValue::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for RawValue<Vec<u8>> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.value
|
||||
|
||||
Reference in New Issue
Block a user