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