feat(bin): --raw and min size arguments for db commands (#5113)

This commit is contained in:
Alexey Shekhirin
2023-10-23 13:08:48 +01:00
committed by GitHub
parent 2b71f0f6b0
commit d05b32449b
5 changed files with 155 additions and 23 deletions

View File

@ -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.");

View File

@ -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()
}
})??;

View File

@ -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

View File

@ -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()

View File

@ -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