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