cli: Add tool in case `sqlite3` is unavailable
Adds the `rad node db exec <query>` command. Easy way to run sqlite queries if the sqlite3 CLI is not available or too old.
This commit is contained in:
parent
ad7ba82e6a
commit
bd8e0ebcda
|
|
@ -13,6 +13,8 @@ use crate::terminal as term;
|
||||||
use crate::terminal::args::{Args, Error, Help};
|
use crate::terminal::args::{Args, Error, Help};
|
||||||
use crate::terminal::Element as _;
|
use crate::terminal::Element as _;
|
||||||
|
|
||||||
|
#[path = "node/commands.rs"]
|
||||||
|
mod commands;
|
||||||
#[path = "node/control.rs"]
|
#[path = "node/control.rs"]
|
||||||
pub mod control;
|
pub mod control;
|
||||||
#[path = "node/events.rs"]
|
#[path = "node/events.rs"]
|
||||||
|
|
@ -35,6 +37,7 @@ Usage
|
||||||
rad node routing [--rid <rid>] [--nid <nid>] [--json] [<option>...]
|
rad node routing [--rid <rid>] [--nid <nid>] [--json] [<option>...]
|
||||||
rad node events [--timeout <secs>] [-n <count>] [<option>...]
|
rad node events [--timeout <secs>] [-n <count>] [<option>...]
|
||||||
rad node config [--addresses]
|
rad node config [--addresses]
|
||||||
|
rad node db <command> [<option>..]
|
||||||
|
|
||||||
For `<node-option>` see `radicle-node --help`.
|
For `<node-option>` see `radicle-node --help`.
|
||||||
|
|
||||||
|
|
@ -73,6 +76,9 @@ pub enum Operation {
|
||||||
Config {
|
Config {
|
||||||
addresses: bool,
|
addresses: bool,
|
||||||
},
|
},
|
||||||
|
Db {
|
||||||
|
args: Vec<OsString>,
|
||||||
|
},
|
||||||
Events {
|
Events {
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
count: usize,
|
count: usize,
|
||||||
|
|
@ -100,6 +106,7 @@ pub enum Operation {
|
||||||
pub enum OperationName {
|
pub enum OperationName {
|
||||||
Connect,
|
Connect,
|
||||||
Config,
|
Config,
|
||||||
|
Db,
|
||||||
Events,
|
Events,
|
||||||
Routing,
|
Routing,
|
||||||
Logs,
|
Logs,
|
||||||
|
|
@ -136,6 +143,7 @@ impl Args for Options {
|
||||||
}
|
}
|
||||||
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
|
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
|
||||||
"connect" => op = Some(OperationName::Connect),
|
"connect" => op = Some(OperationName::Connect),
|
||||||
|
"db" => op = Some(OperationName::Db),
|
||||||
"events" => op = Some(OperationName::Events),
|
"events" => op = Some(OperationName::Events),
|
||||||
"logs" => op = Some(OperationName::Logs),
|
"logs" => op = Some(OperationName::Logs),
|
||||||
"config" => op = Some(OperationName::Config),
|
"config" => op = Some(OperationName::Config),
|
||||||
|
|
@ -188,6 +196,9 @@ impl Args for Options {
|
||||||
Value(val) if matches!(op, Some(OperationName::Start)) => {
|
Value(val) if matches!(op, Some(OperationName::Start)) => {
|
||||||
options.push(val);
|
options.push(val);
|
||||||
}
|
}
|
||||||
|
Value(val) if matches!(op, Some(OperationName::Db)) => {
|
||||||
|
options.push(val);
|
||||||
|
}
|
||||||
_ => return Err(anyhow!(arg.unexpected())),
|
_ => return Err(anyhow!(arg.unexpected())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,6 +211,7 @@ impl Args for Options {
|
||||||
timeout,
|
timeout,
|
||||||
},
|
},
|
||||||
OperationName::Config => Operation::Config { addresses },
|
OperationName::Config => Operation::Config { addresses },
|
||||||
|
OperationName::Db => Operation::Db { args: options },
|
||||||
OperationName::Events => Operation::Events { timeout, count },
|
OperationName::Events => Operation::Events { timeout, count },
|
||||||
OperationName::Routing => Operation::Routing { rid, nid, json },
|
OperationName::Routing => Operation::Routing { rid, nid, json },
|
||||||
OperationName::Logs => Operation::Logs { lines },
|
OperationName::Logs => Operation::Logs { lines },
|
||||||
|
|
@ -235,6 +247,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
control::config(&node)?;
|
control::config(&node)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Operation::Db { args } => {
|
||||||
|
commands::db(&profile, args)?;
|
||||||
|
}
|
||||||
Operation::Sessions => {
|
Operation::Sessions => {
|
||||||
let sessions = control::sessions(&node)?;
|
let sessions = control::sessions(&node)?;
|
||||||
if let Some(table) = sessions {
|
if let Some(table) = sessions {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use radicle::Profile;
|
||||||
|
use radicle_term as term;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub enum Operation {
|
||||||
|
Exec { query: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn db(profile: &Profile, args: Vec<OsString>) -> anyhow::Result<()> {
|
||||||
|
use lexopt::prelude::*;
|
||||||
|
|
||||||
|
let mut parser = lexopt::Parser::from_args(args);
|
||||||
|
let mut op: Option<Operation> = None;
|
||||||
|
|
||||||
|
while let Some(arg) = parser.next()? {
|
||||||
|
match arg {
|
||||||
|
Value(cmd) if op.is_none() => match cmd.to_string_lossy().as_ref() {
|
||||||
|
"exec" => {
|
||||||
|
let val = parser
|
||||||
|
.value()
|
||||||
|
.map_err(|_| anyhow!("a query to execute must be provided for `exec`"))?;
|
||||||
|
op = Some(Operation::Exec {
|
||||||
|
query: val.to_string_lossy().to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
unknown => anyhow::bail!("unknown operation '{unknown}'"),
|
||||||
|
},
|
||||||
|
_ => return Err(anyhow!(arg.unexpected())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match op.ok_or_else(|| anyhow!("a command must be provided, eg. `rad node db exec`"))? {
|
||||||
|
Operation::Exec { query } => {
|
||||||
|
let db = profile.database_mut()?;
|
||||||
|
db.execute(query)?;
|
||||||
|
|
||||||
|
let changed = db.change_count();
|
||||||
|
if changed > 0 {
|
||||||
|
term::success!("{changed} row(s) affected.");
|
||||||
|
} else {
|
||||||
|
term::print(term::format::italic("No rows affected."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
//!
|
//!
|
||||||
//! The database schema is contained within the first migration. See [`version`], [`bump`] and
|
//! The database schema is contained within the first migration. See [`version`], [`bump`] and
|
||||||
//! [`migrate`] for how this works.
|
//! [`migrate`] for how this works.
|
||||||
|
use std::ops::Deref;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{fmt, time};
|
use std::{fmt, time};
|
||||||
|
|
@ -45,6 +46,14 @@ pub struct Database {
|
||||||
pub db: Arc<sql::ConnectionThreadSafe>,
|
pub db: Arc<sql::ConnectionThreadSafe>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for Database {
|
||||||
|
type Target = sql::ConnectionThreadSafe;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Database {
|
impl fmt::Debug for Database {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Database").finish()
|
f.debug_struct("Database").finish()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue