Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Add tool in case `sqlite3` is unavailable
cloudhead committed 2 years ago
commit bd8e0ebcda8f6f06dc20641a71614e3778a43fea
parent ad7ba82e6adfc78510c7c49dee0ab908bd8556bb
3 files changed +73 -0
modified radicle-cli/src/commands/node.rs
@@ -13,6 +13,8 @@ use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
use crate::terminal::Element as _;

+
#[path = "node/commands.rs"]
+
mod commands;
#[path = "node/control.rs"]
pub mod control;
#[path = "node/events.rs"]
@@ -35,6 +37,7 @@ Usage
    rad node routing [--rid <rid>] [--nid <nid>] [--json] [<option>...]
    rad node events [--timeout <secs>] [-n <count>] [<option>...]
    rad node config [--addresses]
+
    rad node db <command> [<option>..]

    For `<node-option>` see `radicle-node --help`.

@@ -73,6 +76,9 @@ pub enum Operation {
    Config {
        addresses: bool,
    },
+
    Db {
+
        args: Vec<OsString>,
+
    },
    Events {
        timeout: time::Duration,
        count: usize,
@@ -100,6 +106,7 @@ pub enum Operation {
pub enum OperationName {
    Connect,
    Config,
+
    Db,
    Events,
    Routing,
    Logs,
@@ -136,6 +143,7 @@ impl Args for Options {
                }
                Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
                    "connect" => op = Some(OperationName::Connect),
+
                    "db" => op = Some(OperationName::Db),
                    "events" => op = Some(OperationName::Events),
                    "logs" => op = Some(OperationName::Logs),
                    "config" => op = Some(OperationName::Config),
@@ -188,6 +196,9 @@ impl Args for Options {
                Value(val) if matches!(op, Some(OperationName::Start)) => {
                    options.push(val);
                }
+
                Value(val) if matches!(op, Some(OperationName::Db)) => {
+
                    options.push(val);
+
                }
                _ => return Err(anyhow!(arg.unexpected())),
            }
        }
@@ -200,6 +211,7 @@ impl Args for Options {
                timeout,
            },
            OperationName::Config => Operation::Config { addresses },
+
            OperationName::Db => Operation::Db { args: options },
            OperationName::Events => Operation::Events { timeout, count },
            OperationName::Routing => Operation::Routing { rid, nid, json },
            OperationName::Logs => Operation::Logs { lines },
@@ -235,6 +247,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                control::config(&node)?;
            }
        }
+
        Operation::Db { args } => {
+
            commands::db(&profile, args)?;
+
        }
        Operation::Sessions => {
            let sessions = control::sessions(&node)?;
            if let Some(table) = sessions {
added radicle-cli/src/commands/node/commands.rs
@@ -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(())
+
}
modified radicle/src/node/db.rs
@@ -7,6 +7,7 @@
//!
//! The database schema is contained within the first migration. See [`version`], [`bump`] and
//! [`migrate`] for how this works.
+
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc;
use std::{fmt, time};
@@ -45,6 +46,14 @@ pub struct Database {
    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 {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Database").finish()