use crate::exec::exec_and_print;
use crate::functions::{display_all_functions, Function};
use crate::print_format::PrintFormat;
use crate::print_options::PrintOptions;
use clap::ValueEnum;
use datafusion::arrow::array::{ArrayRef, StringArray};
use datafusion::arrow::datatypes::{DataType, Field, Schema};
use datafusion::arrow::record_batch::RecordBatch;
use datafusion::common::exec_err;
use datafusion::common::instant::Instant;
use datafusion::error::Result;
use sedona::context::SedonaContext;
use std::str::FromStr;
use std::sync::Arc;
#[derive(Debug)]
pub enum Command {
Quit,
Help,
ListTables,
DescribeTableStmt(String),
ListFunctions,
SearchFunctions(String),
QuietMode(Option<bool>),
OutputFormat(Option<String>),
}
pub enum OutputFormat {
ChangeFormat(String),
}
impl Command {
pub async fn execute(
&self,
ctx: &SedonaContext,
print_options: &mut PrintOptions,
) -> Result<()> {
match self {
Self::Help => {
let now = Instant::now();
let command_batch = all_commands_info();
let schema = command_batch.schema();
let num_rows = command_batch.num_rows();
print_options.print_batches(schema, &[command_batch], now, num_rows, ctx)
}
Self::ListTables => exec_and_print(ctx, print_options, "SHOW TABLES".into()).await,
Self::DescribeTableStmt(name) => {
exec_and_print(ctx, print_options, format!("SHOW COLUMNS FROM {name}")).await
}
Self::QuietMode(quiet) => {
if let Some(quiet) = quiet {
print_options.quiet = *quiet;
println!("Quiet mode set to {}", print_options.quiet);
} else {
println!("Quiet mode is {}", print_options.quiet);
}
Ok(())
}
Self::Quit => exec_err!("Unexpected quit, this should be handled outside"),
Self::ListFunctions => display_all_functions(),
Self::SearchFunctions(function) => {
if let Ok(func) = function.parse::<Function>() {
let details = func.function_details()?;
println!("{details}");
Ok(())
} else {
exec_err!("{function} is not a supported function")
}
}
Self::OutputFormat(_) => {
exec_err!("Unexpected change output format, this should be handled outside")
}
}
}
fn get_name_and_description(&self) -> (&'static str, &'static str) {
match self {
Self::Quit => ("\\q", "quit sedonadb-cli"),
Self::ListTables => ("\\d", "list tables"),
Self::DescribeTableStmt(_) => ("\\d name", "describe table"),
Self::Help => ("\\?", "help"),
Self::ListFunctions => ("\\h", "function list"),
Self::SearchFunctions(_) => ("\\h function", "search function"),
Self::QuietMode(_) => ("\\quiet (true|false)?", "print or set quiet mode"),
Self::OutputFormat(_) => ("\\pset [NAME [VALUE]]", "set table output option\n(format)"),
}
}
}
const ALL_COMMANDS: [Command; 8] = [
Command::ListTables,
Command::DescribeTableStmt(String::new()),
Command::Quit,
Command::Help,
Command::ListFunctions,
Command::SearchFunctions(String::new()),
Command::QuietMode(None),
Command::OutputFormat(None),
];
fn all_commands_info() -> RecordBatch {
let schema = Arc::new(Schema::new(vec![
Field::new("Command", DataType::Utf8, false),
Field::new("Description", DataType::Utf8, false),
]));
let (names, description): (Vec<&str>, Vec<&str>) = ALL_COMMANDS
.into_iter()
.map(|c| c.get_name_and_description())
.unzip();
RecordBatch::try_new(
schema,
[names, description]
.into_iter()
.map(|i| Arc::new(StringArray::from(i)) as ArrayRef)
.collect::<Vec<_>>(),
)
.expect("This should not fail")
}
impl FromStr for Command {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (c, arg) = if let Some((a, b)) = s.split_once(' ') {
(a, Some(b))
} else {
(s, None)
};
Ok(match (c, arg) {
("q", None) => Self::Quit,
("d", None) => Self::ListTables,
("d", Some(name)) => Self::DescribeTableStmt(name.into()),
("?", None) => Self::Help,
("h", None) => Self::ListFunctions,
("h", Some(function)) => Self::SearchFunctions(function.into()),
("quiet", Some("true" | "t" | "yes" | "y" | "on")) => Self::QuietMode(Some(true)),
("quiet", Some("false" | "f" | "no" | "n" | "off")) => Self::QuietMode(Some(false)),
("quiet", None) => Self::QuietMode(None),
("pset", Some(subcommand)) => Self::OutputFormat(Some(subcommand.to_string())),
("pset", None) => Self::OutputFormat(None),
_ => return Err(()),
})
}
}
impl FromStr for OutputFormat {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (c, arg) = if let Some((a, b)) = s.split_once(' ') {
(a, Some(b))
} else {
(s, None)
};
Ok(match (c, arg) {
("format", Some(format)) => Self::ChangeFormat(format.to_string()),
_ => return Err(()),
})
}
}
impl OutputFormat {
pub async fn execute(&self, print_options: &mut PrintOptions) -> Result<()> {
match self {
Self::ChangeFormat(format) => {
if let Ok(format) = format.parse::<PrintFormat>() {
print_options.format = format;
println!("Output format is {:?}.", print_options.format);
Ok(())
} else {
exec_err!(
"{:?} is not a valid format type [possible values: {:?}]",
format,
PrintFormat::value_variants()
)
}
}
}
}
}