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        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            // TODO need to provide valid schema
66            {
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", //PrintFormat::value_variants()
229                    )))
230                }
231            }
232        }
233    }
234}