ballista_cli/
command.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Command within CLI
19
20use 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/// Command
36#[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            // TODO need to provide valid schema
62            {
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", //PrintFormat::value_variants()
224                    )))
225                }
226            }
227        }
228    }
229}