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