Use new client library in CLI sql command (#1561)

This commit is contained in:
Rushmore Mushambi 2022-12-30 23:27:19 +02:00 committed by GitHub
parent d7c26bd64b
commit 7c199ff586
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 60 deletions

View file

@ -27,6 +27,10 @@ We would love it if you could star the repository (https://github.com/surrealdb/
---------- ----------
"; ";
fn split_endpoint(v: &str) -> (&str, &str) {
v.split_once("://").unwrap_or_default()
}
fn file_valid(v: &str) -> Result<(), String> { fn file_valid(v: &str) -> Result<(), String> {
match v { match v {
v if !v.is_empty() => Ok(()), v if !v.is_empty() => Ok(()),
@ -54,9 +58,9 @@ fn path_valid(v: &str) -> Result<(), String> {
} }
fn conn_valid(v: &str) -> Result<(), String> { fn conn_valid(v: &str) -> Result<(), String> {
match v { let scheme = split_endpoint(v).0;
v if v.starts_with("http://") => Ok(()), match scheme {
v if v.starts_with("https://") => Ok(()), "http" | "https" | "ws" | "wss" | "fdb" | "mem" | "rocksdb" | "file" | "tikv" => Ok(()),
_ => Err(String::from( _ => Err(String::from(
"\ "\
Provide a valid database connection string\ Provide a valid database connection string\
@ -459,7 +463,7 @@ pub fn init() {
let matches = setup.get_matches(); let matches = setup.get_matches();
let output = match matches.subcommand() { let output = match matches.subcommand() {
Some(("sql", m)) => sql::init(m), Some(("sql", m)) => futures::executor::block_on(sql::init(m)),
Some(("start", m)) => start::init(m), Some(("start", m)) => start::init(m),
Some(("backup", m)) => backup::init(m), Some(("backup", m)) => backup::init(m),
Some(("import", m)) => import::init(m), Some(("import", m)) => import::init(m),

View file

@ -1,50 +1,62 @@
use crate::cnf::SERVER_AGENT;
use crate::err::Error; use crate::err::Error;
use reqwest::blocking::Client;
use reqwest::blocking::Response;
use reqwest::header::ACCEPT;
use reqwest::header::USER_AGENT;
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::Editor; use rustyline::Editor;
use serde_json::Value; use serde_json::Value;
use surrealdb::engines::any::connect;
use surrealdb::error::Api as ApiError;
use surrealdb::opt::auth::Root;
use surrealdb::sql;
use surrealdb::sql::statements::SetStatement;
use surrealdb::sql::Statement;
use surrealdb::Error as SurrealError;
use surrealdb::Response;
pub fn init(matches: &clap::ArgMatches) -> Result<(), Error> { pub async fn init(matches: &clap::ArgMatches) -> Result<(), Error> {
// Set the default logging level // Set the default logging level
crate::cli::log::init(3); crate::cli::log::init(0);
// Parse all other cli arguments // Parse all other cli arguments
let user = matches.value_of("user").unwrap(); let username = matches.value_of("user").unwrap();
let pass = matches.value_of("pass").unwrap(); let password = matches.value_of("pass").unwrap();
let conn = matches.value_of("conn").unwrap(); let endpoint = matches.value_of("conn").unwrap();
let ns = matches.value_of("ns"); let mut ns = matches.value_of("ns").map(str::to_string);
let db = matches.value_of("db"); let mut db = matches.value_of("db").map(str::to_string);
// If we should pretty-print responses // If we should pretty-print responses
let pretty = matches.is_present("pretty"); let pretty = matches.is_present("pretty");
// Set the correct import URL
let conn = format!("{conn}/sql");
// Make a new remote request // Make a new remote request
let res = Client::new() let client = connect(endpoint).await?;
.post(conn) // Sign in to the server if the specified dabatabase engine supports it
.header(USER_AGENT, SERVER_AGENT) let root = Root {
.header(ACCEPT, "application/json") username,
.basic_auth(user, Some(pass)); password,
// Add NS header if specified
let res = match ns {
Some(ns) => res.header("NS", ns),
None => res,
};
// Add DB header if specified
let res = match db {
Some(db) => res.header("DB", db),
None => res,
}; };
if let Err(error) = client.signin(root).await {
match error {
// Authentication not supported by this engine, we can safely continue
SurrealError::Api(ApiError::AuthNotSupported) => {}
error => {
return Err(error.into());
}
}
}
// Create a new terminal REPL // Create a new terminal REPL
let mut rl = Editor::<()>::new().unwrap(); let mut rl = Editor::<()>::new().unwrap();
// Load the command-line history // Load the command-line history
let _ = rl.load_history("history.txt"); let _ = rl.load_history("history.txt");
// Configure the prompt
let mut prompt = "> ".to_owned();
// Loop over each command-line input // Loop over each command-line input
loop { loop {
// Use namespace / database if specified
if let (Some(namespace), Some(database)) = (&ns, &db) {
match client.use_ns(namespace).use_db(database).await {
Ok(()) => {
prompt = format!("{namespace}/{database}> ");
}
Err(error) => eprintln!("{error}"),
}
}
// Prompt the user to input SQL // Prompt the user to input SQL
let readline = rl.readline("> "); let readline = rl.readline(&prompt);
// Check the user input // Check the user input
match readline { match readline {
// The user typed a query // The user typed a query
@ -55,14 +67,38 @@ pub fn init(matches: &clap::ArgMatches) -> Result<(), Error> {
} }
// Add the entry to the history // Add the entry to the history
rl.add_history_entry(line.as_str()); rl.add_history_entry(line.as_str());
// Clone the request infallibly
let res = res.try_clone().unwrap();
// Complete the request // Complete the request
let res = res.body(line).send(); match sql::parse(&line) {
// Get the request response Ok(query) => {
match process(pretty, res) { for statement in query.iter() {
Ok(v) => println!("{v}"), match statement {
Err(e) => eprintln!("{e}"), Statement::Use(stmt) => {
if let Some(namespace) = &stmt.ns {
ns = Some(namespace.clone());
}
if let Some(database) = &stmt.db {
db = Some(database.clone());
}
}
Statement::Set(SetStatement {
name,
what,
}) => {
if let Err(error) = client.set(name, what).await {
eprintln!("{error}");
}
}
_ => {}
}
}
let res = client.query(query).await;
// Get the request response
match process(pretty, res) {
Ok(v) => println!("{v}"),
Err(e) => eprintln!("{e}"),
}
}
Err(error) => eprintln!("{error}"),
} }
} }
// The user types CTRL-C // The user types CTRL-C
@ -86,28 +122,20 @@ pub fn init(matches: &clap::ArgMatches) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn process(pretty: bool, res: reqwest::Result<Response>) -> Result<String, Error> { fn process(pretty: bool, res: surrealdb::Result<Response>) -> Result<String, Error> {
// Catch any errors // Catch any errors
let res = res?; let values: Vec<Value> = res?.take(0)?;
// Process the TEXT response let value = Value::Array(values);
let res = res.text()?;
// Check if we should prettify // Check if we should prettify
match pretty { match pretty {
// Don't prettify the response // Don't prettify the response
false => Ok(res), false => Ok(value.to_string()),
// Yes prettify the response // Yes prettify the response
true => match res.is_empty() { true => {
// This was an empty response // Pretty the JSON response
true => Ok(res), let res = serde_json::to_string_pretty(&value)?;
// Let's make this response pretty // Everything processed OK
false => { Ok(res)
// Parse the JSON response }
let res: Value = serde_json::from_str(&res)?;
// Pretty the JSON response
let res = serde_json::to_string_pretty(&res)?;
// Everything processed OK
Ok(res)
}
},
} }
} }

View file

@ -6,7 +6,7 @@ use serde_json::error::Error as JsonError;
use serde_pack::encode::Error as PackError; use serde_pack::encode::Error as PackError;
use std::io::Error as IoError; use std::io::Error as IoError;
use std::string::FromUtf8Error as Utf8Error; use std::string::FromUtf8Error as Utf8Error;
use surrealdb::err::Error as DbError; use surrealdb::Error as SurrealError;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -30,7 +30,7 @@ pub enum Error {
InvalidStorage, InvalidStorage,
#[error("There was a problem with the database: {0}")] #[error("There was a problem with the database: {0}")]
Db(#[from] DbError), Db(#[from] SurrealError),
#[error("Couldn't open the specified file: {0}")] #[error("Couldn't open the specified file: {0}")]
Io(#[from] IoError), Io(#[from] IoError),
@ -73,3 +73,9 @@ impl From<JWTError> for Error {
Error::InvalidAuth Error::InvalidAuth
} }
} }
impl From<surrealdb::error::Db> for Error {
fn from(error: surrealdb::error::Db) -> Error {
Error::Db(error.into())
}
}