1use std::str::FromStr;
21use std::sync::Arc;
22use std::time::Instant;
23
24use datafusion::arrow::array::{ArrayRef, StringArray};
25use datafusion::arrow::datatypes::{DataType, Field, Schema};
26use datafusion::arrow::record_batch::RecordBatch;
27use datafusion::common::Result;
28use datafusion::error::DataFusionError;
29use datafusion::prelude::SessionContext;
30
31use crate::functions::{display_all_functions, Function};
32use crate::print_format::PrintFormat;
33use crate::print_options::PrintOptions;
34
35#[derive(Debug)]
37pub enum Command {
38 Quit,
39 Help,
40 ListTables,
41 DescribeTable(String),
42 ListFunctions,
43 SearchFunctions(String),
44 QuietMode(Option<bool>),
45 OutputFormat(Option<String>),
46}
47
48pub enum OutputFormat {
49 ChangeFormat(String),
50}
51
52impl Command {
53 pub async fn execute(
54 &self,
55 ctx: &SessionContext,
56 print_options: &mut PrintOptions,
57 ) -> Result<()> {
58 let now = Instant::now();
59 match self {
60 Self::Help =>
61 {
63 print_options.print_batches(
64 Arc::new(Schema::empty()),
65 &[all_commands_info()],
66 now,
67 )
68 }
69 Self::ListTables => {
70 let df = ctx.sql("SHOW TABLES").await?;
71 let schema = Arc::new(df.schema().as_arrow().clone());
72 let batches = df.collect().await?;
73 print_options.print_batches(schema, &batches, now)
74 }
75 Self::DescribeTable(name) => {
76 let df = ctx.sql(&format!("SHOW COLUMNS FROM {name}")).await?;
77 let schema = Arc::new(df.schema().as_arrow().clone());
78 let batches = df.collect().await?;
79 print_options.print_batches(schema, &batches, now)
80 }
81 Self::QuietMode(quiet) => {
82 if let Some(quiet) = quiet {
83 print_options.quiet = *quiet;
84 println!(
85 "Quiet mode set to {}",
86 if print_options.quiet { "true" } else { "false" }
87 );
88 } else {
89 println!(
90 "Quiet mode is {}",
91 if print_options.quiet { "true" } else { "false" }
92 );
93 }
94 Ok(())
95 }
96 Self::Quit => Err(DataFusionError::Internal(
97 "Unexpected quit, this should be handled outside".to_string(),
98 )),
99 Self::ListFunctions => display_all_functions(),
100 Self::SearchFunctions(function) => {
101 if let Ok(func) = function.parse::<Function>() {
102 let details = func.function_details()?;
103 println!("{details}");
104 Ok(())
105 } else {
106 let msg = format!("{function} is not a supported function");
107 Err(DataFusionError::NotImplemented(msg))
108 }
109 }
110 Self::OutputFormat(_) => Err(DataFusionError::Internal(
111 "Unexpected change output format, this should be handled outside"
112 .to_string(),
113 )),
114 }
115 }
116
117 fn get_name_and_description(&self) -> (&'static str, &'static str) {
118 match self {
119 Self::Quit => ("\\q", "quit ballista-cli"),
120 Self::ListTables => ("\\d", "list tables"),
121 Self::DescribeTable(_) => ("\\d name", "describe table"),
122 Self::Help => ("\\?", "help"),
123 Self::ListFunctions => ("\\h", "function list"),
124 Self::SearchFunctions(_) => ("\\h function", "search function"),
125 Self::QuietMode(_) => ("\\quiet (true|false)?", "print or set quiet mode"),
126 Self::OutputFormat(_) => {
127 ("\\pset [NAME [VALUE]]", "set table output option\n(format)")
128 }
129 }
130 }
131}
132
133const ALL_COMMANDS: [Command; 8] = [
134 Command::ListTables,
135 Command::DescribeTable(String::new()),
136 Command::Quit,
137 Command::Help,
138 Command::ListFunctions,
139 Command::SearchFunctions(String::new()),
140 Command::QuietMode(None),
141 Command::OutputFormat(None),
142];
143
144fn all_commands_info() -> RecordBatch {
145 let schema = Arc::new(Schema::new(vec![
146 Field::new("Command", DataType::Utf8, false),
147 Field::new("Description", DataType::Utf8, false),
148 ]));
149 let (names, description): (Vec<&str>, Vec<&str>) = ALL_COMMANDS
150 .into_iter()
151 .map(|c| c.get_name_and_description())
152 .unzip();
153 RecordBatch::try_new(
154 schema,
155 [names, description]
156 .into_iter()
157 .map(|i| Arc::new(StringArray::from(i)) as ArrayRef)
158 .collect::<Vec<_>>(),
159 )
160 .expect("This should not fail")
161}
162
163impl FromStr for Command {
164 type Err = ();
165
166 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
167 let (c, arg) = if let Some((a, b)) = s.split_once(' ') {
168 (a, Some(b))
169 } else {
170 (s, None)
171 };
172 Ok(match (c, arg) {
173 ("q", None) => Self::Quit,
174 ("d", None) => Self::ListTables,
175 ("d", Some(name)) => Self::DescribeTable(name.into()),
176 ("?", None) => Self::Help,
177 ("h", None) => Self::ListFunctions,
178 ("h", Some(function)) => Self::SearchFunctions(function.into()),
179 ("quiet", Some("true" | "t" | "yes" | "y" | "on")) => {
180 Self::QuietMode(Some(true))
181 }
182 ("quiet", Some("false" | "f" | "no" | "n" | "off")) => {
183 Self::QuietMode(Some(false))
184 }
185 ("quiet", None) => Self::QuietMode(None),
186 ("pset", Some(subcommand)) => {
187 Self::OutputFormat(Some(subcommand.to_string()))
188 }
189 ("pset", None) => Self::OutputFormat(None),
190 _ => return Err(()),
191 })
192 }
193}
194
195impl FromStr for OutputFormat {
196 type Err = ();
197
198 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
199 let (c, arg) = if let Some((a, b)) = s.split_once(' ') {
200 (a, Some(b))
201 } else {
202 (s, None)
203 };
204 Ok(match (c, arg) {
205 ("format", Some(format)) => Self::ChangeFormat(format.to_string()),
206 _ => return Err(()),
207 })
208 }
209}
210
211impl OutputFormat {
212 pub async fn execute(&self, print_options: &mut PrintOptions) -> Result<()> {
213 match self {
214 Self::ChangeFormat(format) => {
215 if let Ok(format) = format.parse::<PrintFormat>() {
216 print_options.format = format;
217 println!("Output format is {:?}.", print_options.format);
218 Ok(())
219 } else {
220 Err(DataFusionError::Execution(format!(
221 "{:?} is not a valid format type [possible values: {:?}]",
222 format,
223 "TO BE FIXED", )))
225 }
226 }
227 }
228 }
229}