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