From fde319994351839b5ce20a783b4d2df478034df2 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sat, 24 Aug 2024 16:49:12 +0200 Subject: [PATCH 01/17] Checkin --- Cargo.lock | 12 + Cargo.toml | 1 + src/app/core.rs | 91 +++-- src/app/datafusion/context.rs | 34 +- src/app/editor/mod.rs | 735 +++++++++++++++++----------------- src/app/handlers/edit.rs | 43 +- src/app/handlers/logging.rs | 34 +- src/app/handlers/mod.rs | 11 +- src/app/handlers/normal.rs | 41 +- src/app/handlers/rc.rs | 19 +- src/app/ui.rs | 144 +++---- src/events/mod.rs | 12 +- src/lib.rs | 63 ++- src/main.rs | 7 +- 14 files changed, 679 insertions(+), 568 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0cdb41c..7ab6815 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1469,6 +1469,7 @@ dependencies = [ "serde_json", "tokio", "tui-logger", + "tui-textarea", "unicode-width", ] @@ -3649,6 +3650,17 @@ dependencies = [ "ratatui", ] +[[package]] +name = "tui-textarea" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c07084342a575cea919eea996b9658a358c800b03d435df581c1d7c60e065a" +dependencies = [ + "crossterm 0.28.1", + "ratatui", + "unicode-width", +] + [[package]] name = "typed-builder" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 41f19d1..81d80f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ ratatui = { version = "0.28", default-features = false, features = [ tui-logger = {version="0.12", features=["crossterm"]} tokio = { version = "1", features = ["full"] } unicode-width = "0.1.9" +tui-textarea = "0.6.1" [features] s3 = ["datafusion-objectstore-s3"] diff --git a/src/app/core.rs b/src/app/core.rs index 56a06cb..63d2d4d 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -20,6 +20,10 @@ use std::io::{BufWriter, Write}; use datafusion::prelude::SessionConfig; use log::info; +use ratatui::buffer::Buffer; +use ratatui::crossterm::event::KeyEvent; +use ratatui::layout::Rect; +use ratatui::widgets::Widget; use tui_logger::TuiWidgetState; use crate::app::datafusion::context::{Context, QueryResults}; @@ -27,7 +31,9 @@ use crate::app::editor::Editor; use crate::app::error::Result; use crate::app::handlers::key_event_handler; use crate::cli::args::Args; -use crate::events::Key; +use crate::events::{Event, Key}; + +use super::ui::{draw_context_tab, draw_logs_tab, draw_query_history_tab, draw_sql_editor_tab}; const DATAFUSION_RC: &str = ".datafusion/.datafusionrc"; @@ -124,13 +130,13 @@ pub struct Logs { } /// App holds the state of the application -pub struct App { +pub struct App<'app> { /// Application tabs pub tab_item: TabItem, /// Current input mode pub input_mode: InputMode, /// SQL Editor and it's state - pub editor: Editor, + pub editor: Editor<'app>, /// DataFusion `ExecutionContext` pub context: Context, /// Results from DataFusion query @@ -139,24 +145,25 @@ pub struct App { pub logs: Logs, } -impl App { - pub async fn new(args: Args) -> App { +impl<'app> App<'app> { + pub fn new(args: Args, editor: Editor<'app>) -> Self { let execution_config = SessionConfig::new().with_information_schema(true); - let mut ctx: Context = match (args.host, args.port) { - (Some(ref h), Some(p)) => Context::new_remote(h, p).await.unwrap(), - _ => Context::new_local(&execution_config).await, - }; + let ctx = Context::new_local(&execution_config); + // let mut ctx: Context = match (args.host, args.port) { + // (Some(ref h), Some(p)) => Context::new_remote(h, p).await.unwrap(), + // _ => Context::new_local(&execution_config).await, + // }; let files = args.file; let rc = App::get_rc_files(args.rc); - if !files.is_empty() { - ctx.exec_files(files).await - } else if !rc.is_empty() { - info!("Executing '~/.datafusion/.datafusionrc' file"); - ctx.exec_files(rc).await - } + // if !files.is_empty() { + // ctx.exec_files(files).await + // } else if !rc.is_empty() { + // info!("Executing '~/.datafusion/.datafusionrc' file"); + // ctx.exec_files(rc).await + // } let log_state = TuiWidgetState::default(); @@ -165,7 +172,8 @@ impl App { App { tab_item: Default::default(), input_mode: Default::default(), - editor: Editor::default(), + editor, + // editor: Editor::default(), context: ctx, query_results: None, logs, @@ -189,22 +197,29 @@ impl App { } } + pub async fn event_handler(&'app mut self, event: Event) -> Result { + match event { + Event::KeyInput(k) => key_event_handler(self, k).await, + _ => Ok(AppReturn::Continue), + } + } + pub async fn reload_rc(&mut self) { let rc = App::get_rc_files(None); self.context.exec_files(rc).await; info!("Reloaded .datafusionrc"); } - pub fn write_rc(&self) -> Result<()> { - let text = self.editor.input.combine_lines(); - let rc = App::get_rc_files(None); - let file = File::create(rc[0].clone())?; - let mut writer = BufWriter::new(file); - writer.write_all(text.as_bytes())?; - Ok(()) - } + // pub fn write_rc(&self) -> Result<()> { + // let text = self.editor.input.combine_lines(); + // let rc = App::get_rc_files(None); + // let file = File::create(rc[0].clone())?; + // let mut writer = BufWriter::new(file); + // writer.write_all(text.as_bytes())?; + // Ok(()) + // } - pub async fn key_handler(&mut self, key: Key) -> AppReturn { + pub async fn key_handler(&'app mut self, key: KeyEvent) -> AppReturn { key_event_handler(self, key).await.unwrap() } @@ -213,6 +228,32 @@ impl App { } } +impl Widget for &App<'_> { + /// Note: Ratatui uses Immediate Mode rendering (i.e. the entire UI is redrawn) + /// on every frame based on application state. There is no permanent widget object + /// in memory. + fn render(self, area: Rect, buf: &mut Buffer) { + match self.tab_item { + TabItem::Editor => draw_sql_editor_tab(self, area, buf), + TabItem::QueryHistory => draw_query_history_tab(self, area, buf), + TabItem::Context => draw_context_tab(self, area, buf), + TabItem::Logs => draw_logs_tab(self, area, buf), + } + // let vertical = Layout::vertical([ + // Constraint::Length(1), + // Constraint::Min(0), + // Constraint::Length(1), + // ]); + // let [header_area, inner_area, footer_area] = vertical.areas(area); + // + // let horizontal = Layout::horizontal([Constraint::Min(0)]); + // let [tabs_area] = horizontal.areas(header_area); + // self.render_tabs(tabs_area, buf); + // self.state.tabs.selected.render(inner_area, buf, self); + // self.render_footer(footer_area, buf); + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/app/datafusion/context.rs b/src/app/datafusion/context.rs index 9f07d29..4f3d93c 100644 --- a/src/app/datafusion/context.rs +++ b/src/app/datafusion/context.rs @@ -72,7 +72,7 @@ impl Context { } /// create a local context using the given config - pub async fn new_local(config: &SessionConfig) -> Context { + pub fn new_local(config: &SessionConfig) -> Context { debug!("Created ExecutionContext"); let ctx = SessionContext::with_config(config.clone()); @@ -233,6 +233,7 @@ impl BallistaContext { mod test { use crate::app::core::{App, TabItem}; use crate::app::datafusion::context::{QueryResults, QueryResultsMeta}; + use crate::app::editor::Editor; use crate::app::handlers::execute_query; use crate::app::ui::Scroll; use crate::cli::args::mock_standard_args; @@ -262,12 +263,14 @@ mod test { #[tokio::test] async fn test_select() { let args = mock_standard_args(); - let mut app = App::new(args).await; + let ed = Editor::default(); + let mut app = App::new(args, ed); let query = "SELECT 1"; - for char in query.chars() { - app.editor.input.append_char(char).unwrap(); - } + app.editor.input.insert_str(query); + // for char in query.chars() { + // app.editor.input.append_char(char).unwrap(); + // } execute_query(&mut app).await.unwrap(); @@ -297,9 +300,10 @@ mod test { let mut app = App::new(args).await; let query = "SELE 1"; - for char in query.chars() { - app.editor.input.append_char(char).unwrap(); - } + app.editor.input.insert_str(query); + // for char in query.chars() { + // app.editor.input.append_char(char).unwrap(); + // } execute_query(&mut app).await.unwrap(); @@ -325,9 +329,10 @@ mod test { let query = "CREATE TABLE abc AS VALUES (1,2,3)"; - for char in query.chars() { - app.editor.input.append_char(char).unwrap(); - } + app.editor.input.insert_str(query); + // for char in query.chars() { + // app.editor.input.append_char(char).unwrap(); + // } execute_query(&mut app).await.unwrap(); @@ -360,9 +365,10 @@ mod test { let mut app = App::new(args).await; let query = "SELECT 1;SELECT 2;"; - for char in query.chars() { - app.editor.input.append_char(char).unwrap(); - } + app.editor.input.insert_str(query); + // for char in query.chars() { + // app.editor.input.append_char(char).unwrap(); + // } execute_query(&mut app).await.unwrap(); diff --git a/src/app/editor/mod.rs b/src/app/editor/mod.rs index 9240f3e..a27d085 100644 --- a/src/app/editor/mod.rs +++ b/src/app/editor/mod.rs @@ -19,6 +19,7 @@ use log::debug; use std::cmp; use std::fs::File; use std::io::{self, BufRead, BufReader}; +use tui_textarea::TextArea; use unicode_width::UnicodeWidthStr; @@ -360,17 +361,17 @@ impl Input { } /// The entire editor and it's state -pub struct Editor { +pub struct Editor<'app> { /// Current value of the input box - pub input: Input, + pub input: TextArea<'app>, /// Flag if SQL statement was terminated with ';' pub sql_terminated: bool, /// History of QueryResultMeta pub history: Vec, } -impl Default for Editor { - fn default() -> Editor { - let input = Input::default(); +impl<'app> Default for Editor<'app> { + fn default() -> Editor<'app> { + let input = TextArea::default(); Editor { input, history: Vec::new(), @@ -379,371 +380,373 @@ impl Default for Editor { } } -impl Editor { - pub fn get_cursor_row(&self) -> u16 { - if self.input.current_row < MAX_EDITOR_LINES { - self.input.current_row - } else { - MAX_EDITOR_LINES - } - } - - pub fn get_cursor_column(&self) -> u16 { - self.input.cursor_column - } +impl Editor<'_> { + // pub fn get_cursor_row(&self) -> u16 { + // if self.input.current_row < MAX_EDITOR_LINES { + // self.input.current_row + // } else { + // MAX_EDITOR_LINES + // } + // } + // + // pub fn get_cursor_column(&self) -> u16 { + // self.input.cursor_column + // } pub fn load_file(&mut self, file: File) -> Result<()> { let buf = BufReader::new(file); - let mut lines = Vec::new(); - for line in buf.lines() { - let mut line = line?; - debug!("Line: {}", line); - line.push('\n'); - let line = line.replace('\t', " "); - lines.push(Line::new(line)); - } - self.input.lines = lines; + // let contents = buf.lines().collect(); + // let mut lines = Vec::new(); + // for line in buf.lines() { + // let mut line = line?; + // debug!("Line: {}", line); + // line.push('\n'); + // let line = line.replace('\t', " "); + // lines.push(Line::new(line)); + // } + // self.input.insert_str(buf); + // self.input.lines = lines; Ok(()) } } -#[cfg(test)] -mod tests { - use crate::app::editor::{Input, Line}; - use std::io::Cursor; - - #[test] - #[should_panic] - fn can_delete_non_ascii_characters() { - // - // Due to missing support for non-ascii characters, this test panics. - // #[should_panic] should be removed as soon as non-ascii chars are supported. - // - let mut input: Input = Input { - lines: vec![Line { - text: Cursor::new(String::from("äää")), - }], - current_row: 0, - cursor_column: 3, - }; - - input.backspace().expect("Expect that can delete character"); - assert_eq!(input.current_row, 0); - assert_eq!(input.cursor_column, 2); - - input.backspace().expect("Expect that can delete character"); - assert_eq!(input.current_row, 0); - assert_eq!(input.cursor_column, 1); - } - - #[test] - fn next_character_in_one_line() { - let mut input: Input = Input { - lines: vec![Line { - text: Cursor::new(String::from("aaa")), - }], - current_row: 0, - cursor_column: 0, - }; - - input.next_char().expect("Could move to next character"); - assert_eq!( - input.cursor_column, 1, - "When moving once, cursor should be after first character" - ); - - input.next_char().expect("Could move to next character"); - assert_eq!(input.cursor_column, 2); - - input.next_char().expect("Could move to next character"); - assert_eq!(input.cursor_column, 3); - - input.next_char().expect("Could move to next character"); - assert_eq!( - input.cursor_column, 3, - "When line is over and no next line exists, cursor should stop" - ); - assert_eq!( - input.current_row, 0, - "When line is over and no next line exists, cursor should stop" - ); - } - - #[test] - fn previous_character_in_one_line() { - let mut input: Input = Input { - lines: vec![Line { - text: Cursor::new(String::from("aaa")), - }], - current_row: 0, - cursor_column: 3, - }; - - input - .previous_char() - .expect("Could move to previous character"); - assert_eq!(input.cursor_column, 2); - - input - .previous_char() - .expect("Could move to previous character"); - assert_eq!(input.cursor_column, 1); - - input - .previous_char() - .expect("Could move to previous character"); - assert_eq!(input.cursor_column, 0); - - input - .previous_char() - .expect("Could move to previous character"); - assert_eq!(input.cursor_column, 0); - } - - #[test] - #[ignore] - fn jump_to_next_line_on_next_character_at_the_end_of_line() { - // This functionality is not implemented but could come in later releases. - let mut input: Input = Input { - lines: vec![ - Line { - text: Cursor::new(String::from("aa")), - }, - Line { - text: Cursor::new(String::from("bb")), - }, - ], - current_row: 0, - cursor_column: 0, - }; - - input.next_char().expect("Could move to next character"); - input.next_char().expect("Could move to next character"); - - // we expect to jump to the next line here - input.next_char().expect("Could move to next character"); - assert_eq!( - input.current_row, 1, - "Cursor should have jumped to next line" - ); - assert_eq!( - input.cursor_column, 0, - "Cursor should be at beginning of the line" - ); - - input.next_char().expect("Could move to next character"); - assert_eq!(input.current_row, 1); - assert_eq!( - input.cursor_column, 1, - "Cursor should be at the end of second line" - ); - - input.next_char().expect("Could move to next character"); - assert_eq!(input.current_row, 1); - assert_eq!(input.cursor_column, 2); - - input.next_char().expect("Could move to next character"); - assert_eq!(input.current_row, 1); - assert_eq!( - input.cursor_column, 2, - "When there is no next line, cursor should stay unchanged" - ); - } - - #[test] - #[ignore] - fn jump_to_previous_line_on_previous_character_at_the_beginning_of_line() { - // This functionality is not implemented but could come in later releases. - let mut input: Input = Input { - lines: vec![ - Line { - text: Cursor::new(String::from("aa")), - }, - Line { - text: Cursor::new(String::from("bb")), - }, - ], - current_row: 1, - cursor_column: 0, - }; - - input.previous_char().expect("Could move to next character"); - assert_eq!( - input.current_row, 0, - "Cursor should have jumped to previous line" - ); - assert_eq!( - input.cursor_column, 1, - "Cursor should be at end of the previous line" - ); - } - - #[test] - fn non_ascii_character_count() { - let input: Input = Input { - lines: vec![Line { - text: Cursor::new(String::from("äää")), - }], - current_row: 0, - cursor_column: 0, - }; - - assert_eq!(input.number_chars_in_current_line(), 3); - - let input2: Input = Input { - lines: vec![Line { - text: Cursor::new(String::from("äääb")), - }], - current_row: 0, - cursor_column: 0, - }; - assert_eq!(input2.number_chars_in_current_line(), 4); - } - - #[test] - #[should_panic] - fn test_append_char() { - // - // Due to missing support for non-ascii characters, this test panics. - // #[should_panic] should be removed as soon as non-ascii chars are supported. - // - let mut input: Input = Input::default(); - - // Input: "" - input.append_char('ä').expect("Could append a character"); - assert_eq!(input.current_row, 0); - assert_eq!(input.cursor_column, 1); - assert_eq!(input.number_chars_in_current_line(), 1); - - // Input: "ä" - input.append_char('b').expect("Could append a character"); - assert_eq!(input.current_row, 0); - assert_eq!(input.cursor_column, 2); - assert_eq!(input.number_chars_in_current_line(), 2); - - // Input: "äb" - input.append_char('\t').expect("Could append a character"); - assert_eq!(input.current_row, 0); - assert_eq!(input.cursor_column, 6); - assert_eq!(input.number_chars_in_current_line(), 6); - - // Input: "äb " - input.append_char('\n').expect("Could append a character"); - assert_eq!(input.current_row, 1); - assert_eq!(input.cursor_column, 0); - assert_eq!(input.number_chars_in_current_line(), 0); - - // Input: "äb \n" - // "" - input.append_char('a').expect("Could append a character"); - assert_eq!(input.current_row, 1); - assert_eq!(input.cursor_column, 1); - assert_eq!(input.number_chars_in_current_line(), 1); - - // Input: "ä|b \n" <- cursor | - // "a" - input.up_row().expect("Can go up"); - input.append_char('a').expect("Could append a character"); - assert_eq!(input.current_row, 0); - assert_eq!(input.cursor_column, 2); - assert_eq!( - input.number_chars_in_current_line(), - 7, - "Line: {}", - input.lines[input.current_row as usize].text.get_ref() - ); - - // Input: "äab \n" - // "a|" <- cursor | - input.down_row().expect("Can go down"); - input.previous_char().expect("Can go left"); - input.append_char('b').expect("Can type a character"); - // Input: "äab \n" - // "b|a" <- cursor | - assert_eq!(input.current_row, 1); - assert_eq!(input.cursor_column, 1); - assert_eq!(input.lines[1].text.get_ref(), "ba"); - } - - #[test] - fn test_up_row_and_down_row() { - let mut input: Input = Input { - lines: vec![ - Line { - text: Cursor::new(String::from("aaaa")), - }, - Line { - text: Cursor::new(String::from("bbbb")), - }, - Line { - text: Cursor::new(String::from("cccc")), - }, - Line { - text: Cursor::new(String::from("")), - }, - Line { - text: Cursor::new(String::from("dddd")), - }, - ], - current_row: 0, - cursor_column: 2, - }; - - input.up_row().expect("No exception should be thrown."); - assert_eq!(input.current_row, 0, "At 0th line, up_row has no effect"); - assert_eq!( - input.cursor_column, 2, - "When up_row has no effect, the location inside the line should stay unchanged" - ); - - input.down_row().expect("No exception should be thrown."); - assert_eq!(input.current_row, 1); - assert_eq!(input.cursor_column, 2); - - input.down_row().expect("No exception should be thrown."); - assert_eq!(input.current_row, 2); - assert_eq!(input.cursor_column, 2); - - input.down_row().expect("No exception should be thrown."); - assert_eq!(input.current_row, 3); - assert_eq!(input.cursor_column, 0); - - input.down_row().expect("No exception should be thrown."); - assert_eq!(input.current_row, 4); - assert_eq!(input.cursor_column, 0); - - input.down_row().expect("No exception should be thrown."); - assert_eq!(input.current_row, 4, "At last line, down_row has no effect"); - assert_eq!(input.cursor_column, 0); - - input.up_row().expect("No exception should be thrown."); - assert_eq!(input.current_row, 3); - assert_eq!( - input.cursor_column, 0, - "When coming from an empty line, the cursor should be at 0th position." - ); - - let mut input2: Input = Input::default(); - // this use case caused a bug - input2.append_char('a').expect("Can append char"); - input2.append_char('\n').expect("Can append new line"); - input2.up_row().expect("Can go up"); - input2.down_row().expect("Can go down"); - assert_eq!(input2.current_row, 1); - assert_eq!(input2.cursor_column, 0); - input2.append_char('b').expect("Can append char"); - assert_eq!(input2.current_row, 1); - assert_eq!(input2.cursor_column, 1); - assert_eq!(input2.lines[0].text.get_ref(), "a\n"); - assert_eq!(input2.lines[1].text.get_ref(), "b"); - } - - #[test] - fn empty_editor_backspace() { - let mut input: Input = Input::default(); - - input.backspace().expect("Backspace doesnt move cursor"); - assert_eq!(input.current_row, 0); - assert_eq!(input.cursor_column, 0); - } -} +// #[cfg(test)] +// mod tests { +// use crate::app::editor::{Input, Line}; +// use std::io::Cursor; + +// #[test] +// #[should_panic] +// fn can_delete_non_ascii_characters() { +// // +// // Due to missing support for non-ascii characters, this test panics. +// // #[should_panic] should be removed as soon as non-ascii chars are supported. +// // +// let mut input: Input = Input { +// lines: vec![Line { +// text: Cursor::new(String::from("äää")), +// }], +// current_row: 0, +// cursor_column: 3, +// }; +// +// input.backspace().expect("Expect that can delete character"); +// assert_eq!(input.current_row, 0); +// assert_eq!(input.cursor_column, 2); +// +// input.backspace().expect("Expect that can delete character"); +// assert_eq!(input.current_row, 0); +// assert_eq!(input.cursor_column, 1); +// } + +// #[test] +// fn next_character_in_one_line() { +// let mut input: Input = Input { +// lines: vec![Line { +// text: Cursor::new(String::from("aaa")), +// }], +// current_row: 0, +// cursor_column: 0, +// }; +// +// input.next_char().expect("Could move to next character"); +// assert_eq!( +// input.cursor_column, 1, +// "When moving once, cursor should be after first character" +// ); +// +// input.next_char().expect("Could move to next character"); +// assert_eq!(input.cursor_column, 2); +// +// input.next_char().expect("Could move to next character"); +// assert_eq!(input.cursor_column, 3); +// +// input.next_char().expect("Could move to next character"); +// assert_eq!( +// input.cursor_column, 3, +// "When line is over and no next line exists, cursor should stop" +// ); +// assert_eq!( +// input.current_row, 0, +// "When line is over and no next line exists, cursor should stop" +// ); +// } + +// #[test] +// fn previous_character_in_one_line() { +// let mut input: Input = Input { +// lines: vec![Line { +// text: Cursor::new(String::from("aaa")), +// }], +// current_row: 0, +// cursor_column: 3, +// }; +// +// input +// .previous_char() +// .expect("Could move to previous character"); +// assert_eq!(input.cursor_column, 2); +// +// input +// .previous_char() +// .expect("Could move to previous character"); +// assert_eq!(input.cursor_column, 1); +// +// input +// .previous_char() +// .expect("Could move to previous character"); +// assert_eq!(input.cursor_column, 0); +// +// input +// .previous_char() +// .expect("Could move to previous character"); +// assert_eq!(input.cursor_column, 0); +// } + +// #[test] +// #[ignore] +// fn jump_to_next_line_on_next_character_at_the_end_of_line() { +// // This functionality is not implemented but could come in later releases. +// let mut input: Input = Input { +// lines: vec![ +// Line { +// text: Cursor::new(String::from("aa")), +// }, +// Line { +// text: Cursor::new(String::from("bb")), +// }, +// ], +// current_row: 0, +// cursor_column: 0, +// }; +// +// input.next_char().expect("Could move to next character"); +// input.next_char().expect("Could move to next character"); +// +// // we expect to jump to the next line here +// input.next_char().expect("Could move to next character"); +// assert_eq!( +// input.current_row, 1, +// "Cursor should have jumped to next line" +// ); +// assert_eq!( +// input.cursor_column, 0, +// "Cursor should be at beginning of the line" +// ); +// +// input.next_char().expect("Could move to next character"); +// assert_eq!(input.current_row, 1); +// assert_eq!( +// input.cursor_column, 1, +// "Cursor should be at the end of second line" +// ); +// +// input.next_char().expect("Could move to next character"); +// assert_eq!(input.current_row, 1); +// assert_eq!(input.cursor_column, 2); +// +// input.next_char().expect("Could move to next character"); +// assert_eq!(input.current_row, 1); +// assert_eq!( +// input.cursor_column, 2, +// "When there is no next line, cursor should stay unchanged" +// ); +// } + +// #[test] +// #[ignore] +// fn jump_to_previous_line_on_previous_character_at_the_beginning_of_line() { +// // This functionality is not implemented but could come in later releases. +// let mut input: Input = Input { +// lines: vec![ +// Line { +// text: Cursor::new(String::from("aa")), +// }, +// Line { +// text: Cursor::new(String::from("bb")), +// }, +// ], +// current_row: 1, +// cursor_column: 0, +// }; +// +// input.previous_char().expect("Could move to next character"); +// assert_eq!( +// input.current_row, 0, +// "Cursor should have jumped to previous line" +// ); +// assert_eq!( +// input.cursor_column, 1, +// "Cursor should be at end of the previous line" +// ); +// } + +// #[test] +// fn non_ascii_character_count() { +// let input: Input = Input { +// lines: vec![Line { +// text: Cursor::new(String::from("äää")), +// }], +// current_row: 0, +// cursor_column: 0, +// }; +// +// assert_eq!(input.number_chars_in_current_line(), 3); +// +// let input2: Input = Input { +// lines: vec![Line { +// text: Cursor::new(String::from("äääb")), +// }], +// current_row: 0, +// cursor_column: 0, +// }; +// assert_eq!(input2.number_chars_in_current_line(), 4); +// } + +// #[test] +// #[should_panic] +// fn test_append_char() { +// // +// // Due to missing support for non-ascii characters, this test panics. +// // #[should_panic] should be removed as soon as non-ascii chars are supported. +// // +// let mut input: Input = Input::default(); +// +// // Input: "" +// input.append_char('ä').expect("Could append a character"); +// assert_eq!(input.current_row, 0); +// assert_eq!(input.cursor_column, 1); +// assert_eq!(input.number_chars_in_current_line(), 1); +// +// // Input: "ä" +// input.append_char('b').expect("Could append a character"); +// assert_eq!(input.current_row, 0); +// assert_eq!(input.cursor_column, 2); +// assert_eq!(input.number_chars_in_current_line(), 2); +// +// // Input: "äb" +// input.append_char('\t').expect("Could append a character"); +// assert_eq!(input.current_row, 0); +// assert_eq!(input.cursor_column, 6); +// assert_eq!(input.number_chars_in_current_line(), 6); +// +// // Input: "äb " +// input.append_char('\n').expect("Could append a character"); +// assert_eq!(input.current_row, 1); +// assert_eq!(input.cursor_column, 0); +// assert_eq!(input.number_chars_in_current_line(), 0); +// +// // Input: "äb \n" +// // "" +// input.append_char('a').expect("Could append a character"); +// assert_eq!(input.current_row, 1); +// assert_eq!(input.cursor_column, 1); +// assert_eq!(input.number_chars_in_current_line(), 1); +// +// // Input: "ä|b \n" <- cursor | +// // "a" +// input.up_row().expect("Can go up"); +// input.append_char('a').expect("Could append a character"); +// assert_eq!(input.current_row, 0); +// assert_eq!(input.cursor_column, 2); +// assert_eq!( +// input.number_chars_in_current_line(), +// 7, +// "Line: {}", +// input.lines[input.current_row as usize].text.get_ref() +// ); +// +// // Input: "äab \n" +// // "a|" <- cursor | +// input.down_row().expect("Can go down"); +// input.previous_char().expect("Can go left"); +// input.append_char('b').expect("Can type a character"); +// // Input: "äab \n" +// // "b|a" <- cursor | +// assert_eq!(input.current_row, 1); +// assert_eq!(input.cursor_column, 1); +// assert_eq!(input.lines[1].text.get_ref(), "ba"); +// } + +// #[test] +// fn test_up_row_and_down_row() { +// let mut input: Input = Input { +// lines: vec![ +// Line { +// text: Cursor::new(String::from("aaaa")), +// }, +// Line { +// text: Cursor::new(String::from("bbbb")), +// }, +// Line { +// text: Cursor::new(String::from("cccc")), +// }, +// Line { +// text: Cursor::new(String::from("")), +// }, +// Line { +// text: Cursor::new(String::from("dddd")), +// }, +// ], +// current_row: 0, +// cursor_column: 2, +// }; +// +// input.up_row().expect("No exception should be thrown."); +// assert_eq!(input.current_row, 0, "At 0th line, up_row has no effect"); +// assert_eq!( +// input.cursor_column, 2, +// "When up_row has no effect, the location inside the line should stay unchanged" +// ); +// +// input.down_row().expect("No exception should be thrown."); +// assert_eq!(input.current_row, 1); +// assert_eq!(input.cursor_column, 2); +// +// input.down_row().expect("No exception should be thrown."); +// assert_eq!(input.current_row, 2); +// assert_eq!(input.cursor_column, 2); +// +// input.down_row().expect("No exception should be thrown."); +// assert_eq!(input.current_row, 3); +// assert_eq!(input.cursor_column, 0); +// +// input.down_row().expect("No exception should be thrown."); +// assert_eq!(input.current_row, 4); +// assert_eq!(input.cursor_column, 0); +// +// input.down_row().expect("No exception should be thrown."); +// assert_eq!(input.current_row, 4, "At last line, down_row has no effect"); +// assert_eq!(input.cursor_column, 0); +// +// input.up_row().expect("No exception should be thrown."); +// assert_eq!(input.current_row, 3); +// assert_eq!( +// input.cursor_column, 0, +// "When coming from an empty line, the cursor should be at 0th position." +// ); +// +// let mut input2: Input = Input::default(); +// // this use case caused a bug +// input2.append_char('a').expect("Can append char"); +// input2.append_char('\n').expect("Can append new line"); +// input2.up_row().expect("Can go up"); +// input2.down_row().expect("Can go down"); +// assert_eq!(input2.current_row, 1); +// assert_eq!(input2.cursor_column, 0); +// input2.append_char('b').expect("Can append char"); +// assert_eq!(input2.current_row, 1); +// assert_eq!(input2.cursor_column, 1); +// assert_eq!(input2.lines[0].text.get_ref(), "a\n"); +// assert_eq!(input2.lines[1].text.get_ref(), "b"); +// } + +// #[test] +// fn empty_editor_backspace() { +// let mut input: Input = Input::default(); +// +// input.backspace().expect("Backspace doesnt move cursor"); +// assert_eq!(input.current_row, 0); +// assert_eq!(input.cursor_column, 0); +// } +// } diff --git a/src/app/handlers/edit.rs b/src/app/handlers/edit.rs index 5203851..ad00f62 100644 --- a/src/app/handlers/edit.rs +++ b/src/app/handlers/edit.rs @@ -22,36 +22,33 @@ // Lines around cursor => First line, middle line, last line use log::debug; +use ratatui::crossterm::event::{KeyCode, KeyEvent}; use crate::app::core::{App, AppReturn, InputMode}; use crate::app::error::Result; use crate::events::Key; -pub async fn edit_mode_handler(app: &mut App, key: Key) -> Result { - debug!( - "{} Entered, current row / col: {} / {}", - key, app.editor.input.current_row, app.editor.input.cursor_column - ); - match key { - Key::Enter => app.editor.input.append_char('\n'), - Key::Char(c) => match c { - ';' => { - let result = app.editor.input.append_char(c); - app.editor.sql_terminated = true; - result - } - _ => app.editor.input.append_char(c), - }, - Key::Left => app.editor.input.previous_char(), - Key::Right => app.editor.input.next_char(), - Key::Up => app.editor.input.up_row(), - Key::Down => app.editor.input.down_row(), - Key::Tab => app.editor.input.tab(), - Key::Backspace => app.editor.input.backspace(), - Key::Esc => { +pub async fn edit_mode_handler(app: &mut App<'_>, key: KeyEvent) -> Result { + // debug!( + // "{} Entered, current row / col: {} / {}", + // key, app.editor.input.current_row, app.editor.input.cursor_column + // ); + match key.code { + // KeyCode::Char(c) => match c { + // ';' => { + // let result = app.editor.input.append_char(c); + // app.editor.sql_terminated = true; + // result + // } + // _ => app.editor.input.append_char(c), + // }, + KeyCode::Esc => { app.input_mode = InputMode::Normal; Ok(AppReturn::Continue) } - _ => Ok(AppReturn::Continue), + _ => { + app.editor.input.input(key); + Ok(AppReturn::Continue) + } } } diff --git a/src/app/handlers/logging.rs b/src/app/handlers/logging.rs index bd3b490..58d839f 100644 --- a/src/app/handlers/logging.rs +++ b/src/app/handlers/logging.rs @@ -17,64 +17,64 @@ use crate::app::core::{App, AppReturn, TabItem}; use crate::app::error::Result; -use crate::events::Key; use log::debug; +use ratatui::crossterm::event::{KeyCode, KeyEvent}; use tui_logger::TuiWidgetEvent; -pub async fn logging_handler(app: &mut App, key: Key) -> Result { +pub async fn logging_handler<'app>(app: &'app mut App<'app>, key: KeyEvent) -> Result { match app.tab_item { - TabItem::Logs => match key { - Key::Char('h') => { + TabItem::Logs => match key.code { + KeyCode::Char('h') => { app.logs.state.transition(TuiWidgetEvent::HideKey); Ok(AppReturn::Continue) } - Key::Char('f') => { + KeyCode::Char('f') => { app.logs.state.transition(TuiWidgetEvent::FocusKey); Ok(AppReturn::Continue) } - Key::Char('+') => { + KeyCode::Char('+') => { app.logs.state.transition(TuiWidgetEvent::PlusKey); Ok(AppReturn::Continue) } - Key::Char('-') => { + KeyCode::Char('-') => { app.logs.state.transition(TuiWidgetEvent::MinusKey); Ok(AppReturn::Continue) } - Key::Char('q') => Ok(AppReturn::Exit), - Key::Char(' ') => { + KeyCode::Char('q') => Ok(AppReturn::Exit), + KeyCode::Char(' ') => { app.logs.state.transition(TuiWidgetEvent::SpaceKey); Ok(AppReturn::Continue) } - Key::Esc => { + KeyCode::Esc => { app.logs.state.transition(TuiWidgetEvent::EscapeKey); Ok(AppReturn::Continue) } - Key::Down => { + KeyCode::Down => { app.logs.state.transition(TuiWidgetEvent::DownKey); Ok(AppReturn::Continue) } - Key::Up => { + KeyCode::Up => { app.logs.state.transition(TuiWidgetEvent::UpKey); Ok(AppReturn::Continue) } - Key::Right => { + KeyCode::Right => { app.logs.state.transition(TuiWidgetEvent::RightKey); Ok(AppReturn::Continue) } - Key::Left => { + KeyCode::Left => { app.logs.state.transition(TuiWidgetEvent::LeftKey); Ok(AppReturn::Continue) } - Key::PageDown => { + KeyCode::PageDown => { app.logs.state.transition(TuiWidgetEvent::NextPageKey); Ok(AppReturn::Continue) } - Key::PageUp => { + KeyCode::PageUp => { app.logs.state.transition(TuiWidgetEvent::PrevPageKey); Ok(AppReturn::Continue) } - Key::Char(c) if c.is_ascii_digit() => { + KeyCode::Char(c) if c.is_ascii_digit() => { change_tab(c, app)?; Ok(AppReturn::Continue) } diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index 7871bd6..481ef67 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -24,6 +24,7 @@ use arrow::util::pretty::pretty_format_batches; use datafusion::error::DataFusionError; use datafusion::prelude::DataFrame; use log::{debug, error}; +use ratatui::crossterm::event::KeyEvent; use std::sync::Arc; use std::time::Instant; @@ -33,7 +34,7 @@ use crate::app::error::{DftError, Result}; use crate::app::ui::Scroll; use crate::events::Key; -pub async fn key_event_handler(app: &mut App, key: Key) -> Result { +pub async fn key_event_handler<'app>(app: &'app mut App<'app>, key: KeyEvent) -> Result { match app.input_mode { InputMode::Normal => normal::normal_mode_handler(app, key).await, InputMode::Editing => edit::edit_mode_handler(app, key).await, @@ -41,13 +42,13 @@ pub async fn key_event_handler(app: &mut App, key: Key) -> Result { } } -pub async fn execute_query(app: &mut App) -> Result { - let sql: String = app.editor.input.combine_lines(); +pub async fn execute_query(app: &mut App<'_>) -> Result { + let sql: String = app.editor.input.lines().join("\n"); handle_queries(app, sql).await?; Ok(AppReturn::Continue) } -async fn handle_queries(app: &mut App, sql: String) -> Result<()> { +async fn handle_queries(app: &mut App<'_>, sql: String) -> Result<()> { let start = Instant::now(); let queries = sql.split(';'); for query in queries { @@ -66,7 +67,7 @@ async fn handle_queries(app: &mut App, sql: String) -> Result<()> { } async fn handle_successful_query( - app: &mut App, + app: &mut App<'_>, start: Instant, sql: String, df: Arc, diff --git a/src/app/handlers/normal.rs b/src/app/handlers/normal.rs index 12c1830..d8834b2 100644 --- a/src/app/handlers/normal.rs +++ b/src/app/handlers/normal.rs @@ -19,47 +19,50 @@ use crate::app::core::{App, AppReturn, InputMode, TabItem}; use crate::app::error::Result; use crate::app::handlers::execute_query; use crate::app::handlers::logging; -use crate::events::Key; use log::debug; +use ratatui::crossterm::event::{KeyCode, KeyEvent}; -pub async fn normal_mode_handler(app: &mut App, key: Key) -> Result { +pub async fn normal_mode_handler<'app>( + app: &'app mut App<'app>, + key: KeyEvent, +) -> Result { match app.tab_item { - TabItem::Editor => match key { - Key::Enter => execute_query(app).await, - Key::Char('c') => { - app.editor.input.clear()?; + TabItem::Editor => match key.code { + KeyCode::Enter => execute_query(app).await, + KeyCode::Char('c') => { + // app.editor.input.clear()?; app.input_mode = InputMode::Editing; Ok(AppReturn::Continue) } - Key::Char('e') => { + KeyCode::Char('e') => { app.input_mode = InputMode::Editing; Ok(AppReturn::Continue) } - Key::Char('q') => Ok(AppReturn::Exit), - Key::Char('r') => { + KeyCode::Char('q') => Ok(AppReturn::Exit), + KeyCode::Char('r') => { app.input_mode = InputMode::Rc; Ok(AppReturn::Continue) } - Key::Char(c) => { + KeyCode::Char(c) => { if c.is_ascii_digit() { change_tab(c, app) } else { Ok(AppReturn::Continue) } } - Key::Down => { + KeyCode::Down => { if let Some(results) = &mut app.query_results { results.scroll.x += 1 } Ok(AppReturn::Continue) } - Key::PageDown => { + KeyCode::PageDown => { if let Some(results) = &mut app.query_results { results.scroll.x += 10 } Ok(AppReturn::Continue) } - Key::Up => { + KeyCode::Up => { if let Some(results) = &mut app.query_results { let new_x = match results.scroll.x { 0 => 0, @@ -69,7 +72,7 @@ pub async fn normal_mode_handler(app: &mut App, key: Key) -> Result { } Ok(AppReturn::Continue) } - Key::PageUp => { + KeyCode::PageUp => { if let Some(results) = &mut app.query_results { let new_x = match results.scroll.x { 0 => 0, @@ -80,13 +83,13 @@ pub async fn normal_mode_handler(app: &mut App, key: Key) -> Result { Ok(AppReturn::Continue) } - Key::Right => { + KeyCode::Right => { if let Some(results) = &mut app.query_results { results.scroll.y += 3 } Ok(AppReturn::Continue) } - Key::Left => { + KeyCode::Left => { if let Some(results) = &mut app.query_results { let new_y = match results.scroll.y { 0..=2 => 0, @@ -99,9 +102,9 @@ pub async fn normal_mode_handler(app: &mut App, key: Key) -> Result { _ => Ok(AppReturn::Continue), }, TabItem::Logs => logging::logging_handler(app, key).await, - _ => match key { - Key::Char('q') => Ok(AppReturn::Exit), - Key::Char(c) if c.is_ascii_digit() => change_tab(c, app), + _ => match key.code { + KeyCode::Char('q') => Ok(AppReturn::Exit), + KeyCode::Char(c) if c.is_ascii_digit() => change_tab(c, app), _ => Ok(AppReturn::Continue), }, } diff --git a/src/app/handlers/rc.rs b/src/app/handlers/rc.rs index 4a28d91..e688f1c 100644 --- a/src/app/handlers/rc.rs +++ b/src/app/handlers/rc.rs @@ -16,19 +16,20 @@ // under the License. use log::debug; +use ratatui::crossterm::event::{KeyCode, KeyEvent}; use std::fs::File; use crate::app::core::{App, AppReturn, InputMode}; use crate::app::error::Result; use crate::events::Key; -pub async fn rc_mode_handler(app: &mut App, key: Key) -> Result { - debug!( - "{} Entered, current row / col: {} / {}", - key, app.editor.input.current_row, app.editor.input.cursor_column - ); - match key { - Key::Char(c) => match c { +pub async fn rc_mode_handler(app: &mut App<'_>, key: KeyEvent) -> Result { + // debug!( + // "{} Entered, current row / col: {} / {}", + // key, app.editor.input.current_row, app.editor.input.cursor_column + // ); + match key.code { + KeyCode::Char(c) => match c { 'e' => { app.input_mode = InputMode::Editing; Ok(AppReturn::Continue) @@ -47,12 +48,12 @@ pub async fn rc_mode_handler(app: &mut App, key: Key) -> Result { Ok(AppReturn::Continue) } 'w' => { - app.write_rc()?; + // app.write_rc()?; Ok(AppReturn::Continue) } _ => Ok(AppReturn::Continue), }, - Key::Esc => { + KeyCode::Esc => { app.input_mode = InputMode::Normal; Ok(AppReturn::Continue) } diff --git a/src/app/ui.rs b/src/app/ui.rs index 1d24fec..557d9be 100644 --- a/src/app/ui.rs +++ b/src/app/ui.rs @@ -16,10 +16,11 @@ // under the License. use ratatui::{ + buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span, Text}, - widgets::{Block, Borders, List, ListItem, Paragraph, Tabs}, + widgets::{Block, Borders, List, ListItem, Paragraph, Tabs, Widget}, Frame, }; use tui_logger::TuiLoggerSmartWidget; @@ -31,16 +32,16 @@ pub struct Scroll { pub y: u16, } -pub fn draw_ui(f: &mut Frame, app: &App) { - match app.tab_item { - TabItem::Editor => draw_sql_editor_tab(f, app), - TabItem::QueryHistory => draw_query_history_tab(f, app), - TabItem::Context => draw_context_tab(f, app), - TabItem::Logs => draw_logs_tab(f, app), - } -} +// pub fn draw_ui(f: &mut Frame, app: &App) { +// match app.tab_item { +// TabItem::Editor => draw_sql_editor_tab(f, app), +// TabItem::QueryHistory => draw_query_history_tab(f, app), +// TabItem::Context => draw_context_tab(f, app), +// TabItem::Logs => draw_logs_tab(f, app), +// } +// } -fn draw_sql_editor_tab(f: &mut Frame, app: &App) { +pub fn draw_sql_editor_tab(app: &App, area: Rect, buf: &mut Buffer) { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) @@ -53,21 +54,25 @@ fn draw_sql_editor_tab(f: &mut Frame, app: &App) { ] .as_ref(), ) - .split(f.area()); + .split(area); let help_message = draw_sql_editor_help(app); - f.render_widget(help_message, chunks[0]); + help_message.render(chunks[0], buf); + // f.render_widget(help_message, chunks[0]); let tabs = draw_tabs(app); - f.render_widget(tabs, chunks[1]); - let editor = draw_editor(app); - f.render_widget(editor, chunks[2]); - draw_cursor(app, f, &chunks); + tabs.render(chunks[1], buf); + // f.render_widget(tabs, chunks[1]); + // let editor = draw_editor(app); + // f.render_widget(editor, chunks[2]); + app.editor.input.render(chunks[2], buf); + // draw_cursor(app, f, &chunks); let query_results = draw_query_results(app); - f.render_widget(query_results, chunks[3]); + query_results.render(chunks[3], buf); + // f.render_widget(query_results, chunks[3]); } -fn draw_query_history_tab(f: &mut Frame, app: &App) { +pub fn draw_query_history_tab(app: &App, area: Rect, buf: &mut Buffer) { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) @@ -79,18 +84,21 @@ fn draw_query_history_tab(f: &mut Frame, app: &App) { ] .as_ref(), ) - .split(f.area()); + .split(area); let help_message = draw_default_help(); - f.render_widget(help_message, chunks[0]); + help_message.render(chunks[0], buf); + // f.render_widget(help_message, chunks[0]); let tabs = draw_tabs(app); - f.render_widget(tabs, chunks[1]); + tabs.render(chunks[1], buf); + // f.render_widget(tabs, chunks[1]); let query_history = draw_query_history(app); - f.render_widget(query_history, chunks[2]) + // f.render_widget(query_history, chunks[2]) + query_history.render(chunks[2], buf); } -fn draw_context_tab(f: &mut Frame, app: &App) { +pub fn draw_context_tab(app: &App, area: Rect, buf: &mut Buffer) { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) @@ -102,18 +110,22 @@ fn draw_context_tab(f: &mut Frame, app: &App) { ] .as_ref(), ) - .split(f.area()); + .split(area); let help_message = draw_default_help(); - f.render_widget(help_message, chunks[0]); + help_message.render(chunks[0], buf); + // f.render_widget(help_message, chunks[0]); let tabs = draw_tabs(app); - f.render_widget(tabs, chunks[1]); + // f.render_widget(tabs, chunks[1]); + tabs.render(chunks[1], buf); - draw_context(f, app, chunks[2]); + draw_context(app, chunks[2], buf); + + // draw_context(f, app, chunks[2]); } -fn draw_logs_tab(f: &mut Frame, app: &App) { +pub fn draw_logs_tab(app: &App, area: Rect, buf: &mut Buffer) { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) @@ -125,15 +137,18 @@ fn draw_logs_tab(f: &mut Frame, app: &App) { ] .as_ref(), ) - .split(f.area()); + .split(area); let help_message = draw_default_help(); - f.render_widget(help_message, chunks[0]); + // f.render_widget(help_message, chunks[0]); + help_message.render(chunks[0], buf); let tabs = draw_tabs(app); - f.render_widget(tabs, chunks[1]); + // f.render_widget(tabs, chunks[1]); + tabs.render(chunks[1], buf); let logs = draw_logs(app); - f.render_widget(logs, chunks[2]) + // f.render_widget(logs, chunks[2]) + logs.render(chunks[2], buf); } fn draw_sql_editor_help<'screen>(app: &App) -> Paragraph<'screen> { @@ -198,32 +213,32 @@ fn draw_default_help<'screen>() -> Paragraph<'screen> { Paragraph::new(text) } -fn draw_editor<'screen>(app: &App) -> Paragraph<'screen> { - Paragraph::new(app.editor.input.combine_visible_lines()) - .style(match app.input_mode { - InputMode::Editing => Style::default().fg(Color::Yellow), - _ => Style::default(), - }) - .block(Block::default().borders(Borders::ALL).title("SQL Editor")) -} - -fn draw_cursor(app: &App, f: &mut Frame, chunks: &[Rect]) { - if let InputMode::Editing = app.input_mode { - // Make the cursor visible and ask tui-rs to put it at the specified coordinates after rendering - f.set_cursor( - // Put cursor past the end of the input text - chunks[2].x + app.editor.get_cursor_column() + 1, - // Move one line down, from the border to the input line - chunks[2].y + app.editor.get_cursor_row() + 1, - ) - } -} - -fn draw_query_results(app: &App) -> Paragraph { +// fn draw_editor<'screen>(app: &App, area: R) -> Paragraph<'screen> { +// app.editor.input.render(area, buf) +// Paragraph::new(app.editor.input.combine_visible_lines()) +// .style(match app.input_mode { +// InputMode::Editing => Style::default().fg(Color::Yellow), +// _ => Style::default(), +// }) +// .block(Block::default().borders(Borders::ALL).title("SQL Editor")) +// } + +// fn draw_cursor(app: &App, f: &mut Frame, chunks: &[Rect]) { +// if let InputMode::Editing = app.input_mode { +// // Make the cursor visible and ask tui-rs to put it at the specified coordinates after rendering +// f.set_cursor( +// // Put cursor past the end of the input text +// chunks[2].x + app.editor.get_cursor_column() + 1, +// // Move one line down, from the border to the input line +// chunks[2].y + app.editor.get_cursor_row() + 1, +// ) +// } +// } + +fn draw_query_results<'app>(app: &'app App) -> Paragraph<'app> { // Query results not shown correctly on error. For example `show tables for x` let (query_results, duration) = match &app.query_results { Some(query_results) => { - // debug!("Query results available"); let query_meta = app.editor.history.last().unwrap(); let results = if query_meta.query.starts_with("CREATE") { Paragraph::new(String::from("Table created")) @@ -235,7 +250,6 @@ fn draw_query_results(app: &App) -> Paragraph { (results, query_duration_info) } None => { - // debug!("Query results not available"); let last_query = &app.editor.history.last(); let no_queries_text = match last_query { Some(query_meta) => { @@ -295,33 +309,29 @@ fn draw_query_history<'screen>(app: &App) -> List<'screen> { ) } -fn draw_logs(app: &App) -> TuiLoggerSmartWidget { +fn draw_logs<'app>(app: &'app App) -> TuiLoggerSmartWidget<'app> { TuiLoggerSmartWidget::default() .style_error(Style::default().fg(Color::Red)) .style_debug(Style::default().fg(Color::Green)) .style_warn(Style::default().fg(Color::Yellow)) .style_trace(Style::default().fg(Color::Gray)) .style_info(Style::default().fg(Color::Blue)) - // .block( - // Block::default() - // .title("Logs") - // .border_style(Style::default()) - // .borders(Borders::ALL), - // ) .state(&app.logs.state) } -fn draw_context(f: &mut Frame, app: &App, area: Rect) { - let context = Layout::default() +fn draw_context(app: &App, area: Rect, buf: &mut Buffer) { + let chunks = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(area); let exec_config = draw_execution_config(app); - f.render_widget(exec_config, context[0]); + // f.render_widget(exec_config, context[0]); + exec_config.render(chunks[0], buf); let physical_opts = draw_physical_optimizers(app); - f.render_widget(physical_opts, context[1]); + // f.render_widget(physical_opts, context[1]); + physical_opts.render(chunks[1], buf); } fn draw_execution_config<'screen>(app: &App) -> List<'screen> { diff --git a/src/events/mod.rs b/src/events/mod.rs index 0910333..bdaed4c 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -17,12 +17,11 @@ mod key; -use crossterm::event::KeyEvent; use log::debug; pub use self::key::Key; -use crossterm::event; +use ratatui::crossterm::event::{self, KeyEvent}; use std::time::Duration; use std::{sync::mpsc, thread}; @@ -34,7 +33,7 @@ pub enum InputEvent { } pub enum Event { - KeyInput(Key), + KeyInput(KeyEvent), Tick, } @@ -52,11 +51,12 @@ impl Events { thread::spawn(move || { loop { // poll for tick rate duration, if no event, sent tick event. - if crossterm::event::poll(tick_rate).unwrap() { + if ratatui::crossterm::event::poll(tick_rate).unwrap() { if let event::Event::Key(key) = event::read().unwrap() { debug!("Key Event: {:?}", key); - let key = Key::from(key); - event_tx.send(Event::KeyInput(key)).unwrap(); + // let key = Key::from(key); + event_tx.send(Event::KeyInput(key)); + // event_tx.send(Event::KeyInput(key)).unwrap(); } } event_tx.send(Event::Tick).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 1adf467..be2d8d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,25 +20,42 @@ pub mod cli; pub mod events; pub mod utils; -use std::io; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::time::Duration; +use std::{borrow::Borrow, io}; -use app::core::{App, AppReturn}; -use crossterm::{ +use app::editor::Editor; +use app::{ + core::{App, AppReturn}, + error::DftError, +}; +use cli::args::Args; +use ratatui::crossterm::{ event::{DisableMouseCapture, EnableMouseCapture}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use ratatui::{backend::CrosstermBackend, Terminal}; -use tokio::sync::Mutex; +// use tokio::sync::Mutex; use crate::app::error::Result; use crate::app::ui; use crate::events::{Event, Events}; -pub async fn run_app(app: Arc>) -> Result<()> { +fn draw_app( + terminal: &mut Terminal>, + app: &App<'_>, +) -> Result<()> { + terminal.draw(|f| { + f.render_widget(app, f.area()); + })?; + Ok(()) +} + +pub async fn run_app(args: Args, ed: Editor<'_>) -> Result<()> { + // let ed = Editor::default(); + let mut app = App::new(args, ed); enable_raw_mode().unwrap(); let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); @@ -49,22 +66,38 @@ pub async fn run_app(app: Arc>) -> Result<()> { let events = Events::new(tick_rate); loop { - let mut app = app.lock().await; - terminal.draw(|f| ui::draw_ui(f, &app))?; - // terminal.draw(|f| ui::draw_ui(f, app.borrow()))?; - let event = events.next().unwrap(); - let result = match event { - Event::KeyInput(key) => app.key_handler(key).await, - Event::Tick => app.update_on_tick(), - }; + terminal.draw(|f| { + let area = f.area(); + f.render_widget(&app, area); + })?; + + // Ensure the immutable borrow is dropped before we proceed. + // Then handle the event (mutable borrow) + let result = { app.event_handler(event).await }; - if result == AppReturn::Exit { + if result? == AppReturn::Exit { break; } } + // loop { + // let event = events.next().unwrap(); + // if let Event::Tick = event { + // terminal.draw(|f| f.render_widget(&app, f.area()))?; + // } + // // draw_app(&mut terminal, &app)?; + // // terminal.draw(|f| ui::draw_ui(f, &app))?; + // // terminal.draw(|f| ui::draw_ui(f, app.borrow()))?; + // + // // Handle events + // let result = app.event_handler(event).await?; + // if result == AppReturn::Exit { + // break; + // } + // } + disable_raw_mode()?; execute!( terminal.backend_mut(), diff --git a/src/main.rs b/src/main.rs index 9a3e024..48209c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ use std::sync::Arc; use clap::Parser; use datafusion_tui::app::core::App; +use datafusion_tui::app::editor::Editor; use datafusion_tui::cli::args::Args; use datafusion_tui::run_app; use log::LevelFilter; @@ -35,8 +36,10 @@ async fn main() -> Result<(), Box> { tui_logger::init_logger(LevelFilter::Debug).unwrap(); tui_logger::set_default_level(LevelFilter::Debug); let args = Args::parse(); - let app = Arc::new(Mutex::new(App::new(args).await)); - let res = run_app(app).await; + // let app = Arc::new(Mutex::new(App::new(args).await)); + // let res = run_app(app).await; + let ed = Editor::default(); + run_app(args, ed).await; if let Err(err) = res { println!("{:?}", err) From e29e45d2c967eebb4b68e1b16dce54751dc0dff8 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sat, 24 Aug 2024 18:30:29 +0200 Subject: [PATCH 02/17] Working --- Cargo.lock | 3697 +++++++----------- Cargo.toml | 69 +- src/app/config.rs | 121 + src/app/core.rs | 281 -- src/app/datafusion/catalog_providers/glue.rs | 75 - src/app/datafusion/catalog_providers/mod.rs | 21 - src/app/datafusion/context.rs | 394 -- src/app/datafusion/mod.rs | 20 - src/app/datafusion/object_stores/mod.rs | 21 - src/app/datafusion/object_stores/s3.rs | 80 - src/app/editor/mod.rs | 752 ---- src/app/error.rs | 55 - src/app/execution.rs | 69 + src/app/handlers/edit.rs | 54 - src/app/handlers/logging.rs | 97 - src/app/handlers/mod.rs | 181 +- src/app/handlers/normal.rs | 123 - src/app/handlers/rc.rs | 62 - src/app/mod.rs | 349 +- src/app/state/mod.rs | 76 + src/app/state/tabs/explore.rs | 56 + src/app/state/tabs/mod.rs | 1 + src/app/ui.rs | 369 -- src/cli/args.rs | 112 - src/cli/mod.rs | 70 +- src/cli/print_format.rs | 161 - src/events/key.rs | 245 -- src/events/mod.rs | 74 - src/lib.rs | 109 - src/main.rs | 55 +- src/telemetry/mod.rs | 46 + src/ui/convert.rs | 230 ++ src/ui/mod.rs | 80 + src/ui/tabs/explore.rs | 45 + src/ui/tabs/mod.rs | 1 + src/utils/mod.rs | 1 - src/utils/test_util.rs | 48 - 37 files changed, 2602 insertions(+), 5698 deletions(-) create mode 100644 src/app/config.rs delete mode 100644 src/app/core.rs delete mode 100644 src/app/datafusion/catalog_providers/glue.rs delete mode 100644 src/app/datafusion/catalog_providers/mod.rs delete mode 100644 src/app/datafusion/context.rs delete mode 100644 src/app/datafusion/mod.rs delete mode 100644 src/app/datafusion/object_stores/mod.rs delete mode 100644 src/app/datafusion/object_stores/s3.rs delete mode 100644 src/app/editor/mod.rs delete mode 100644 src/app/error.rs create mode 100644 src/app/execution.rs delete mode 100644 src/app/handlers/edit.rs delete mode 100644 src/app/handlers/logging.rs delete mode 100644 src/app/handlers/normal.rs delete mode 100644 src/app/handlers/rc.rs create mode 100644 src/app/state/mod.rs create mode 100644 src/app/state/tabs/explore.rs create mode 100644 src/app/state/tabs/mod.rs delete mode 100644 src/app/ui.rs delete mode 100644 src/cli/args.rs delete mode 100644 src/cli/print_format.rs delete mode 100644 src/events/key.rs delete mode 100644 src/events/mod.rs delete mode 100644 src/lib.rs create mode 100644 src/telemetry/mod.rs create mode 100644 src/ui/convert.rs create mode 100644 src/ui/mod.rs create mode 100644 src/ui/tabs/explore.rs create mode 100644 src/ui/tabs/mod.rs delete mode 100644 src/utils/mod.rs delete mode 100644 src/utils/test_util.rs diff --git a/Cargo.lock b/Cargo.lock index 7ab6815..2706928 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -9,21 +18,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "adler32" -version = "1.2.0" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom 0.2.5", - "once_cell", - "version_check", -] +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" @@ -32,31 +30,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "const-random", + "getrandom", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "0.7.18" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "alloc-no-stdlib" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] @@ -83,823 +83,347 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" - -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "arrow" -version = "13.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6bee230122beb516ead31935a61f683715f987c6f003eff44ad6986624105a" -dependencies = [ - "bitflags 1.3.2", - "chrono", - "comfy-table", - "csv", - "flatbuffers", - "half", - "hex", - "indexmap", - "lazy_static", - "lexical-core", - "multiversion", - "num", - "rand 0.8.5", - "regex", - "serde", - "serde_derive", - "serde_json", -] - -[[package]] -name = "arrow-flight" -version = "13.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a3666d2dbc637fa979d1f0bf3031d39a80e709f3b9ec88e3d573c1d666bf553" -dependencies = [ - "arrow", - "base64", - "bytes", - "futures", - "proc-macro2", - "prost", - "prost-derive", - "tokio", - "tonic", - "tonic-build", -] - -[[package]] -name = "async-stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.94", -] - -[[package]] -name = "async-trait" -version = "0.1.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.94", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "avro-rs" -version = "0.13.0" +name = "anstream" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece550dd6710221de9bcdc1697424d8eee4fc4ca7e017479ea9d50c348465e37" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ - "byteorder", - "crc", - "digest 0.9.0", - "lazy_static", - "libflate", - "num-bigint 0.2.6", - "rand 0.7.3", - "serde", - "serde_json", - "snap 0.2.5", - "strum 0.18.0", - "strum_macros 0.18.0", - "thiserror", - "typed-builder", - "uuid 0.8.2", - "zerocopy 0.3.0", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] -name = "aws-config" -version = "0.9.0" +name = "anstyle" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da63196d2d0dd38667b404459a35d32562a8d83c1f46c5b789ab89ab176fd53" -dependencies = [ - "aws-http 0.9.0", - "aws-sdk-sso 0.9.0", - "aws-sdk-sts 0.9.0", - "aws-smithy-async 0.39.0", - "aws-smithy-client 0.39.0", - "aws-smithy-http 0.39.0", - "aws-smithy-http-tower 0.39.0", - "aws-smithy-json 0.39.0", - "aws-smithy-types 0.39.0", - "aws-types 0.9.0", - "bytes", - "hex", - "http", - "hyper", - "ring", - "tokio", - "tower", - "tracing", - "zeroize", -] +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] -name = "aws-config" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58e10d03d2180f9fce2fe72ffa007ccf44b4714b127d8429e78c3e55fe345c0" -dependencies = [ - "aws-http 0.12.0", - "aws-sdk-sso 0.12.0", - "aws-sdk-sts 0.12.0", - "aws-smithy-async 0.42.0", - "aws-smithy-client 0.42.0", - "aws-smithy-http 0.42.0", - "aws-smithy-http-tower 0.42.0", - "aws-smithy-json 0.42.0", - "aws-smithy-types 0.42.0", - "aws-types 0.12.0", - "bytes", - "hex", - "http", - "hyper", - "ring", - "tokio", - "tower", - "tracing", - "zeroize", -] - -[[package]] -name = "aws-endpoint" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5279590d48e92b287f864e099c7e851af03a5e184a57cec0959872cee297c7a0" -dependencies = [ - "aws-smithy-http 0.39.0", - "aws-types 0.9.0", - "http", - "regex", - "tracing", -] - -[[package]] -name = "aws-endpoint" -version = "0.12.0" +name = "anstyle-parse" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eadd9ea45f65689fbd088b22a2aaed363582fab5dcd335a1a7317640436952f" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ - "aws-smithy-http 0.42.0", - "aws-types 0.12.0", - "http", - "regex", - "tracing", + "utf8parse", ] [[package]] -name = "aws-http" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7046bdd807c70caf28d6dbc69b9d6d8dda1728577866d3ff3862de585b8b0eb" -dependencies = [ - "aws-smithy-http 0.39.0", - "aws-smithy-types 0.39.0", - "aws-types 0.9.0", - "http", - "lazy_static", - "percent-encoding", - "tracing", -] - -[[package]] -name = "aws-http" -version = "0.12.0" +name = "anstyle-query" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80fd2c81e2782d1f57a532543524c59b9c32dcc9132a7a2d308864c05b645e98" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "aws-smithy-http 0.42.0", - "aws-smithy-types 0.42.0", - "aws-types 0.12.0", - "http", - "lazy_static", - "percent-encoding", - "tracing", + "windows-sys 0.52.0", ] [[package]] -name = "aws-sdk-glue" -version = "0.12.0" +name = "anstyle-wincon" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ee1c361d87f065f2ea1adcdfd44c8de03efb89b20877fe56a8c6e3a3d08c69" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ - "aws-endpoint 0.12.0", - "aws-http 0.12.0", - "aws-sig-auth 0.12.0", - "aws-smithy-async 0.42.0", - "aws-smithy-client 0.42.0", - "aws-smithy-http 0.42.0", - "aws-smithy-http-tower 0.42.0", - "aws-smithy-json 0.42.0", - "aws-smithy-types 0.42.0", - "aws-types 0.12.0", - "bytes", - "http", - "tokio-stream", - "tower", -] - -[[package]] -name = "aws-sdk-s3" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fd99b22cbdb894925468005ad55defcfe0ce294fadcc5f7be9d9119646b0de" -dependencies = [ - "aws-endpoint 0.9.0", - "aws-http 0.9.0", - "aws-sig-auth 0.9.0", - "aws-sigv4 0.9.0", - "aws-smithy-async 0.39.0", - "aws-smithy-client 0.39.0", - "aws-smithy-eventstream", - "aws-smithy-http 0.39.0", - "aws-smithy-http-tower 0.39.0", - "aws-smithy-types 0.39.0", - "aws-smithy-xml 0.39.0", - "aws-types 0.9.0", - "bytes", - "http", - "md5", - "tokio-stream", - "tower", -] - -[[package]] -name = "aws-sdk-sso" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96f9038b498944025a39e426ae38f64e3e8481a9d675469580e1de7397b46ed5" -dependencies = [ - "aws-endpoint 0.9.0", - "aws-http 0.9.0", - "aws-sig-auth 0.9.0", - "aws-smithy-async 0.39.0", - "aws-smithy-client 0.39.0", - "aws-smithy-http 0.39.0", - "aws-smithy-http-tower 0.39.0", - "aws-smithy-json 0.39.0", - "aws-smithy-types 0.39.0", - "aws-types 0.9.0", - "bytes", - "http", - "tokio-stream", - "tower", + "anstyle", + "windows-sys 0.52.0", ] [[package]] -name = "aws-sdk-sso" -version = "0.12.0" +name = "arrayref" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c856987e02db8c5d81cb97e2a24609b05a6289db3d861c5b7d73081d6ecc70d" -dependencies = [ - "aws-endpoint 0.12.0", - "aws-http 0.12.0", - "aws-sig-auth 0.12.0", - "aws-smithy-async 0.42.0", - "aws-smithy-client 0.42.0", - "aws-smithy-http 0.42.0", - "aws-smithy-http-tower 0.42.0", - "aws-smithy-json 0.42.0", - "aws-smithy-types 0.42.0", - "aws-types 0.12.0", - "bytes", - "http", - "tokio-stream", - "tower", -] +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] -name = "aws-sdk-sts" -version = "0.9.0" +name = "arrayvec" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e717e67debcd7f9d87563d08e7d40e3c5c28634a8badc491650d5ad2305befd3" -dependencies = [ - "aws-endpoint 0.9.0", - "aws-http 0.9.0", - "aws-sig-auth 0.9.0", - "aws-smithy-async 0.39.0", - "aws-smithy-client 0.39.0", - "aws-smithy-http 0.39.0", - "aws-smithy-http-tower 0.39.0", - "aws-smithy-query 0.39.0", - "aws-smithy-types 0.39.0", - "aws-smithy-xml 0.39.0", - "aws-types 0.9.0", - "bytes", - "http", - "tower", -] +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "aws-sdk-sts" -version = "0.12.0" +name = "arrow" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0a83f4382e47778399d0e0ae38761eee2fbad1d3bdde21358bd00cea46f36" +checksum = "05048a8932648b63f21c37d88b552ccc8a65afb6dfe9fc9f30ce79174c2e7a85" dependencies = [ - "aws-endpoint 0.12.0", - "aws-http 0.12.0", - "aws-sig-auth 0.12.0", - "aws-smithy-async 0.42.0", - "aws-smithy-client 0.42.0", - "aws-smithy-http 0.42.0", - "aws-smithy-http-tower 0.42.0", - "aws-smithy-query 0.42.0", - "aws-smithy-types 0.42.0", - "aws-smithy-xml 0.42.0", - "aws-types 0.12.0", - "bytes", - "http", - "tower", + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-csv", + "arrow-data", + "arrow-ipc", + "arrow-json", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "arrow-string", ] [[package]] -name = "aws-sig-auth" -version = "0.9.0" +name = "arrow-arith" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0e6e4ba09f502057ad6a4ebf3627f9dae8402e366cf7b36ca1c09cbff8b5834" +checksum = "1d8a57966e43bfe9a3277984a14c24ec617ad874e4c0e1d2a1b083a39cfbf22c" dependencies = [ - "aws-sigv4 0.9.0", - "aws-smithy-eventstream", - "aws-smithy-http 0.39.0", - "aws-types 0.9.0", - "http", - "thiserror", - "tracing", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "num", ] [[package]] -name = "aws-sig-auth" -version = "0.12.0" +name = "arrow-array" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27079eb4062be105e44ab4638ade63cf6448c2df5cb093d885faab8082fd5c9d" +checksum = "16f4a9468c882dc66862cef4e1fd8423d47e67972377d85d80e022786427768c" dependencies = [ - "aws-sigv4 0.12.0", - "aws-smithy-http 0.42.0", - "aws-types 0.12.0", - "http", - "tracing", + "ahash", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "chrono-tz", + "half", + "hashbrown", + "num", ] [[package]] -name = "aws-sigv4" -version = "0.9.0" +name = "arrow-buffer" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea07a5a108ee538793d681d608057218df95c5575f6c0699a1973c27a09334b2" +checksum = "c975484888fc95ec4a632cdc98be39c085b1bb518531b0c80c5d462063e5daa1" dependencies = [ - "aws-smithy-eventstream", - "aws-smithy-http 0.39.0", "bytes", - "form_urlencoded", - "hex", - "http", - "once_cell", - "percent-encoding", - "regex", - "ring", - "time", - "tracing", -] - -[[package]] -name = "aws-sigv4" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0be4a7223c244f3c57426087204cf66e04d1e73adb62571090613889cf6ee57" -dependencies = [ - "aws-smithy-http 0.42.0", - "form_urlencoded", - "hex", - "http", - "once_cell", - "percent-encoding", - "regex", - "ring", - "time", - "tracing", -] - -[[package]] -name = "aws-smithy-async" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ab5373d24e1651860240f122a8d956f7a2094d4553c78979617a7fac640030" -dependencies = [ - "futures-util", - "pin-project-lite", - "tokio", - "tokio-stream", -] - -[[package]] -name = "aws-smithy-async" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cafe7e459caf2e2f834a77062f2c47f7df1956cf3be3060b23bf0721f17e1a4" -dependencies = [ - "futures-util", - "pin-project-lite", - "tokio", - "tokio-stream", + "half", + "num", ] [[package]] -name = "aws-smithy-client" -version = "0.39.0" +name = "arrow-cast" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88e8a92747322eace67f666402a5949da27675f60a2b9098b84b63edef8e6980" +checksum = "da26719e76b81d8bc3faad1d4dbdc1bcc10d14704e63dc17fc9f3e7e1e567c8e" dependencies = [ - "aws-smithy-async 0.39.0", - "aws-smithy-http 0.39.0", - "aws-smithy-http-tower 0.39.0", - "aws-smithy-types 0.39.0", - "bytes", - "fastrand", - "http", - "http-body", - "hyper", - "hyper-rustls", - "lazy_static", - "pin-project", - "pin-project-lite", - "tokio", - "tower", - "tracing", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "atoi", + "base64", + "chrono", + "comfy-table", + "half", + "lexical-core", + "num", + "ryu", ] [[package]] -name = "aws-smithy-client" -version = "0.42.0" +name = "arrow-csv" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de133c5629abab72cad7c4eeab522cb9dfa11ab7ace07d051ef7c29bb54fd370" +checksum = "c13c36dc5ddf8c128df19bab27898eea64bf9da2b555ec1cd17a8ff57fba9ec2" dependencies = [ - "aws-smithy-async 0.42.0", - "aws-smithy-http 0.42.0", - "aws-smithy-http-tower 0.42.0", - "aws-smithy-types 0.42.0", - "bytes", - "fastrand", - "http", - "http-body", - "hyper", - "hyper-rustls", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "csv", + "csv-core", "lazy_static", - "pin-project", - "pin-project-lite", - "tokio", - "tower", - "tracing", -] - -[[package]] -name = "aws-smithy-eventstream" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775b1de8d55fd1cda393c3d81cb5c3dc0e1cac38170883049e2d7a8e16cefad1" -dependencies = [ - "aws-smithy-types 0.39.0", - "bytes", - "crc32fast", -] - -[[package]] -name = "aws-smithy-http" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579d0c2ae96c700499c5330f082c4170b0535835f01eb845056324aa0abd04b4" -dependencies = [ - "aws-smithy-eventstream", - "aws-smithy-types 0.39.0", - "bytes", - "bytes-utils", - "futures-core", - "http", - "http-body", - "hyper", - "once_cell", - "percent-encoding", - "pin-project", - "tokio", - "tokio-util 0.6.9", - "tracing", -] - -[[package]] -name = "aws-smithy-http" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119f6f903f5c27308c732f6c7e460aee91e083b3229db2b21fa40436fc60eda7" -dependencies = [ - "aws-smithy-types 0.42.0", - "bytes", - "bytes-utils", - "futures-core", - "http", - "http-body", - "hyper", - "once_cell", - "percent-encoding", - "pin-project", - "tokio", - "tokio-util 0.7.2", - "tracing", -] - -[[package]] -name = "aws-smithy-http-tower" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "101a2e213acebe624cfb9bfc944de5e33c849e0df0f09c3d3aa3b54368dbe7af" -dependencies = [ - "aws-smithy-http 0.39.0", - "bytes", - "http", - "http-body", - "pin-project", - "tower", - "tracing", -] - -[[package]] -name = "aws-smithy-http-tower" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "991855d24079be0b7485a5753f987f060a153757c7f21729d705f2834366c096" -dependencies = [ - "aws-smithy-http 0.42.0", - "bytes", - "http", - "http-body", - "pin-project", - "tower", - "tracing", -] - -[[package]] -name = "aws-smithy-json" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd21f28535a2538b77274aa590abfb6d37aece3281dfc4c9411c1625d3b9239e" -dependencies = [ - "aws-smithy-types 0.39.0", -] - -[[package]] -name = "aws-smithy-json" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81c96aa965ba386c7206c0913c6cbdfd27cf6b276e8834a7408b67f04994e647" -dependencies = [ - "aws-smithy-types 0.42.0", -] - -[[package]] -name = "aws-smithy-query" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5a2c90311b0d20cf23212a15961cad2b76480863b1f7ce0608d9ece8dacdfb" -dependencies = [ - "aws-smithy-types 0.39.0", - "urlencoding 1.3.3", + "lexical-core", + "regex", ] [[package]] -name = "aws-smithy-query" -version = "0.42.0" +name = "arrow-data" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ec1e497d5e75d42787c9440bbbed5fcdb02b0bdc41fd8b7fe270449e66deff" +checksum = "dd9d6f18c65ef7a2573ab498c374d8ae364b4a4edf67105357491c031f716ca5" dependencies = [ - "aws-smithy-types 0.42.0", - "urlencoding 2.1.0", + "arrow-buffer", + "arrow-schema", + "half", + "num", ] [[package]] -name = "aws-smithy-types" -version = "0.39.0" +name = "arrow-ipc" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962f2da621cd29f272636eebce39ca321c91e02bbb7eb848c4587ac14933d339" +checksum = "e786e1cdd952205d9a8afc69397b317cfbb6e0095e445c69cda7e8da5c1eeb0f" dependencies = [ - "itoa 1.0.1", - "num-integer", - "ryu", - "time", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "flatbuffers", + "lz4_flex", ] [[package]] -name = "aws-smithy-types" -version = "0.42.0" +name = "arrow-json" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b470753cf7e2ce6b7c55f66cb1d17576654878cd41d72bd863a38336569543f4" +checksum = "fb22284c5a2a01d73cebfd88a33511a3234ab45d66086b2ca2d1228c3498e445" dependencies = [ - "itoa 1.0.1", - "num-integer", - "ryu", - "time", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "indexmap", + "lexical-core", + "num", + "serde", + "serde_json", ] [[package]] -name = "aws-smithy-types-convert" -version = "0.39.0" +name = "arrow-ord" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0bd06156b2fa773a2168e46b2a10bef50604481178bbe4839c80b69082d179" +checksum = "42745f86b1ab99ef96d1c0bcf49180848a64fe2c7a7a0d945bc64fa2b21ba9bc" dependencies = [ - "aws-smithy-types 0.39.0", - "chrono", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "half", + "num", ] [[package]] -name = "aws-smithy-xml" -version = "0.39.0" +name = "arrow-row" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829c7efd92b7a6d0536ceb48fd93a289ddf8763c67bffe875d82eae3f9886546" +checksum = "4cd09a518c602a55bd406bcc291a967b284cfa7a63edfbf8b897ea4748aad23c" dependencies = [ - "thiserror", - "xmlparser", + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "half", ] [[package]] -name = "aws-smithy-xml" -version = "0.42.0" +name = "arrow-schema" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d795da895bc0d8a2ba44550ee5ec928af06db8ecdfd0e42b88d046e97e73dd94" -dependencies = [ - "xmlparser", -] +checksum = "9e972cd1ff4a4ccd22f86d3e53e835c2ed92e0eea6a3e8eadb72b4f1ac802cf8" [[package]] -name = "aws-types" -version = "0.9.0" +name = "arrow-select" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68159725aa77553dbc6028f36d8378563cd45b18ef9cf03d1515ac469efacf13" +checksum = "600bae05d43483d216fb3494f8c32fdbefd8aa4e1de237e790dbb3d9f44690a3" dependencies = [ - "aws-smithy-async 0.39.0", - "aws-smithy-client 0.39.0", - "aws-smithy-types 0.39.0", - "rustc_version", - "tracing", - "zeroize", + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num", ] [[package]] -name = "aws-types" -version = "0.12.0" +name = "arrow-string" +version = "52.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5641c99c740574f7a7e70c03773f5ead9f9daf2d95ce6c790ddb4747e000bb0" +checksum = "f0dc1985b67cb45f6606a248ac2b4a288849f196bab8c657ea5589f47cdd55e6" dependencies = [ - "aws-smithy-async 0.42.0", - "aws-smithy-client 0.42.0", - "aws-smithy-http 0.42.0", - "aws-smithy-types 0.42.0", - "http", - "rustc_version", - "tracing", - "zeroize", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "memchr", + "num", + "regex", + "regex-syntax 0.8.4", ] [[package]] -name = "axum" -version = "0.5.4" +name = "async-compression" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4af7447fc1214c1f3a1ace861d0216a6c8bb13965b64bbad9650f375b67689a" +checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" dependencies = [ - "async-trait", - "axum-core", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http", - "http-body", - "hyper", - "itoa 1.0.1", - "matchit", + "bzip2", + "flate2", + "futures-core", + "futures-io", "memchr", - "mime", - "percent-encoding", "pin-project-lite", - "serde", - "sync_wrapper", "tokio", - "tower", - "tower-http", - "tower-layer", - "tower-service", + "xz2", + "zstd", + "zstd-safe", ] [[package]] -name = "axum-core" -version = "0.2.3" +name = "async-trait" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdc19781b16e32f8a7200368a336fa4509d4b72ef15dd4e41df5290855ee1e6" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "mime", + "proc-macro2", + "quote", + "syn 2.0.76", ] [[package]] -name = "ballista" -version = "0.7.0" +name = "atoi" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe41856042559d786291973375953b90329170396f21c8de0afe17f89a5c430" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ - "ballista-core", - "datafusion", - "futures", - "log", - "parking_lot", - "sqlparser", - "tempfile", - "tokio", + "num-traits", ] [[package]] -name = "ballista-core" -version = "0.7.0" +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4682c8b191b71a16a9548c6b4e894cf892644f3eae00f6981174adad25db647" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ - "ahash 0.7.6", - "arrow-flight", - "async-trait", - "chrono", - "clap", - "datafusion", - "datafusion-proto", - "futures", - "hashbrown 0.12.0", - "libloading", - "log", - "once_cell", - "parking_lot", - "parse_arg", - "prost", - "prost-types", - "rustc_version", - "serde", - "sqlparser", - "tokio", - "tonic", - "tonic-build", - "uuid 1.1.0", - "walkdir", + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.4", + "object", + "rustc-demangle", ] [[package]] name = "base64" -version = "0.13.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -912,68 +436,43 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -dependencies = [ - "serde", -] [[package]] name = "blake2" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.3", + "digest", ] [[package]] name = "blake3" -version = "1.3.1" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "digest 0.10.3", ] [[package]] name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array 0.12.4", -] - -[[package]] -name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "block-padding" -version = "0.1.5" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "byte-tools", + "generic-array", ] [[package]] name = "brotli" -version = "3.3.3" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f838e47a451d5a8fa552371f80024dd6ace9b7acdf25c4c3d0f9bc6816fb1c39" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -982,64 +481,51 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.2" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "build_const" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" - [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] -name = "byte-tools" -version = "0.3.1" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "byteorder" -version = "1.4.3" +name = "bytes" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] -name = "bytes" -version = "1.1.0" +name = "bzip2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] [[package]] -name = "bytes-utils" -version = "0.1.1" +name = "bzip2-sys" +version = "0.1.11+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e314712951c43123e5920a446464929adc667a5eade7f8fb3997776c9df6e54" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" dependencies = [ - "bytes", - "either", + "cc", + "libc", + "pkg-config", ] [[package]] @@ -1082,154 +568,191 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", - "js-sys", "num-traits", - "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", ] [[package]] name = "clap" -version = "3.1.5" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ - "atty", - "bitflags 1.3.2", + "clap_builder", "clap_derive", - "indexmap", - "lazy_static", - "os_str_bytes", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", "strsim", - "termcolor", - "textwrap", ] [[package]] name = "clap_derive" -version = "3.1.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ - "heck 0.4.0", - "proc-macro-error", + "heck 0.5.0", "proc-macro2", "quote", - "syn 1.0.94", + "syn 2.0.76", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "color-eyre" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", ] [[package]] -name = "cmake" -version = "0.1.48" +name = "color-spantrace" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ - "cc", + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", ] +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "comfy-table" -version = "5.0.1" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b103d85ca6e209388771bfb7aa6b68a7aeec4afbf6f0a0264bfbf50360e5212e" +checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ - "strum 0.23.0", - "strum_macros 0.23.1", + "strum", + "strum_macros", "unicode-width", ] [[package]] name = "compact_str" -version = "0.8.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" dependencies = [ "castaway", "cfg-if", - "itoa 1.0.1", - "rustversion", + "itoa", "ryu", - "serde", "static_assertions", ] [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "const-random" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] [[package]] -name = "core-foundation" -version = "0.9.3" +name = "const-random-macro" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "core-foundation-sys", - "libc", + "getrandom", + "once_cell", + "tiny-keccak", ] [[package]] -name = "core-foundation-sys" -version = "0.8.3" +name = "constant_time_eq" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] -name = "cpufeatures" -version = "0.2.1" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" -dependencies = [ - "libc", -] +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "crc" -version = "1.8.1" +name = "cpufeatures" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ - "build_const", + "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossterm" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b75a27dc8d220f1f8521ea69cd55a34d720a200ebb3a624d9aa19193d3b432" -dependencies = [ - "bitflags 1.3.2", - "crossterm_winapi", - "libc", - "mio 0.7.14", - "parking_lot", - "serde", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm" -version = "0.28.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", - "mio 1.0.2", + "futures-core", + "libc", + "mio", "parking_lot", - "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -1244,289 +767,438 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.5", + "generic-array", "typenum", ] [[package]] name = "csv" -version = "1.1.6" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" dependencies = [ - "bstr", "csv-core", - "itoa 0.4.8", + "itoa", "ryu", "serde", ] [[package]] name = "csv-core" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ "memchr", ] [[package]] -name = "ct-logs" -version = "0.8.0" +name = "dashmap" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "sct", + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] name = "datafusion" -version = "8.0.0" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8174c458d0266ba442038160fad2c98f02924e6179d6d46175f600b69abb5bb7" +checksum = "ab9d55a9cd2634818953809f75ebe5248b00dd43c3227efb2a51a2d5feaad54e" dependencies = [ - "ahash 0.7.6", + "ahash", "arrow", + "arrow-array", + "arrow-ipc", + "arrow-schema", + "async-compression", "async-trait", - "avro-rs", + "bytes", + "bzip2", "chrono", + "dashmap", "datafusion-common", - "datafusion-data-access", + "datafusion-common-runtime", + "datafusion-execution", "datafusion-expr", + "datafusion-functions", + "datafusion-functions-aggregate", + "datafusion-functions-array", + "datafusion-optimizer", "datafusion-physical-expr", - "datafusion-row", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-sql", + "flate2", "futures", - "hashbrown 0.12.0", - "lazy_static", + "glob", + "half", + "hashbrown", + "indexmap", + "itertools 0.12.1", "log", - "num-traits", "num_cpus", - "ordered-float 3.0.0", + "object_store", "parking_lot", "parquet", "paste", "pin-project-lite", - "rand 0.8.5", - "smallvec", + "rand", "sqlparser", "tempfile", "tokio", - "tokio-stream", - "uuid 1.1.0", + "tokio-util", + "url", + "uuid", + "xz2", + "zstd", ] [[package]] -name = "datafusion-catalogprovider-glue" -version = "0.1.7" +name = "datafusion-common" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d99e7aaab07e644f6aaf38ccf8107e7b02fac1673fbfaf1c1b7e420530b58e3" +checksum = "def66b642959e7f96f5d2da22e1f43d3bd35598f821e5ce351a0553e0f1b7367" dependencies = [ - "aws-config 0.12.0", - "aws-sdk-glue", - "aws-types 0.12.0", - "datafusion", - "datafusion-objectstore-s3", - "pest", - "pest_derive", - "tokio", + "ahash", + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-schema", + "chrono", + "half", + "hashbrown", + "instant", + "libc", + "num_cpus", + "object_store", + "parquet", + "sqlparser", ] [[package]] -name = "datafusion-common" -version = "8.0.0" +name = "datafusion-common-runtime" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c687e12a58d1499b19ee899fa467d122c8cc9223570af6f3eb3c4d9d9be929b" +checksum = "f104bb9cb44c06c9badf8a0d7e0855e5f7fa5e395b887d7f835e8a9457dc1352" dependencies = [ - "arrow", - "avro-rs", - "ordered-float 3.0.0", - "parquet", - "sqlparser", + "tokio", ] [[package]] -name = "datafusion-data-access" -version = "8.0.0" +name = "datafusion-execution" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cca9e1200ddd362d97f22d185e98995ebd3737d2b3d69a200a06a5099fa699a" +checksum = "2ac0fd8b5d80bbca3fc3b6f40da4e9f6907354824ec3b18bbd83fee8cf5c3c3e" dependencies = [ - "async-trait", + "arrow", "chrono", + "dashmap", + "datafusion-common", + "datafusion-expr", "futures", - "glob", + "hashbrown", + "log", + "object_store", "parking_lot", + "rand", "tempfile", - "tokio", + "url", +] + +[[package]] +name = "datafusion-expr" +version = "40.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2103d2cc16fb11ef1fa993a6cac57ed5cb028601db4b97566c90e5fa77aa1e68" +dependencies = [ + "ahash", + "arrow", + "arrow-array", + "arrow-buffer", + "chrono", + "datafusion-common", + "paste", + "serde_json", + "sqlparser", + "strum", + "strum_macros", +] + +[[package]] +name = "datafusion-functions" +version = "40.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a369332afd0ef5bd565f6db2139fb9f1dfdd0afa75a7f70f000b74208d76994f" +dependencies = [ + "arrow", + "base64", + "blake2", + "blake3", + "chrono", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "hashbrown", + "hex", + "itertools 0.12.1", + "log", + "md-5", + "rand", + "regex", + "sha2", + "unicode-segmentation", + "uuid", ] [[package]] -name = "datafusion-expr" -version = "8.0.0" +name = "datafusion-functions-aggregate" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20909d70931b88605b6f121c0ed820cec1d5b802cb51b7b5759f0421be3add8" +checksum = "92718db1aff70c47e5abf9fc975768530097059e5db7c7b78cd64b5e9a11fc77" dependencies = [ - "ahash 0.7.6", + "ahash", "arrow", + "arrow-schema", "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "log", + "paste", "sqlparser", ] [[package]] -name = "datafusion-objectstore-s3" -version = "0.2.1" +name = "datafusion-functions-array" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61a8c89f8099405b3740cac89cef16a74586903b93f4b7f78788840e8429ff1" +checksum = "30bb80f46ff3dcf4bb4510209c2ba9b8ce1b716ac8b7bf70c6bf7dca6260c831" dependencies = [ - "async-trait", - "aws-config 0.9.0", - "aws-sdk-s3", - "aws-smithy-async 0.39.0", - "aws-smithy-types 0.39.0", - "aws-smithy-types-convert", - "aws-types 0.9.0", - "bytes", - "datafusion-data-access", - "futures", - "http", - "num_cpus", - "tokio", + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-schema", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions", + "datafusion-functions-aggregate", + "itertools 0.12.1", + "log", + "paste", ] [[package]] -name = "datafusion-physical-expr" -version = "8.0.0" +name = "datafusion-optimizer" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9083cb20b57216430d5b8e52341782d9520ce77e65c60803e330fd09bdfa6e" +checksum = "82f34692011bec4fdd6fc18c264bf8037b8625d801e6dd8f5111af15cb6d71d3" dependencies = [ - "ahash 0.7.6", "arrow", - "blake2", - "blake3", + "async-trait", "chrono", "datafusion-common", "datafusion-expr", - "datafusion-row", - "hashbrown 0.12.0", - "lazy_static", - "md-5", - "ordered-float 3.0.0", + "datafusion-physical-expr", + "hashbrown", + "indexmap", + "itertools 0.12.1", + "log", "paste", - "rand 0.8.5", - "regex", - "sha2", - "unicode-segmentation", + "regex-syntax 0.8.4", ] [[package]] -name = "datafusion-proto" -version = "8.0.0" +name = "datafusion-physical-expr" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a9cd99d47b7b19fb4b2b96005e8273f51ffe855bd0893742ce26f4c39f59f3" +checksum = "45538630defedb553771434a437f7ca8f04b9b3e834344aafacecb27dc65d5e5" dependencies = [ - "datafusion", - "prost", - "tonic-build", + "ahash", + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-schema", + "arrow-string", + "base64", + "chrono", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "half", + "hashbrown", + "hex", + "indexmap", + "itertools 0.12.1", + "log", + "paste", + "petgraph", + "regex", ] [[package]] -name = "datafusion-row" -version = "8.0.0" +name = "datafusion-physical-expr-common" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f649a2021eefef44e0bef6782d053148b2fef74db7592fecfe87e042d52947a" +checksum = "9d8a72b0ca908e074aaeca52c14ddf5c28d22361e9cb6bc79bb733cd6661b536" dependencies = [ + "ahash", "arrow", "datafusion-common", - "paste", - "rand 0.8.5", + "datafusion-expr", + "hashbrown", + "rand", ] [[package]] -name = "datafusion-tui" -version = "0.1.0" +name = "datafusion-physical-plan" +version = "40.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b504eae6107a342775e22e323e9103f7f42db593ec6103b28605b7b7b1405c4a" dependencies = [ + "ahash", "arrow", - "aws-sdk-s3", - "aws-types 0.9.0", - "ballista", - "clap", - "crossterm 0.23.0", - "datafusion", - "datafusion-catalogprovider-glue", - "datafusion-objectstore-s3", - "dirs", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-schema", + "async-trait", + "chrono", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate", + "datafusion-physical-expr", + "datafusion-physical-expr-common", "futures", - "http", + "half", + "hashbrown", + "indexmap", + "itertools 0.12.1", "log", - "mimalloc", - "ratatui", - "serde", - "serde_json", + "once_cell", + "parking_lot", + "pin-project-lite", + "rand", "tokio", - "tui-logger", - "tui-textarea", - "unicode-width", ] [[package]] -name = "digest" -version = "0.8.1" +name = "datafusion-sql" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +checksum = "e5db33f323f41b95ae201318ba654a9bf11113e58a51a1dff977b1a836d3d889" dependencies = [ - "generic-array 0.12.4", + "arrow", + "arrow-array", + "arrow-schema", + "datafusion-common", + "datafusion-expr", + "log", + "regex", + "sqlparser", + "strum", ] [[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +name = "dft" +version = "0.1.0" dependencies = [ - "generic-array 0.14.5", + "async-trait", + "clap", + "color-eyre", + "crossterm", + "datafusion", + "directories", + "futures", + "futures-util", + "itertools 0.13.0", + "lazy_static", + "open", + "ratatui", + "serde", + "strum", + "tokio", + "tokio-stream", + "tokio-util", + "toml", + "tracing", + "tracing-error", + "tracing-subscriber", + "tui-textarea", ] [[package]] name = "digest" -version = "0.10.3" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.2", + "block-buffer", "crypto-common", "subtle", ] [[package]] -name = "dirs" -version = "4.0.0" +name = "directories" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "winapi", + "windows-sys 0.48.0", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "either" -version = "1.6.1" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" @@ -1539,70 +1211,61 @@ dependencies = [ ] [[package]] -name = "fake-simd" -version = "0.1.2" +name = "eyre" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] [[package]] name = "fastrand" -version = "1.7.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fixedbitset" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flatbuffers" -version = "2.1.2" +version = "24.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b428b715fdbdd1c364b84573b5fdc0f84f8e423661b9f398735278bc7f2b6a" +checksum = "8add37afff2d4ffa83bc748a70b4b1370984f6980768554182424ef71447c35f" dependencies = [ "bitflags 1.3.2", - "smallvec", - "thiserror", + "rustc_version", ] [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ - "cfg-if", "crc32fast", - "libc", - "miniz_oxide", + "miniz_oxide 0.8.0", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "futures" -version = "0.3.21" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1615,9 +1278,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1625,15 +1288,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1642,38 +1305,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 1.0.94", + "syn 2.0.76", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1687,29 +1350,11 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1717,70 +1362,36 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] [[package]] -name = "getrandom" -version = "0.2.5" +name = "gimli" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", -] +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "h2" -version = "0.3.12" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util 0.6.9", - "tracing", -] +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "hashbrown" -version = "0.12.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ - "ahash 0.7.6", + "cfg-if", + "crunchy", + "num-traits", ] [[package]] @@ -1789,24 +1400,15 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.11", + "ahash", "allocator-api2", ] [[package]] name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" @@ -1814,15 +1416,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.9" @@ -1836,161 +1429,108 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "http" -version = "0.2.6" +name = "humantime" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" -dependencies = [ - "bytes", - "fnv", - "itoa 1.0.1", -] +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] -name = "http-body" -version = "0.4.4" +name = "iana-time-zone" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ - "bytes", - "http", - "pin-project-lite", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "http-range-header" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" - -[[package]] -name = "httparse" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.17" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa 1.0.1", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", + "cc", ] [[package]] -name = "hyper-rustls" -version = "0.22.1" +name = "idna" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "ct-logs", - "futures-util", - "hyper", - "log", - "rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls", - "webpki", + "unicode-bidi", + "unicode-normalization", ] [[package]] -name = "hyper-timeout" -version = "0.4.1" +name = "indenter" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] -name = "iana-time-zone" -version = "0.1.60" +name = "indexmap" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", + "equivalent", + "hashbrown", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "instant" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ - "cc", + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "indexmap" -version = "1.7.0" +name = "integer-encoding" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" -dependencies = [ - "autocfg", - "hashbrown 0.11.2", -] +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] -name = "instability" -version = "0.3.2" +name = "is-docker" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" dependencies = [ - "quote", - "syn 2.0.76", + "once_cell", ] [[package]] -name = "instant" -version = "0.1.12" +name = "is-wsl" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" dependencies = [ - "cfg-if", + "is-docker", + "once_cell", ] [[package]] -name = "integer-encoding" -version = "1.1.7" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dc51180a9b377fd75814d0cc02199c20f8e99433d6762f650d39cdbbd3b56f" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.10.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -2006,15 +1546,9 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.1" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" @@ -2027,24 +1561,24 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lexical-core" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3926d8f156019890be4abe5fd3785e0cff1001e06f59c597641fd513a5a284" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" dependencies = [ "lexical-parse-float", "lexical-parse-integer", @@ -2055,9 +1589,9 @@ dependencies = [ [[package]] name = "lexical-parse-float" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4d066d004fa762d9da995ed21aa8845bb9f6e4265f540d716fb4b315197bf0e" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" dependencies = [ "lexical-parse-integer", "lexical-util", @@ -2066,9 +1600,9 @@ dependencies = [ [[package]] name = "lexical-parse-integer" -version = "0.8.0" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c92badda8cc0fc4f3d3cc1c30aaefafb830510c8781ce4e8669881f3ed53ac" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" dependencies = [ "lexical-util", "static_assertions", @@ -2076,18 +1610,18 @@ dependencies = [ [[package]] name = "lexical-util" -version = "0.8.1" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff669ccaae16ee33af90dc51125755efed17f1309626ba5c12052512b11e291" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" dependencies = [ "static_assertions", ] [[package]] name = "lexical-write-float" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5186948c7b297abaaa51560f2581dae625e5ce7dfc2d8fdc56345adb6dc576" +checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" dependencies = [ "lexical-util", "lexical-write-integer", @@ -2096,9 +1630,9 @@ dependencies = [ [[package]] name = "lexical-write-integer" -version = "0.8.0" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece956492e0e40fd95ef8658a34d53a3b8c2015762fdcaaff2167b28de1f56ef" +checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" dependencies = [ "lexical-util", "static_assertions", @@ -2111,42 +1645,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] -name = "libflate" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05605ab2bce11bcfc0e9c635ff29ef8b2ea83f29be257ee7d730cac3ee373093" -dependencies = [ - "adler32", - "crc32fast", - "libflate_lz77", -] - -[[package]] -name = "libflate_lz77" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a734c0493409afcd49deee13c006a04e3586b9761a03543c6272c9c51f2f5a" -dependencies = [ - "rle-decode-fast", -] - -[[package]] -name = "libloading" -version = "0.7.3" +name = "libm" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" -dependencies = [ - "cfg-if", - "winapi", -] +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] -name = "libmimalloc-sys" -version = "0.1.24" +name = "libredox" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7705fc40f6ed493f73584abbb324e74f96b358ff60dfe5659a0f8fc12c590a69" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "cc", + "bitflags 2.6.0", + "libc", ] [[package]] @@ -2157,21 +1668,19 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" @@ -2179,182 +1688,101 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ - "hashbrown 0.14.5", + "hashbrown", ] [[package]] -name = "lz4" -version = "1.23.3" +name = "lz4_flex" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edcb94251b1c375c459e5abe9fb0168c1c826c3370172684844f8f3f8d1a885" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" dependencies = [ - "libc", - "lz4-sys", + "twox-hash", ] [[package]] -name = "lz4-sys" -version = "1.9.3" +name = "lzma-sys" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7be8908e2ed6f31c02db8a9fa962f03e36c53fbfde437363eae3306b85d7e17" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" dependencies = [ "cc", "libc", + "pkg-config", ] [[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "matchit" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" - -[[package]] -name = "md-5" -version = "0.10.1" +name = "matchers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "digest 0.10.3", + "regex-automata 0.1.10", ] [[package]] -name = "md5" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "mimalloc" -version = "0.1.28" +name = "md-5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dfa131390c2f6bdb3242f65ff271fcdaca5ff7b6c08f28398be7f2280e3926" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "libmimalloc-sys", + "cfg-if", + "digest", ] [[package]] -name = "mime" -version = "0.3.16" +name = "memchr" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", - "autocfg", -] - -[[package]] -name = "mio" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" -dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", ] [[package]] -name = "mio" -version = "0.8.3" +name = "miniz_oxide" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", + "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ - "hermit-abi 0.3.9", "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - -[[package]] -name = "multiversion" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025c962a3dd3cc5e0e520aa9c612201d127dcdf28616974961a649dca64f5373" -dependencies = [ - "multiversion-macros", -] - -[[package]] -name = "multiversion-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a3e2bde382ebf960c1f3e79689fa5941625fe9bf694a1cb64af3e85faff3af" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.94", + "wasi", + "windows-sys 0.48.0", ] [[package]] -name = "ntapi" -version = "0.3.7" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ + "overload", "winapi", ] [[package]] name = "num" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ - "num-bigint 0.4.3", + "num-bigint", "num-complex", "num-integer", "num-iter", @@ -2364,50 +1792,37 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" -version = "0.4.0" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.42" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -2416,42 +1831,63 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", - "num-bigint 0.4.3", + "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", ] [[package]] -name = "num_threads" -version = "0.1.4" +name = "object" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c539a50b93a303167eded6e8dff5220cd39447409fb659f4cd24b1f72fe4f133" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ - "libc", + "memchr", +] + +[[package]] +name = "object_store" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6da452820c715ce78221e8202ccc599b4a52f3e1eb3eedb487b680c81a8e3f3" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "futures", + "humantime", + "itertools 0.13.0", + "parking_lot", + "percent-encoding", + "snafu", + "tokio", + "tracing", + "url", + "walkdir", ] [[package]] @@ -2461,49 +1897,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "opaque-debug" -version = "0.2.3" +name = "open" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" -version = "1.1.1" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ "num-traits", ] [[package]] -name = "ordered-float" -version = "3.0.0" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bcbab4bfea7a59c2c0fe47211a1ac4e3e96bea6eb446d704f310bc5c732ae2" -dependencies = [ - "num-traits", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "os_str_bytes" -version = "6.0.0" +name = "owo-colors" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -dependencies = [ - "memchr", -] +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -2511,253 +1946,162 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.32.0", + "windows-targets 0.52.6", ] [[package]] name = "parquet" -version = "13.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6d737baed48775e87a69aa262f1fa2f1d6bd074dedbe9cac244b9aabf2a0b4" -dependencies = [ - "arrow", +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e977b9066b4d3b03555c22bdc442f3fadebd96a39111249113087d0edb2691cd" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-ipc", + "arrow-schema", + "arrow-select", "base64", "brotli", - "byteorder", + "bytes", "chrono", "flate2", - "lz4", + "futures", + "half", + "hashbrown", + "lz4_flex", "num", - "num-bigint 0.4.3", - "parquet-format", - "rand 0.8.5", - "snap 1.0.5", + "num-bigint", + "object_store", + "paste", + "seq-macro", + "snap", "thrift", + "tokio", + "twox-hash", "zstd", + "zstd-sys", ] [[package]] -name = "parquet-format" -version = "4.0.0" +name = "parse-zoneinfo" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f0c06cdcd5460967c485f9c40a821746f5955ad81990533c7fae95dbd9bc0b5" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ - "thrift", + "regex", ] -[[package]] -name = "parse_arg" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14248cc8eced350e20122a291613de29e4fa129ba2731818c4cdbb44fccd3e55" - [[package]] name = "paste" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pest" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" -dependencies = [ - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.1.0" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" -dependencies = [ - "pest", - "pest_generator", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "pest_generator" -version = "2.1.3" +name = "pathdiff" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 1.0.94", -] +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] -name = "pest_meta" -version = "2.1.3" +name = "percent-encoding" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" -dependencies = [ - "maplit", - "pest", - "sha-1", -] +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" -version = "0.6.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b305cc4569dd4e8765bab46261f67ef5d4d11a4b6e745100ee5dad8948b46c" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", ] [[package]] -name = "pin-project" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.94", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "prettyplease" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28f53e8b192565862cf99343194579a022eb9c7dd3a8d03134734803c7b3125" -dependencies = [ - "proc-macro2", - "syn 1.0.94", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "phf" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.94", - "version_check", + "phf_shared", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "phf_codegen" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "phf_generator", + "phf_shared", ] [[package]] -name = "proc-macro2" -version = "1.0.86" +name = "phf_generator" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ - "unicode-ident", + "phf_shared", + "rand", ] [[package]] -name = "prost" -version = "0.10.4" +name = "phf_shared" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "bytes", - "prost-derive", + "siphasher", ] [[package]] -name = "prost-build" -version = "0.10.4" +name = "pin-project-lite" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" -dependencies = [ - "bytes", - "cfg-if", - "cmake", - "heck 0.4.0", - "itertools 0.10.3", - "lazy_static", - "log", - "multimap", - "petgraph", - "prost", - "prost-types", - "regex", - "tempfile", - "which", -] +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] -name = "prost-derive" -version = "0.10.1" +name = "ppv-lite86" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "anyhow", - "itertools 0.10.3", - "proc-macro2", - "quote", - "syn 1.0.94", + "zerocopy", ] [[package]] -name = "prost-types" -version = "0.10.1" +name = "proc-macro2" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ - "bytes", - "prost", + "unicode-ident", ] [[package]] @@ -2769,19 +2113,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -2789,18 +2120,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -2810,53 +2131,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom 0.2.5", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "rand_core 0.5.1", + "getrandom", ] [[package]] name = "ratatui" -version = "0.28.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba6a365afbe5615999275bea2446b970b10a41102500e27ce7678d50d978303" +checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" dependencies = [ "bitflags 2.6.0", "cassowary", "compact_str", - "crossterm 0.28.1", - "instability", + "crossterm", "itertools 0.13.0", "lru", "paste", - "serde", - "strum 0.26.3", - "strum_macros 0.26.4", + "stability", + "strum", + "strum_macros", "unicode-segmentation", "unicode-truncate", "unicode-width", @@ -2864,32 +2166,34 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.11" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.5", - "redox_syscall", + "getrandom", + "libredox", + "thiserror", ] [[package]] name = "regex" -version = "1.5.6" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -2897,42 +2201,38 @@ name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] [[package]] -name = "regex-syntax" -version = "0.6.26" +name = "regex-automata" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "ring" -version = "0.16.20" +name = "regex-syntax" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] -name = "rle-decode-fast" -version = "1.0.3" +name = "rustc-demangle" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" @@ -2956,42 +2256,17 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" -dependencies = [ - "base64", - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls-native-certs" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" -dependencies = [ - "openssl-probe", - "rustls", - "schannel", - "security-framework", -] - [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -3002,114 +2277,83 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi", -] - [[package]] name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "security-framework" -version = "2.6.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "security-framework-sys" -version = "2.6.1" +name = "semver" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] -name = "semver" -version = "1.0.6" +name = "seq-macro" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" +checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 1.0.94", + "syn 2.0.76", ] [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ - "indexmap", - "itoa 1.0.1", + "itoa", + "memchr", "ryu", "serde", ] [[package]] -name = "sha-1" -version = "0.8.2" +name = "serde_spanned" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug", + "serde", ] [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", ] [[package]] @@ -3135,96 +2379,110 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio 0.7.14", - "mio 1.0.2", + "mio", "signal-hook", ] [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" -version = "0.4.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "snap" -version = "0.2.5" +name = "snafu" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95d697d63d44ad8b78b8d235bf85b34022a78af292c8918527c5f0cffdde7f43" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" dependencies = [ - "byteorder", - "lazy_static", + "doc-comment", + "snafu-derive", ] [[package]] -name = "snap" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" - -[[package]] -name = "socket2" -version = "0.4.4" +name = "snafu-derive" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" dependencies = [ - "libc", - "winapi", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "spin" -version = "0.5.2" +name = "snap" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "sqlparser" -version = "0.17.0" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc2739f3a9bfc68e2f7b7695589f6cb0181c88af73ceaee0c84215cd2a2ae28" +checksum = "295e9930cd7a97e58ca2a070541a3ca502b17f5d1fa7157376d0fabd85324f25" dependencies = [ "log", + "sqlparser_derive", ] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "sqlparser_derive" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] [[package]] -name = "strsim" -version = "0.10.0" +name = "stability" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.76", +] [[package]] -name = "strum" -version = "0.18.0" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "strum" -version = "0.23.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" @@ -3232,32 +2490,7 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros 0.26.4", -] - -[[package]] -name = "strum_macros" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" -dependencies = [ - "heck 0.3.3", - "proc-macro2", - "quote", - "syn 1.0.94", -] - -[[package]] -name = "strum_macros" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" -dependencies = [ - "heck 0.3.3", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.94", + "strum_macros", ] [[package]] @@ -3275,325 +2508,197 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.94" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07e33e919ebcd69113d5be0e4d70c5707004ff45188910106854f38b960df4a" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "syn" version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" - -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.94", - "unicode-xid", -] - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ - "winapi-util", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "textwrap" -version = "0.15.0" +name = "tempfile" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 1.0.94", + "syn 2.0.76", ] [[package]] -name = "threadpool" -version = "1.8.1" +name = "thread_local" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "num_cpus", + "cfg-if", + "once_cell", ] [[package]] name = "thrift" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6d965454947cc7266d22716ebfd07b18d84ebaf35eec558586bbb2a8cb6b5b" +checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" dependencies = [ "byteorder", "integer-encoding", - "log", - "ordered-float 1.1.1", - "threadpool", + "ordered-float", ] [[package]] -name = "time" -version = "0.3.7" +name = "tiny-keccak" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ - "libc", - "num_threads", + "crunchy", ] [[package]] -name = "tokio" -version = "1.19.0" +name = "tinyvec" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f392c8f16bda3456c0b00c6de39cb100449b98de55ac41c6cdd2bfcf53a1245" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ - "bytes", - "libc", - "memchr", - "mio 0.8.3", - "num_cpus", - "once_cell", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "winapi", + "tinyvec_macros", ] [[package]] -name = "tokio-io-timeout" -version = "1.2.0" +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" dependencies = [ + "backtrace", + "bytes", "pin-project-lite", - "tokio", + "tokio-macros", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 1.0.94", -] - -[[package]] -name = "tokio-rustls" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" -dependencies = [ - "rustls", - "tokio", - "webpki", + "syn 2.0.76", ] [[package]] name = "tokio-stream" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.9" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ - "bytes", "futures-core", - "futures-sink", - "log", "pin-project-lite", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.2" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] -name = "tonic" -version = "0.7.2" +name = "toml" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9d60db39854b30b835107500cf0aca0b0d14d6e1c3de124217c23a29c2ddb" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64", - "bytes", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost", - "prost-derive", - "tokio", - "tokio-stream", - "tokio-util 0.7.2", - "tower", - "tower-layer", - "tower-service", - "tracing", - "tracing-futures", + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", ] [[package]] -name = "tonic-build" -version = "0.7.2" +name = "toml_datetime" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9263bf4c9bfaae7317c1c2faf7f18491d2fe476f70c414b73bf5d445b00ffa1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ - "prettyplease", - "proc-macro2", - "prost-build", - "quote", - "syn 1.0.94", + "serde", ] [[package]] -name = "tower" -version = "0.4.12" +name = "toml_edit" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "futures-core", - "futures-util", "indexmap", - "pin-project", - "pin-project-lite", - "rand 0.8.5", - "slab", - "tokio", - "tokio-util 0.7.2", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d342c6d58709c0a6d48d48dabbb62d4ef955cf5f0f3bbfd845838e7ae88dbae" -dependencies = [ - "bitflags 1.3.2", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-range-header", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", ] -[[package]] -name = "tower-layer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - [[package]] name = "tracing" -version = "0.1.32" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -3602,87 +2707,96 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.20" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.94", + "syn 2.0.76", ] [[package]] name = "tracing-core" -version = "0.1.23" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ - "lazy_static", + "once_cell", + "valuable", ] [[package]] -name = "tracing-futures" -version = "0.2.5" +name = "tracing-error" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" dependencies = [ - "pin-project", "tracing", + "tracing-subscriber", ] [[package]] -name = "try-lock" -version = "0.2.3" +name = "tracing-log" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] [[package]] -name = "tui-logger" -version = "0.12.0" +name = "tracing-subscriber" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f163885b0550ad9821f52700b6da2e0e249641722be4e2b615ee6c5010ec3e" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ - "chrono", - "fxhash", - "lazy_static", - "log", - "parking_lot", - "ratatui", + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] name = "tui-textarea" -version = "0.6.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c07084342a575cea919eea996b9658a358c800b03d435df581c1d7c60e065a" +checksum = "00524c1366ee838839dd327d1f339ff51846ad4ea85bfa1332859e79adec612c" dependencies = [ - "crossterm 0.28.1", + "crossterm", "ratatui", "unicode-width", ] [[package]] -name = "typed-builder" -version = "0.5.1" +name = "twox-hash" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cea224ddd4282dfc40d1edabbd0c020a12e946e3a48e2c2b8f6ff167ad29fe" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.94", + "cfg-if", + "static_assertions", ] [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] -name = "ucd-trie" -version = "0.1.3" +name = "unicode-bidi" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -3690,11 +2804,20 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-truncate" @@ -3714,87 +2837,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "urlencoding" -version = "1.3.3" +name = "url" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a1f0175e03a0973cf4afd476bef05c26e228520400eb1fd473ad417b1c00ffb" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] [[package]] -name = "urlencoding" -version = "2.1.0" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b90931029ab9b034b300b797048cf23723400aa757e8a2bfb9d748102f9821" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "0.8.2" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ - "getrandom 0.2.5", - "serde", + "getrandom", ] [[package]] -name = "uuid" -version = "1.1.0" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93bbc61e655a4833cf400d0d15bf3649313422fa7572886ad6dab16d79886365" -dependencies = [ - "getrandom 0.2.5", -] +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", - "winapi", "winapi-util", ] -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3803,34 +2892,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", - "syn 1.0.94", + "syn 2.0.76", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3838,54 +2928,33 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 1.0.94", + "syn 2.0.76", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.56" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "which" -version = "4.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" -dependencies = [ - "either", - "lazy_static", - "libc", -] - [[package]] name = "winapi" version = "0.3.9" @@ -3904,11 +2973,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -3923,42 +2992,49 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.32.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", + "windows-targets 0.48.5", ] [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows-targets", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -3967,33 +3043,33 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "windows_aarch64_msvc" -version = "0.32.0" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" @@ -4003,15 +3079,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" @@ -4027,15 +3097,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" @@ -4045,15 +3109,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" @@ -4063,21 +3121,21 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "windows_x86_64_msvc" -version = "0.32.0" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" @@ -4086,19 +3144,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "xmlparser" -version = "0.13.3" +name = "winnow" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] [[package]] -name = "zerocopy" -version = "0.3.0" +name = "xz2" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" dependencies = [ - "byteorder", - "zerocopy-derive 0.2.0", + "lzma-sys", ] [[package]] @@ -4107,18 +3167,8 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy-derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" -dependencies = [ - "proc-macro2", - "syn 1.0.94", - "synstructure", + "byteorder", + "zerocopy-derive", ] [[package]] @@ -4132,37 +3182,30 @@ dependencies = [ "syn 2.0.76", ] -[[package]] -name = "zeroize" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" - [[package]] name = "zstd" -version = "0.11.2+zstd.1.5.2" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" dependencies = [ - "libc", "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" +version = "2.0.12+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" dependencies = [ "cc", - "libc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 81d80f6..75f6be5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,48 +1,35 @@ [package] -edition = "2021" -name = "datafusion-tui" -description = "Terminal based, extensible, interactive data analysis tool using SQL " +name = "dft" version = "0.1.0" -homepage = "https://github.com/datafusion-contrib/datafusion-tui" -repository = "https://github.com/datafusion-contrib/datafusion-tui" -readme = "README.md" -authors = ["Matthew Turner "] -license = "Apache-2.0" -keywords = ["arrow", "query", "sql", "datafusion"] +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-trait = "0.1.80" +clap = { version = "4.5.1", features = ["derive"] } +color-eyre = "0.6.3" +crossterm = { version = "0.27.0", features = ["event-stream"] } +# datafusion = { path = "/Users/matth/OpenSource/arrow-datafusion/datafusion/core"} +datafusion = "40.0.0" +directories = "5.0.1" +futures = "0.3.30" +futures-util = "0.3.30" +itertools = "0.13.0" +lazy_static = "1.4.0" +open = "5.1.4" +ratatui = "0.27.0" +serde = { version = "1.0.197", features = ["derive"] } +strum = "0.26.2" +tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } +tokio-stream = "0.1.15" +tokio-util = "0.7.10" +toml = "0.8.12" +tracing = { version = "0.1.40", features = ["log"] } +tracing-error = "0.2.0" +tracing-subscriber = {version = "0.3.18", features = ["env-filter"] } +tui-logger = "0.12" +tui-textarea = "0.5.3" -arrow = "13" -aws-sdk-s3 = "0.9.0" -aws-types = "0.9.0" -ballista = { version = "0.7", optional = true } -clap = { version = "3", features = ["derive", "cargo"] } -crossterm = { version = "0.23", features = ["serde"] } -datafusion = "8" -datafusion-catalogprovider-glue = { version = "0.1.7", optional = true } -datafusion-objectstore-s3 = { version = "0.2.1", optional = true } -dirs = "4.0" -futures = "0.3" -http = "0.2.6" -log = "0.4" -mimalloc = { version = "0.1", default-features = false } -serde = "1.0.136" -serde_json = "1.0.79" -ratatui = { version = "0.28", default-features = false, features = [ - 'crossterm', - 'serde', -] } -tui-logger = {version="0.12", features=["crossterm"]} -tokio = { version = "1", features = ["full"] } -unicode-width = "0.1.9" -tui-textarea = "0.6.1" - -[features] -s3 = ["datafusion-objectstore-s3"] -glue = ["datafusion-catalogprovider-glue"] - -[[bin]] -name = "dft" -path = "src/main.rs" +[lints.clippy] +clone_on_ref_ptr = "deny" diff --git a/src/app/config.rs b/src/app/config.rs new file mode 100644 index 0000000..3ae501d --- /dev/null +++ b/src/app/config.rs @@ -0,0 +1,121 @@ +use std::{collections::HashMap, path::PathBuf}; + +use directories::{ProjectDirs, UserDirs}; +use lazy_static::lazy_static; +use serde::Deserialize; + +lazy_static! { + pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string(); + pub static ref DATA_FOLDER: Option = + std::env::var(format!("{}_DATA", PROJECT_NAME.clone())) + .ok() + .map(PathBuf::from); + pub static ref LOG_ENV: String = format!("{}_LOGLEVEL", PROJECT_NAME.clone()); + pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME")); +} + +fn project_directory() -> PathBuf { + if let Some(user_dirs) = UserDirs::new() { + return user_dirs.home_dir().join(".config").join("dft"); + }; + + let maybe_project_dirs = ProjectDirs::from("", "", env!("CARGO_PKG_NAME")); + if let Some(project_dirs) = maybe_project_dirs { + project_dirs.data_local_dir().to_path_buf() + } else { + panic!("No known data directory") + } +} + +pub fn get_data_dir() -> PathBuf { + if let Some(data_dir) = DATA_FOLDER.clone() { + data_dir + } else { + project_directory() + } +} + +#[derive(Debug, Default, Deserialize)] +pub struct AppConfig { + #[serde(default = "default_datafusion_config")] + pub datafusion: DataFusionConfig, + #[serde(default = "default_display_config")] + pub display: DisplayConfig, + #[serde(default = "default_interaction_config")] + pub interaction: InteractionConfig, +} + +fn default_datafusion_config() -> DataFusionConfig { + DataFusionConfig::default() +} + +fn default_display_config() -> DisplayConfig { + DisplayConfig::default() +} + +fn default_interaction_config() -> InteractionConfig { + InteractionConfig::default() +} + +#[derive(Debug, Deserialize)] +pub struct DisplayConfig { + #[serde(default = "default_tick_rate")] + pub tick_rate: f64, + #[serde(default = "default_frame_rate")] + pub frame_rate: f64, +} + +fn default_tick_rate() -> f64 { + 5.0 +} + +fn default_frame_rate() -> f64 { + 5.0 +} + +impl Default for DisplayConfig { + fn default() -> Self { + Self { + tick_rate: 5.0, + frame_rate: 5.0, + } + } +} + +#[derive(Clone, Debug, Deserialize)] +pub struct DataFusionConfig { + #[serde(default = "default_stream_batch_size")] + pub stream_batch_size: usize, +} + +fn default_stream_batch_size() -> usize { + 1 +} + +impl Default for DataFusionConfig { + fn default() -> Self { + Self { + stream_batch_size: 1, + } + } +} + +#[derive(Debug, Default, Deserialize)] +pub struct InteractionConfig { + #[serde(default = "default_mouse")] + pub mouse: bool, + #[serde(default = "default_paste")] + pub paste: bool, +} + +fn default_mouse() -> bool { + false +} + +fn default_paste() -> bool { + false +} + +fn default_fetch_limit() -> usize { + 100_usize +} diff --git a/src/app/core.rs b/src/app/core.rs deleted file mode 100644 index 63d2d4d..0000000 --- a/src/app/core.rs +++ /dev/null @@ -1,281 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use std::fs::File; -use std::io::{BufWriter, Write}; - -use datafusion::prelude::SessionConfig; -use log::info; -use ratatui::buffer::Buffer; -use ratatui::crossterm::event::KeyEvent; -use ratatui::layout::Rect; -use ratatui::widgets::Widget; -use tui_logger::TuiWidgetState; - -use crate::app::datafusion::context::{Context, QueryResults}; -use crate::app::editor::Editor; -use crate::app::error::Result; -use crate::app::handlers::key_event_handler; -use crate::cli::args::Args; -use crate::events::{Event, Key}; - -use super::ui::{draw_context_tab, draw_logs_tab, draw_query_history_tab, draw_sql_editor_tab}; - -const DATAFUSION_RC: &str = ".datafusion/.datafusionrc"; - -pub struct Tabs { - pub titles: Vec<&'static str>, - pub index: usize, -} - -#[derive(Default, Debug, Copy, Eq, PartialEq, Clone)] -pub enum TabItem { - #[default] - Editor, - QueryHistory, - Context, - Logs, -} - -impl TabItem { - pub(crate) fn all_values() -> Vec { - vec![ - TabItem::Editor, - TabItem::QueryHistory, - TabItem::Context, - TabItem::Logs, - ] - } - - pub(crate) fn list_index(&self) -> usize { - return TabItem::all_values() - .iter() - .position(|x| x == self) - .unwrap(); - } - - pub(crate) fn title_with_key(&self) -> String { - format!("{} [{}]", self.title(), self.list_index() + 1) - } - - pub(crate) fn title(&self) -> &'static str { - match self { - TabItem::Editor => "SQL Editor", - TabItem::QueryHistory => "Query History", - TabItem::Context => "Context", - TabItem::Logs => "Logs", - } - } -} - -impl TryFrom for TabItem { - type Error = String; - - fn try_from(value: char) -> std::result::Result { - match value { - '1' => Ok(TabItem::Editor), - '2' => Ok(TabItem::QueryHistory), - '3' => Ok(TabItem::Context), - '4' => Ok(TabItem::Logs), - i => Err(format!( - "Invalid tab index: {}, valid range is [1..={}]", - i, 4 - )), - } - } -} - -impl From for usize { - fn from(item: TabItem) -> Self { - match item { - TabItem::Editor => 1, - TabItem::QueryHistory => 2, - TabItem::Context => 3, - TabItem::Logs => 4, - } - } -} - -#[derive(Default, Debug, Copy, Clone)] -pub enum InputMode { - #[default] - Normal, - Editing, - Rc, -} - -/// Status that determines whether app should continue or exit -#[derive(PartialEq)] -pub enum AppReturn { - Continue, - Exit, -} - -pub struct Logs { - pub state: TuiWidgetState, -} - -/// App holds the state of the application -pub struct App<'app> { - /// Application tabs - pub tab_item: TabItem, - /// Current input mode - pub input_mode: InputMode, - /// SQL Editor and it's state - pub editor: Editor<'app>, - /// DataFusion `ExecutionContext` - pub context: Context, - /// Results from DataFusion query - pub query_results: Option, - /// Application logs - pub logs: Logs, -} - -impl<'app> App<'app> { - pub fn new(args: Args, editor: Editor<'app>) -> Self { - let execution_config = SessionConfig::new().with_information_schema(true); - let ctx = Context::new_local(&execution_config); - // let mut ctx: Context = match (args.host, args.port) { - // (Some(ref h), Some(p)) => Context::new_remote(h, p).await.unwrap(), - // _ => Context::new_local(&execution_config).await, - // }; - - let files = args.file; - - let rc = App::get_rc_files(args.rc); - - // if !files.is_empty() { - // ctx.exec_files(files).await - // } else if !rc.is_empty() { - // info!("Executing '~/.datafusion/.datafusionrc' file"); - // ctx.exec_files(rc).await - // } - - let log_state = TuiWidgetState::default(); - - let logs = Logs { state: log_state }; - - App { - tab_item: Default::default(), - input_mode: Default::default(), - editor, - // editor: Editor::default(), - context: ctx, - query_results: None, - logs, - } - } - - fn get_rc_files(rc: Option>) -> Vec { - match rc { - Some(file) => file, - None => { - let mut files = Vec::new(); - let home = dirs::home_dir(); - if let Some(p) = home { - let home_rc = p.join(DATAFUSION_RC); - if home_rc.exists() { - files.push(home_rc.into_os_string().into_string().unwrap()); - } - } - files - } - } - } - - pub async fn event_handler(&'app mut self, event: Event) -> Result { - match event { - Event::KeyInput(k) => key_event_handler(self, k).await, - _ => Ok(AppReturn::Continue), - } - } - - pub async fn reload_rc(&mut self) { - let rc = App::get_rc_files(None); - self.context.exec_files(rc).await; - info!("Reloaded .datafusionrc"); - } - - // pub fn write_rc(&self) -> Result<()> { - // let text = self.editor.input.combine_lines(); - // let rc = App::get_rc_files(None); - // let file = File::create(rc[0].clone())?; - // let mut writer = BufWriter::new(file); - // writer.write_all(text.as_bytes())?; - // Ok(()) - // } - - pub async fn key_handler(&'app mut self, key: KeyEvent) -> AppReturn { - key_event_handler(self, key).await.unwrap() - } - - pub fn update_on_tick(&self) -> AppReturn { - AppReturn::Continue - } -} - -impl Widget for &App<'_> { - /// Note: Ratatui uses Immediate Mode rendering (i.e. the entire UI is redrawn) - /// on every frame based on application state. There is no permanent widget object - /// in memory. - fn render(self, area: Rect, buf: &mut Buffer) { - match self.tab_item { - TabItem::Editor => draw_sql_editor_tab(self, area, buf), - TabItem::QueryHistory => draw_query_history_tab(self, area, buf), - TabItem::Context => draw_context_tab(self, area, buf), - TabItem::Logs => draw_logs_tab(self, area, buf), - } - // let vertical = Layout::vertical([ - // Constraint::Length(1), - // Constraint::Min(0), - // Constraint::Length(1), - // ]); - // let [header_area, inner_area, footer_area] = vertical.areas(area); - // - // let horizontal = Layout::horizontal([Constraint::Min(0)]); - // let [tabs_area] = horizontal.areas(header_area); - // self.render_tabs(tabs_area, buf); - // self.state.tabs.selected.render(inner_area, buf, self); - // self.render_footer(footer_area, buf); - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_tab_item_from_char() { - assert!(TabItem::try_from('0').is_err()); - assert_eq!(TabItem::Editor, TabItem::try_from('1').unwrap()); - assert_eq!(TabItem::QueryHistory, TabItem::try_from('2').unwrap()); - assert_eq!(TabItem::Context, TabItem::try_from('3').unwrap()); - assert_eq!(TabItem::Logs, TabItem::try_from('4').unwrap()); - assert!(TabItem::try_from('5').is_err()); - } - - #[test] - fn test_tab_item_to_usize() { - (0_usize..TabItem::all_values().len()).for_each(|i| { - assert_eq!( - TabItem::all_values()[i], - TabItem::try_from(format!("{}", i + 1).chars().next().unwrap()).unwrap() - ); - assert_eq!(TabItem::all_values()[i].list_index(), i); - }); - } -} diff --git a/src/app/datafusion/catalog_providers/glue.rs b/src/app/datafusion/catalog_providers/glue.rs deleted file mode 100644 index a7581e7..0000000 --- a/src/app/datafusion/catalog_providers/glue.rs +++ /dev/null @@ -1,75 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use datafusion::prelude::SessionContext; - -#[cfg(feature = "glue")] -pub async fn register_glue(ctx: SessionContext) -> SessionContext { - use datafusion_catalogprovider_glue::catalog_provider::glue::GlueCatalogProvider; - use log::{error, info}; - use serde::Deserialize; - use std::fs::File; - use std::sync::Arc; - - #[derive(Deserialize, Debug)] - struct GlueConfig { - databases: Vec, - } - - async fn config_to_glue(cfg: GlueConfig) -> GlueCatalogProvider { - info!("Creating Glue Catalog from: {:?}", cfg); - let mut glue = GlueCatalogProvider::default().await; - for db in cfg.databases { - info!("Registering database {}", db); - let register_results = glue.register_tables(db.as_str()).await.unwrap(); - for result in register_results { - if result.is_err() { - // Only output tables which were not registered... - error!("{}", result.err().unwrap()); - } - } - } - glue - } - - let home = dirs::home_dir(); - if let Some(p) = home { - let glue_config_path = p.join(".datafusion/catalog_providers/glue.json"); - let glue = if glue_config_path.exists() { - let cfg: GlueConfig = - serde_json::from_reader(File::open(glue_config_path).unwrap()).unwrap(); - let glue = config_to_glue(cfg).await; - info!("Created GlueCatalogProvider from config"); - Arc::new(glue) - } else { - let mut glue = GlueCatalogProvider::default().await; - let register_results = glue.register_all().await.unwrap(); - for result in register_results { - if result.is_err() { - // Only output tables which were not registered... - error!("{}", result.err().unwrap()); - } - } - info!("Created GlueCatalogProvider from default AWS credentials"); - Arc::new(glue) - }; - - ctx.register_catalog("glue", glue); - info!("Registered GlueCatalogProvider"); - } - ctx -} diff --git a/src/app/datafusion/catalog_providers/mod.rs b/src/app/datafusion/catalog_providers/mod.rs deleted file mode 100644 index 5b42a8b..0000000 --- a/src/app/datafusion/catalog_providers/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -#[cfg(feature = "glue")] -mod glue; -#[cfg(feature = "glue")] -pub use glue::register_glue; diff --git a/src/app/datafusion/context.rs b/src/app/datafusion/context.rs deleted file mode 100644 index 4f3d93c..0000000 --- a/src/app/datafusion/context.rs +++ /dev/null @@ -1,394 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -//! Context (remote or local) - -use arrow::record_batch::RecordBatch; -use datafusion::dataframe::DataFrame; -use datafusion::error::{DataFusionError, Result}; -use datafusion::execution::context::{SessionConfig, SessionContext}; - -use log::{debug, error, info}; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::sync::Arc; - -use crate::app::ui::Scroll; - -#[derive(Clone, Debug, PartialEq)] -pub struct QueryResultsMeta { - pub query: String, - pub succeeded: bool, - pub error: Option, - pub rows: usize, - pub query_duration: f64, -} - -pub struct QueryResults { - pub batches: Vec, - pub pretty_batches: String, - pub meta: QueryResultsMeta, - pub scroll: Scroll, -} - -impl QueryResults { - pub fn format_timing_info(&self) -> String { - format!( - "[ {} {} in set. Query took {:.3} seconds ] ", - self.meta.rows, - if self.meta.rows == 1 { "row" } else { "rows" }, - self.meta.query_duration - ) - } -} - -/// The CLI supports using a local DataFusion context or a distributed BallistaContext -pub enum Context { - /// In-process execution with DataFusion - Local(SessionContext), - /// Distributed execution with Ballista (if available) - Remote(BallistaContext), -} - -impl Context { - /// create a new remote context with given host and port - pub async fn new_remote(host: &str, port: u16) -> Result { - debug!("Created BallistaContext @ {:?}:{:?}", host, port); - Ok(Context::Remote(BallistaContext::try_new(host, port).await?)) - } - - /// create a local context using the given config - pub fn new_local(config: &SessionConfig) -> Context { - debug!("Created ExecutionContext"); - let ctx = SessionContext::with_config(config.clone()); - - #[cfg(feature = "s3")] - use crate::app::datafusion::object_stores::register_s3; - #[cfg(feature = "s3")] - let ctx = register_s3(ctx).await; - #[cfg(feature = "s3")] - info!("Finished registering s3"); - - #[cfg(feature = "glue")] - use crate::app::datafusion::catalog_providers::register_glue; - #[cfg(feature = "glue")] - let ctx = register_glue(ctx).await; - #[cfg(feature = "glue")] - info!("Finished registering Glue"); - - Context::Local(ctx) - } - - /// execute an SQL statement against the context - pub async fn sql(&mut self, sql: &str) -> Result> { - info!("Executing SQL: {:?}", sql); - match self { - Context::Local(datafusion) => datafusion.sql(sql).await, - Context::Remote(ballista) => ballista.sql(sql).await, - } - } - - pub async fn exec_files(&mut self, files: Vec) { - let files = files - .into_iter() - .map(|file_path| File::open(file_path).unwrap()) - .collect::>(); - for file in files { - let mut reader = BufReader::new(file); - exec_from_lines(self, &mut reader).await; - } - } - - pub fn format_execution_config(&self) -> Option> { - match self { - Context::Local(ctx) => { - let mut config = Vec::new(); - let cfg = ctx.copied_config(); - debug!("Extracting ExecutionConfig attributes"); - config.push(format!("Target Partitions: {}", cfg.target_partitions)); - config.push(format!("Repartition Joins: {}", cfg.repartition_joins)); - config.push(format!( - "Repartition Aggregations: {}", - cfg.repartition_aggregations - )); - config.push(format!("Repartition Windows: {}", cfg.repartition_windows)); - Some(config) - } - Context::Remote(_) => None, - } - } - - pub fn format_physical_optimizers(&self) -> Option> { - match self { - Context::Local(ctx) => { - let physical_opts = ctx.state.read().physical_optimizers.clone(); - debug!("Extracting physical optimizer rules"); - let opts = physical_opts - .iter() - .map(|opt| opt.name().to_string()) - .collect(); - Some(opts) - } - Context::Remote(_) => None, - } - } -} - -async fn exec_from_lines(ctx: &mut Context, reader: &mut BufReader) { - let mut query = "".to_owned(); - - for line in reader.lines() { - match line { - Ok(line) if line.starts_with("--") => { - continue; - } - Ok(line) => { - let line = line.trim_end(); - query.push_str(line); - if line.ends_with(';') { - match exec_and_print(ctx, query).await { - Ok(_) => {} - Err(err) => error!("{:?}", err), - } - query = "".to_owned(); - } else { - query.push('\n'); - } - } - _ => { - break; - } - } - } - - // run the left over query if the last statement doesn't contain ‘;’ - if !query.is_empty() { - match exec_and_print(ctx, query).await { - Ok(_) => {} - Err(err) => error!("{:?}", err), - } - } -} - -async fn exec_and_print(ctx: &mut Context, sql: String) -> Result<()> { - let _df = ctx.sql(&sql).await?; - Ok(()) -} - -// implement wrappers around the BallistaContext to support running without ballista - -// Feature added but not tested as cant install from crates -#[cfg(feature = "ballista")] -use ballista; -#[cfg(feature = "ballista")] -pub struct BallistaContext(ballista::context::BallistaContext); -#[cfg(feature = "ballista")] -impl BallistaContext { - pub async fn try_new(host: &str, port: u16) -> Result { - use ballista::context::BallistaContext; - use ballista::prelude::BallistaConfig; - let builder = BallistaConfig::builder().set("ballista.with_information_schema", "true"); - let config = builder - .build() - .map_err(|e| DataFusionError::Execution(format!("{:?}", e)))?; - let remote_ctx = BallistaContext::remote(host, port, &config) - .await - .map_err(|e| DataFusionError::Execution(format!("{:?}", e)))?; - Ok(Self(remote_ctx)) - } - pub async fn sql(&mut self, sql: &str) -> Result> { - self.0.sql(sql).await - } -} - -#[cfg(not(feature = "ballista"))] -pub struct BallistaContext(); -#[cfg(not(feature = "ballista"))] -impl BallistaContext { - pub async fn try_new(_host: &str, _port: u16) -> Result { - Err(DataFusionError::NotImplemented( - "Remote execution not supported. Compile with feature 'ballista' to enable".to_string(), - )) - } - pub async fn sql(&mut self, _sql: &str) -> Result> { - unreachable!() - } -} - -#[cfg(test)] -mod test { - use crate::app::core::{App, TabItem}; - use crate::app::datafusion::context::{QueryResults, QueryResultsMeta}; - use crate::app::editor::Editor; - use crate::app::handlers::execute_query; - use crate::app::ui::Scroll; - use crate::cli::args::mock_standard_args; - use crate::utils::test_util::assert_results_eq; - - #[test] - fn test_tab_item_from_char() { - assert!(TabItem::try_from('0').is_err()); - assert_eq!(TabItem::Editor, TabItem::try_from('1').unwrap()); - assert_eq!(TabItem::QueryHistory, TabItem::try_from('2').unwrap()); - assert_eq!(TabItem::Context, TabItem::try_from('3').unwrap()); - assert_eq!(TabItem::Logs, TabItem::try_from('4').unwrap()); - assert!(TabItem::try_from('5').is_err()); - } - - #[test] - fn test_tab_item_to_usize() { - (0_usize..TabItem::all_values().len()).for_each(|i| { - assert_eq!( - TabItem::all_values()[i], - TabItem::try_from(format!("{}", i + 1).chars().next().unwrap()).unwrap() - ); - assert_eq!(TabItem::all_values()[i].list_index(), i); - }); - } - - #[tokio::test] - async fn test_select() { - let args = mock_standard_args(); - let ed = Editor::default(); - let mut app = App::new(args, ed); - - let query = "SELECT 1"; - app.editor.input.insert_str(query); - // for char in query.chars() { - // app.editor.input.append_char(char).unwrap(); - // } - - execute_query(&mut app).await.unwrap(); - - let results = app.query_results.unwrap(); - - let expected_meta = QueryResultsMeta { - query: query.to_string(), - succeeded: true, - error: None, - rows: 1, - query_duration: 0f64, - }; - - let expected_results = QueryResults { - batches: Vec::new(), - pretty_batches: String::new(), - meta: expected_meta, - scroll: Scroll { x: 0, y: 0 }, - }; - - assert_results_eq(Some(results), Some(expected_results)); - } - - #[tokio::test] - async fn test_select_with_typo() { - let args = mock_standard_args(); - let mut app = App::new(args).await; - - let query = "SELE 1"; - app.editor.input.insert_str(query); - // for char in query.chars() { - // app.editor.input.append_char(char).unwrap(); - // } - - execute_query(&mut app).await.unwrap(); - - let actual = app.editor.history.pop(); - - let expected_meta = QueryResultsMeta { - query: query.to_string(), - succeeded: false, - error: Some( - "SQL error: ParserError(\"Expected an SQL statement, found: SELE\")".to_string(), - ), - rows: 0, - query_duration: 0f64, - }; - - assert_eq!(actual, Some(expected_meta)); - } - - #[tokio::test] - async fn test_create_table() { - let args = mock_standard_args(); - let mut app = App::new(args).await; - - let query = "CREATE TABLE abc AS VALUES (1,2,3)"; - - app.editor.input.insert_str(query); - // for char in query.chars() { - // app.editor.input.append_char(char).unwrap(); - // } - - execute_query(&mut app).await.unwrap(); - - let results = app.query_results; - - // Need to look into rows here. Originally I believe creating a table - // returned no rows but now it appears to return the created table. - // It seems this is from datafusion side but not certain. - let expected_meta = QueryResultsMeta { - query: query.to_string(), - succeeded: true, - error: None, - rows: 1, - query_duration: 0f64, - }; - - let expected_results = QueryResults { - batches: Vec::new(), - pretty_batches: String::new(), - meta: expected_meta, - scroll: Scroll { x: 0, y: 0 }, - }; - - assert_results_eq(results, Some(expected_results)); - } - - #[tokio::test] - async fn test_multiple_queries() { - let args = mock_standard_args(); - let mut app = App::new(args).await; - - let query = "SELECT 1;SELECT 2;"; - app.editor.input.insert_str(query); - // for char in query.chars() { - // app.editor.input.append_char(char).unwrap(); - // } - - execute_query(&mut app).await.unwrap(); - - let results = app.query_results.unwrap(); - - let expected_meta = QueryResultsMeta { - query: "SELECT 2".to_string(), - succeeded: true, - error: None, - rows: 1, - query_duration: 0f64, - }; - - let expected_results = QueryResults { - batches: Vec::new(), - pretty_batches: String::new(), - meta: expected_meta, - scroll: Scroll { x: 0, y: 0 }, - }; - - assert_results_eq(Some(results), Some(expected_results)); - } -} diff --git a/src/app/datafusion/mod.rs b/src/app/datafusion/mod.rs deleted file mode 100644 index 19097f0..0000000 --- a/src/app/datafusion/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -pub mod catalog_providers; -pub mod context; -pub mod object_stores; diff --git a/src/app/datafusion/object_stores/mod.rs b/src/app/datafusion/object_stores/mod.rs deleted file mode 100644 index c52d036..0000000 --- a/src/app/datafusion/object_stores/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -#[cfg(feature = "s3")] -mod s3; -#[cfg(feature = "s3")] -pub use s3::register_s3; diff --git a/src/app/datafusion/object_stores/s3.rs b/src/app/datafusion/object_stores/s3.rs deleted file mode 100644 index f9890a0..0000000 --- a/src/app/datafusion/object_stores/s3.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use datafusion::prelude::SessionContext; - -#[cfg(feature = "s3")] -pub async fn register_s3(ctx: SessionContext) -> SessionContext { - use aws_sdk_s3::Endpoint; - // use aws_smithy_http::endpoint::Endpoint; - use aws_types::credentials::{Credentials, SharedCredentialsProvider}; - use datafusion_objectstore_s3::object_store::s3::S3FileSystem; - use http::Uri; - use log::info; - use serde::Deserialize; - use std::fs::File; - use std::str::FromStr; - use std::sync::Arc; - - #[derive(Deserialize, Debug)] - struct S3Config { - endpoint: String, - access_key_id: String, - secret_access_key: String, - } - - async fn config_to_s3(cfg: S3Config) -> S3FileSystem { - info!("Creating S3 from: {:?}", cfg); - S3FileSystem::new( - Some(SharedCredentialsProvider::new(Credentials::new( - cfg.access_key_id, - cfg.secret_access_key, - None, - None, - "Static", - ))), // Credentials provider - None, // Region - Some(Endpoint::immutable( - Uri::from_str(cfg.endpoint.as_str()).unwrap(), - )), // Endpoint - None, // RetryConfig - None, // AsyncSleep - None, // TimeoutConfig - ) - .await - } - - let home = dirs::home_dir(); - if let Some(p) = home { - let s3_config_path = p.join(".datafusion/object_stores/s3.json"); - let s3 = if s3_config_path.exists() { - let cfg: S3Config = - serde_json::from_reader(File::open(s3_config_path).unwrap()).unwrap(); - let s3 = config_to_s3(cfg).await; - info!("Created S3FileSystem from custom endpoint"); - Arc::new(s3) - } else { - let s3 = S3FileSystem::default().await; - info!("Created S3FileSystem from default AWS credentials"); - Arc::new(s3) - }; - - ctx.runtime_env().register_object_store("s3", s3); - info!("Registered S3 ObjectStore"); - } - ctx -} diff --git a/src/app/editor/mod.rs b/src/app/editor/mod.rs deleted file mode 100644 index a27d085..0000000 --- a/src/app/editor/mod.rs +++ /dev/null @@ -1,752 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use log::debug; -use std::cmp; -use std::fs::File; -use std::io::{self, BufRead, BufReader}; -use tui_textarea::TextArea; - -use unicode_width::UnicodeWidthStr; - -use crate::app::core::AppReturn; -use crate::app::datafusion::context::QueryResultsMeta; -use crate::app::error::Result; - -const MAX_EDITOR_LINES: u16 = 17; - -/// Single line of text in SQL Editor and cursor over it -#[derive(Debug)] -pub struct Line { - text: io::Cursor, -} - -impl Default for Line { - fn default() -> Line { - Line { - text: io::Cursor::new(String::new()), - } - } -} - -impl Line { - pub fn new(text: String) -> Self { - Line { - text: io::Cursor::new(text), - } - } -} - -/// All lines in SQL Editor and cursor location -#[derive(Debug, Default)] -pub struct Input { - pub lines: Vec, - /// Current line in editor - pub current_row: u16, - /// Current column in editor - pub cursor_column: u16, -} - -impl Input { - pub fn combine_lines(&self) -> String { - let text: Vec<&str> = self - .lines - .iter() - // Replace tabs with spaces - .map(|line| line.text.get_ref().as_str()) - .collect(); - text.join("") - } - - pub fn combine_visible_lines(&self) -> String { - let start = if self.current_row < MAX_EDITOR_LINES { - 0 - } else { - self.current_row - MAX_EDITOR_LINES - } as usize; - - let end = start + (MAX_EDITOR_LINES as usize) + 1; - - let text: Vec<&str> = if start == 0 { - // debug!("Combining all lines"); - self.lines - .iter() - .map(|line| line.text.get_ref().as_str()) - .collect() - } else { - debug!("Combining visible lines: start({}) to end({})", start, end); - self.lines[start..end] - .iter() - .map(|line| line.text.get_ref().as_str()) - .collect() - }; - - text.join("") - } - - pub fn append_char(&mut self, c: char) -> Result { - if self.lines.is_empty() { - let line = Line::default(); - self.lines.push(line) - } - match c { - '\n' => self.new_line(), - '\t' => { - self.lines[self.current_row as usize] - .text - .get_mut() - .push_str(" "); - self.cursor_column += 4 - } - _ => { - self.lines[self.current_row as usize] - .text - .get_mut() - .insert(self.cursor_column as usize, c); - debug!( - "Line after appending {:?} : {:?}", - c, - self.lines[self.current_row as usize].text.get_ref() - ); - self.cursor_column += 1; - } - } - Ok(AppReturn::Continue) - } - - /// Remove last character from the current line. - pub fn pop(&mut self) -> Option { - self.lines[self.current_row as usize].text.get_mut().pop() - } - - /// Moves the cursor one line up. - pub fn up_row(&mut self) -> Result { - if self.current_row > 0 { - match self.lines[self.current_row as usize] - .text - .get_ref() - .is_empty() - { - true => { - self.current_row -= 1; - let new_row_width = - self.lines[self.current_row as usize].text.get_ref().width() as u16; - self.cursor_column = new_row_width; - } - false => { - let previous_col = self.cursor_column; - self.current_row -= 1; - let new_row_width = - self.lines[self.current_row as usize].text.get_ref().width() as u16; - let new_col = cmp::min(previous_col, new_row_width); - self.cursor_column = new_col; - } - } - } - Ok(AppReturn::Continue) - } - - /// Moves the cursor one line down. - pub fn down_row(&mut self) -> Result { - if self.lines.is_empty() { - return Ok(AppReturn::Continue); - } else if self.current_row + 1 < self.lines.len() as u16 { - let previous_col = self.cursor_column; - self.current_row += 1; - let new_row_width = self.lines[self.current_row as usize].text.get_ref().width() as u16; - let new_col = cmp::min(previous_col, new_row_width); - self.cursor_column = new_col; - } - Ok(AppReturn::Continue) - } - - /// Moves the cursor to the next character. - pub fn next_char(&mut self) -> Result { - if self.lines.is_empty() - || self.cursor_column - == self.lines[self.current_row as usize].text.get_ref().width() as u16 - { - return Ok(AppReturn::Continue); - } else if (self.cursor_column + 1 - == self.lines[self.current_row as usize].text.get_ref().width() as u16) - && (self.current_row as usize != self.lines.len() - 1) - { - self.current_row += 1; - self.cursor_column = 0 - } else { - self.cursor_column += 1 - } - Ok(AppReturn::Continue) - } - - /// Moves the cursor to the previous character. - pub fn previous_char(&mut self) -> Result { - if (self.cursor_column == 0) && (self.current_row > 0) { - self.current_row -= 1; - self.cursor_column = self.lines[self.current_row as usize].text.get_ref().width() as u16 - } else if self.cursor_column > 0 { - self.cursor_column -= 1 - } - Ok(AppReturn::Continue) - } - - #[allow(dead_code)] - /// Returns the number of UTF8 characters in the line where the cursor is located. - /// This function is only required, if the editor needs to support non-ascii characters - /// which have more then 1 byte.
- /// Example:
- /// "abcd" has 4 characters and 4 bytes
- /// "äbcd" has 4 characters but 5 bytes (first character requires 2 bytes) - fn number_chars_in_current_line(&self) -> usize { - self.lines[self.current_row as usize] - .text - .get_ref() - .chars() - .count() - } - - pub fn backspace(&mut self) -> Result { - debug!("Backspace entered. Input Before: {:?}", self); - if self.cursor_is_at_editor_beginning() { - debug!("On first column of first line. Unable to backspace") - } else { - match self.lines[self.current_row as usize] - .text - .get_ref() - .is_empty() - { - true => { - self.up_row()?; - // Pop newline character - self.pop(); - } - false => { - if self.cursor_is_at_line_beginning() { - let prior_row_text = - self.lines[self.current_row as usize].text.get_ref().clone(); - self.up_row()?; - self.pop(); - self.lines[self.current_row as usize] - .text - .get_mut() - .push_str(&prior_row_text); - self.lines.remove((self.current_row + 1) as usize); - } else if self.cursor_is_at_line_end() || self.cursor_is_in_line_middle() { - self.lines[self.current_row as usize] - .text - .get_mut() - .remove((self.cursor_column - 1) as usize); - self.cursor_column -= 1 - } - } - }; - } - debug!("Input After: {:?}", self); - Ok(AppReturn::Continue) - } - - pub fn clear(&mut self) -> Result { - let lines = Vec::::new(); - self.lines = lines; - self.current_row = 0; - self.cursor_column = 0; - Ok(AppReturn::Continue) - } - - pub fn tab(&mut self) -> Result { - self.append_char('\t') - } - - fn new_line(&mut self) { - if self.cursor_is_at_line_beginning() { - debug!("Cursor at line beginning"); - let line_empty = self.lines[self.current_row as usize] - .text - .get_ref() - .is_empty(); - - let line = if line_empty { - Line::default() - } else { - let text = self.lines[self.current_row as usize].text.get_ref().clone(); - Line::new(text) - }; - - self.lines[self.current_row as usize] = Line::new(String::from('\n')); - if self.on_last_line() { - self.lines.push(line); - } else { - self.lines.insert((self.current_row + 1) as usize, line) - } - self.current_row += 1; - self.cursor_column = 0; - debug!("Lines: {:?}", self.lines); - } else if self.cursor_is_at_line_end() { - debug!("Cursor at line end"); - self.lines[self.current_row as usize] - .text - .get_mut() - .push('\n'); - let line = Line::default(); - if self.on_last_line() { - self.lines.push(line); - } else { - self.lines.insert((self.current_row + 1) as usize, line) - } - self.current_row += 1; - self.cursor_column = 0; - } else if self.cursor_is_in_line_middle() { - debug!("Cursor in middle of line"); - let new_line: String = self.lines[self.current_row as usize] - .text - .get_mut() - .drain((self.cursor_column as usize)..) - .collect(); - self.lines[self.current_row as usize] - .text - .get_mut() - .push('\n'); - - debug!("New line: {}", new_line); - let line = Line::new(new_line); - if self.on_last_line() { - self.lines.push(line); - } else { - self.lines.insert((self.current_row + 1) as usize, line) - } - self.current_row += 1; - self.cursor_column = 0; - } else { - debug!("Unhandled") - } - } - - fn cursor_is_at_editor_beginning(&self) -> bool { - (self.current_row == 0) && (self.cursor_column == 0) - } - - fn cursor_is_at_line_end(&self) -> bool { - let len = self.lines[self.current_row as usize].text.get_ref().len(); - self.cursor_column as usize == len - } - - fn cursor_is_at_line_beginning(&self) -> bool { - self.cursor_column == 0 - } - - fn cursor_is_in_line_middle(&self) -> bool { - let len = self.lines[self.current_row as usize].text.get_ref().len(); - (self.cursor_column > 0) && ((self.cursor_column as usize) < len) - } - - fn on_last_line(&self) -> bool { - let res = self.current_row as usize == self.lines.len() - 1; - debug!("On last line: {}", res); - res - } -} - -/// The entire editor and it's state -pub struct Editor<'app> { - /// Current value of the input box - pub input: TextArea<'app>, - /// Flag if SQL statement was terminated with ';' - pub sql_terminated: bool, - /// History of QueryResultMeta - pub history: Vec, -} -impl<'app> Default for Editor<'app> { - fn default() -> Editor<'app> { - let input = TextArea::default(); - Editor { - input, - history: Vec::new(), - sql_terminated: false, - } - } -} - -impl Editor<'_> { - // pub fn get_cursor_row(&self) -> u16 { - // if self.input.current_row < MAX_EDITOR_LINES { - // self.input.current_row - // } else { - // MAX_EDITOR_LINES - // } - // } - // - // pub fn get_cursor_column(&self) -> u16 { - // self.input.cursor_column - // } - - pub fn load_file(&mut self, file: File) -> Result<()> { - let buf = BufReader::new(file); - // let contents = buf.lines().collect(); - // let mut lines = Vec::new(); - // for line in buf.lines() { - // let mut line = line?; - // debug!("Line: {}", line); - // line.push('\n'); - // let line = line.replace('\t', " "); - // lines.push(Line::new(line)); - // } - // self.input.insert_str(buf); - // self.input.lines = lines; - Ok(()) - } -} - -// #[cfg(test)] -// mod tests { -// use crate::app::editor::{Input, Line}; -// use std::io::Cursor; - -// #[test] -// #[should_panic] -// fn can_delete_non_ascii_characters() { -// // -// // Due to missing support for non-ascii characters, this test panics. -// // #[should_panic] should be removed as soon as non-ascii chars are supported. -// // -// let mut input: Input = Input { -// lines: vec![Line { -// text: Cursor::new(String::from("äää")), -// }], -// current_row: 0, -// cursor_column: 3, -// }; -// -// input.backspace().expect("Expect that can delete character"); -// assert_eq!(input.current_row, 0); -// assert_eq!(input.cursor_column, 2); -// -// input.backspace().expect("Expect that can delete character"); -// assert_eq!(input.current_row, 0); -// assert_eq!(input.cursor_column, 1); -// } - -// #[test] -// fn next_character_in_one_line() { -// let mut input: Input = Input { -// lines: vec![Line { -// text: Cursor::new(String::from("aaa")), -// }], -// current_row: 0, -// cursor_column: 0, -// }; -// -// input.next_char().expect("Could move to next character"); -// assert_eq!( -// input.cursor_column, 1, -// "When moving once, cursor should be after first character" -// ); -// -// input.next_char().expect("Could move to next character"); -// assert_eq!(input.cursor_column, 2); -// -// input.next_char().expect("Could move to next character"); -// assert_eq!(input.cursor_column, 3); -// -// input.next_char().expect("Could move to next character"); -// assert_eq!( -// input.cursor_column, 3, -// "When line is over and no next line exists, cursor should stop" -// ); -// assert_eq!( -// input.current_row, 0, -// "When line is over and no next line exists, cursor should stop" -// ); -// } - -// #[test] -// fn previous_character_in_one_line() { -// let mut input: Input = Input { -// lines: vec![Line { -// text: Cursor::new(String::from("aaa")), -// }], -// current_row: 0, -// cursor_column: 3, -// }; -// -// input -// .previous_char() -// .expect("Could move to previous character"); -// assert_eq!(input.cursor_column, 2); -// -// input -// .previous_char() -// .expect("Could move to previous character"); -// assert_eq!(input.cursor_column, 1); -// -// input -// .previous_char() -// .expect("Could move to previous character"); -// assert_eq!(input.cursor_column, 0); -// -// input -// .previous_char() -// .expect("Could move to previous character"); -// assert_eq!(input.cursor_column, 0); -// } - -// #[test] -// #[ignore] -// fn jump_to_next_line_on_next_character_at_the_end_of_line() { -// // This functionality is not implemented but could come in later releases. -// let mut input: Input = Input { -// lines: vec![ -// Line { -// text: Cursor::new(String::from("aa")), -// }, -// Line { -// text: Cursor::new(String::from("bb")), -// }, -// ], -// current_row: 0, -// cursor_column: 0, -// }; -// -// input.next_char().expect("Could move to next character"); -// input.next_char().expect("Could move to next character"); -// -// // we expect to jump to the next line here -// input.next_char().expect("Could move to next character"); -// assert_eq!( -// input.current_row, 1, -// "Cursor should have jumped to next line" -// ); -// assert_eq!( -// input.cursor_column, 0, -// "Cursor should be at beginning of the line" -// ); -// -// input.next_char().expect("Could move to next character"); -// assert_eq!(input.current_row, 1); -// assert_eq!( -// input.cursor_column, 1, -// "Cursor should be at the end of second line" -// ); -// -// input.next_char().expect("Could move to next character"); -// assert_eq!(input.current_row, 1); -// assert_eq!(input.cursor_column, 2); -// -// input.next_char().expect("Could move to next character"); -// assert_eq!(input.current_row, 1); -// assert_eq!( -// input.cursor_column, 2, -// "When there is no next line, cursor should stay unchanged" -// ); -// } - -// #[test] -// #[ignore] -// fn jump_to_previous_line_on_previous_character_at_the_beginning_of_line() { -// // This functionality is not implemented but could come in later releases. -// let mut input: Input = Input { -// lines: vec![ -// Line { -// text: Cursor::new(String::from("aa")), -// }, -// Line { -// text: Cursor::new(String::from("bb")), -// }, -// ], -// current_row: 1, -// cursor_column: 0, -// }; -// -// input.previous_char().expect("Could move to next character"); -// assert_eq!( -// input.current_row, 0, -// "Cursor should have jumped to previous line" -// ); -// assert_eq!( -// input.cursor_column, 1, -// "Cursor should be at end of the previous line" -// ); -// } - -// #[test] -// fn non_ascii_character_count() { -// let input: Input = Input { -// lines: vec![Line { -// text: Cursor::new(String::from("äää")), -// }], -// current_row: 0, -// cursor_column: 0, -// }; -// -// assert_eq!(input.number_chars_in_current_line(), 3); -// -// let input2: Input = Input { -// lines: vec![Line { -// text: Cursor::new(String::from("äääb")), -// }], -// current_row: 0, -// cursor_column: 0, -// }; -// assert_eq!(input2.number_chars_in_current_line(), 4); -// } - -// #[test] -// #[should_panic] -// fn test_append_char() { -// // -// // Due to missing support for non-ascii characters, this test panics. -// // #[should_panic] should be removed as soon as non-ascii chars are supported. -// // -// let mut input: Input = Input::default(); -// -// // Input: "" -// input.append_char('ä').expect("Could append a character"); -// assert_eq!(input.current_row, 0); -// assert_eq!(input.cursor_column, 1); -// assert_eq!(input.number_chars_in_current_line(), 1); -// -// // Input: "ä" -// input.append_char('b').expect("Could append a character"); -// assert_eq!(input.current_row, 0); -// assert_eq!(input.cursor_column, 2); -// assert_eq!(input.number_chars_in_current_line(), 2); -// -// // Input: "äb" -// input.append_char('\t').expect("Could append a character"); -// assert_eq!(input.current_row, 0); -// assert_eq!(input.cursor_column, 6); -// assert_eq!(input.number_chars_in_current_line(), 6); -// -// // Input: "äb " -// input.append_char('\n').expect("Could append a character"); -// assert_eq!(input.current_row, 1); -// assert_eq!(input.cursor_column, 0); -// assert_eq!(input.number_chars_in_current_line(), 0); -// -// // Input: "äb \n" -// // "" -// input.append_char('a').expect("Could append a character"); -// assert_eq!(input.current_row, 1); -// assert_eq!(input.cursor_column, 1); -// assert_eq!(input.number_chars_in_current_line(), 1); -// -// // Input: "ä|b \n" <- cursor | -// // "a" -// input.up_row().expect("Can go up"); -// input.append_char('a').expect("Could append a character"); -// assert_eq!(input.current_row, 0); -// assert_eq!(input.cursor_column, 2); -// assert_eq!( -// input.number_chars_in_current_line(), -// 7, -// "Line: {}", -// input.lines[input.current_row as usize].text.get_ref() -// ); -// -// // Input: "äab \n" -// // "a|" <- cursor | -// input.down_row().expect("Can go down"); -// input.previous_char().expect("Can go left"); -// input.append_char('b').expect("Can type a character"); -// // Input: "äab \n" -// // "b|a" <- cursor | -// assert_eq!(input.current_row, 1); -// assert_eq!(input.cursor_column, 1); -// assert_eq!(input.lines[1].text.get_ref(), "ba"); -// } - -// #[test] -// fn test_up_row_and_down_row() { -// let mut input: Input = Input { -// lines: vec![ -// Line { -// text: Cursor::new(String::from("aaaa")), -// }, -// Line { -// text: Cursor::new(String::from("bbbb")), -// }, -// Line { -// text: Cursor::new(String::from("cccc")), -// }, -// Line { -// text: Cursor::new(String::from("")), -// }, -// Line { -// text: Cursor::new(String::from("dddd")), -// }, -// ], -// current_row: 0, -// cursor_column: 2, -// }; -// -// input.up_row().expect("No exception should be thrown."); -// assert_eq!(input.current_row, 0, "At 0th line, up_row has no effect"); -// assert_eq!( -// input.cursor_column, 2, -// "When up_row has no effect, the location inside the line should stay unchanged" -// ); -// -// input.down_row().expect("No exception should be thrown."); -// assert_eq!(input.current_row, 1); -// assert_eq!(input.cursor_column, 2); -// -// input.down_row().expect("No exception should be thrown."); -// assert_eq!(input.current_row, 2); -// assert_eq!(input.cursor_column, 2); -// -// input.down_row().expect("No exception should be thrown."); -// assert_eq!(input.current_row, 3); -// assert_eq!(input.cursor_column, 0); -// -// input.down_row().expect("No exception should be thrown."); -// assert_eq!(input.current_row, 4); -// assert_eq!(input.cursor_column, 0); -// -// input.down_row().expect("No exception should be thrown."); -// assert_eq!(input.current_row, 4, "At last line, down_row has no effect"); -// assert_eq!(input.cursor_column, 0); -// -// input.up_row().expect("No exception should be thrown."); -// assert_eq!(input.current_row, 3); -// assert_eq!( -// input.cursor_column, 0, -// "When coming from an empty line, the cursor should be at 0th position." -// ); -// -// let mut input2: Input = Input::default(); -// // this use case caused a bug -// input2.append_char('a').expect("Can append char"); -// input2.append_char('\n').expect("Can append new line"); -// input2.up_row().expect("Can go up"); -// input2.down_row().expect("Can go down"); -// assert_eq!(input2.current_row, 1); -// assert_eq!(input2.cursor_column, 0); -// input2.append_char('b').expect("Can append char"); -// assert_eq!(input2.current_row, 1); -// assert_eq!(input2.cursor_column, 1); -// assert_eq!(input2.lines[0].text.get_ref(), "a\n"); -// assert_eq!(input2.lines[1].text.get_ref(), "b"); -// } - -// #[test] -// fn empty_editor_backspace() { -// let mut input: Input = Input::default(); -// -// input.backspace().expect("Backspace doesnt move cursor"); -// assert_eq!(input.current_row, 0); -// assert_eq!(input.cursor_column, 0); -// } -// } diff --git a/src/app/error.rs b/src/app/error.rs deleted file mode 100644 index 65270b0..0000000 --- a/src/app/error.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use datafusion::error::DataFusionError; -use std::error; -use std::fmt::{Display, Formatter}; -use std::io; -use std::result; - -pub type Result = result::Result; - -#[derive(Debug)] -pub enum DftError { - DataFusionError(DataFusionError), - IoError(io::Error), - UiError(String), -} - -impl From for DftError { - fn from(e: io::Error) -> Self { - DftError::IoError(e) - } -} - -impl From for DftError { - fn from(e: DataFusionError) -> Self { - DftError::DataFusionError(e) - } -} - -impl Display for DftError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - match *self { - DftError::DataFusionError(ref desc) => write!(f, "DataFusion error: {}", desc), - DftError::IoError(ref desc) => write!(f, "IO error: {}", desc), - DftError::UiError(ref text) => write!(f, "UI Error: {}", text), - } - } -} - -impl error::Error for DftError {} diff --git a/src/app/execution.rs b/src/app/execution.rs new file mode 100644 index 0000000..298507f --- /dev/null +++ b/src/app/execution.rs @@ -0,0 +1,69 @@ +use std::sync::Arc; + +use color_eyre::eyre::Result; +use datafusion::arrow::datatypes::{DataType, Field, Schema, TimeUnit}; +use datafusion::arrow::util::pretty::pretty_format_batches; +use datafusion::common::Constraints; +use datafusion::datasource::stream::{StreamConfig, StreamTable}; +use datafusion::execution::TaskContext; +use datafusion::physical_plan::execute_stream; +use datafusion::prelude::*; +use tokio::sync::broadcast::{self, Sender}; +use tokio_stream::StreamExt; +use tokio_util::sync::CancellationToken; + +use super::config::DataFusionConfig; + +pub struct ExecutionContext { + pub session_ctx: SessionContext, + pub config: DataFusionConfig, + /// Sends WSMessages to DataFusion. We have a separate sender for this, rather than piggy + /// backing on `app_event_tx` because we are only concerned about `WSMessage` and not other + /// `AppEvent`'s. + pub cancellation_token: CancellationToken, +} + +impl ExecutionContext { + pub fn new(config: DataFusionConfig) -> Self { + let cfg = SessionConfig::default() + .with_batch_size(1) + .with_information_schema(true); + + let session_ctx = SessionContext::new_with_config(cfg); + let cancellation_token = CancellationToken::new(); + + Self { + config, + session_ctx, + cancellation_token, + } + } + + pub fn create_tables(&mut self) -> Result<()> { + Ok(()) + } + + pub async fn execute_stream_sql(&mut self, query: &str) -> Result<()> { + let df = self.session_ctx.sql(query).await.unwrap(); + let physical_plan = df.create_physical_plan().await.unwrap(); + // We use small batch size because web socket stream comes in small increments (each + // message usually only has at most a few records). + let stream_cfg = SessionConfig::default().with_batch_size(self.config.stream_batch_size); + let stream_task_ctx = TaskContext::default().with_session_config(stream_cfg); + let mut stream = execute_stream(physical_plan, stream_task_ctx.into()).unwrap(); + + while let Some(maybe_batch) = stream.next().await { + let batch = maybe_batch.unwrap(); + let d = pretty_format_batches(&[batch]).unwrap(); + println!("{}", d); + } + Ok(()) + } + + pub async fn show_catalog(&self) -> Result<()> { + let tables = self.session_ctx.sql("SHOW tables").await?.collect().await?; + let formatted = pretty_format_batches(&tables).unwrap(); + println!("{}", formatted); + Ok(()) + } +} diff --git a/src/app/handlers/edit.rs b/src/app/handlers/edit.rs deleted file mode 100644 index ad00f62..0000000 --- a/src/app/handlers/edit.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -// Enumeration of the possibilities for each key stroke -// Key type => Letter or special character -// Cursor location => Beginning of line, middle of line, end of line -// Text around cursor => Before cursor, on cursor, after cursor -// Lines around cursor => First line, middle line, last line - -use log::debug; -use ratatui::crossterm::event::{KeyCode, KeyEvent}; - -use crate::app::core::{App, AppReturn, InputMode}; -use crate::app::error::Result; -use crate::events::Key; - -pub async fn edit_mode_handler(app: &mut App<'_>, key: KeyEvent) -> Result { - // debug!( - // "{} Entered, current row / col: {} / {}", - // key, app.editor.input.current_row, app.editor.input.cursor_column - // ); - match key.code { - // KeyCode::Char(c) => match c { - // ';' => { - // let result = app.editor.input.append_char(c); - // app.editor.sql_terminated = true; - // result - // } - // _ => app.editor.input.append_char(c), - // }, - KeyCode::Esc => { - app.input_mode = InputMode::Normal; - Ok(AppReturn::Continue) - } - _ => { - app.editor.input.input(key); - Ok(AppReturn::Continue) - } - } -} diff --git a/src/app/handlers/logging.rs b/src/app/handlers/logging.rs deleted file mode 100644 index 58d839f..0000000 --- a/src/app/handlers/logging.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use crate::app::core::{App, AppReturn, TabItem}; -use crate::app::error::Result; -use log::debug; -use ratatui::crossterm::event::{KeyCode, KeyEvent}; -use tui_logger::TuiWidgetEvent; - -pub async fn logging_handler<'app>(app: &'app mut App<'app>, key: KeyEvent) -> Result { - match app.tab_item { - TabItem::Logs => match key.code { - KeyCode::Char('h') => { - app.logs.state.transition(TuiWidgetEvent::HideKey); - Ok(AppReturn::Continue) - } - KeyCode::Char('f') => { - app.logs.state.transition(TuiWidgetEvent::FocusKey); - Ok(AppReturn::Continue) - } - KeyCode::Char('+') => { - app.logs.state.transition(TuiWidgetEvent::PlusKey); - Ok(AppReturn::Continue) - } - KeyCode::Char('-') => { - app.logs.state.transition(TuiWidgetEvent::MinusKey); - Ok(AppReturn::Continue) - } - KeyCode::Char('q') => Ok(AppReturn::Exit), - KeyCode::Char(' ') => { - app.logs.state.transition(TuiWidgetEvent::SpaceKey); - Ok(AppReturn::Continue) - } - KeyCode::Esc => { - app.logs.state.transition(TuiWidgetEvent::EscapeKey); - Ok(AppReturn::Continue) - } - KeyCode::Down => { - app.logs.state.transition(TuiWidgetEvent::DownKey); - Ok(AppReturn::Continue) - } - KeyCode::Up => { - app.logs.state.transition(TuiWidgetEvent::UpKey); - Ok(AppReturn::Continue) - } - KeyCode::Right => { - app.logs.state.transition(TuiWidgetEvent::RightKey); - Ok(AppReturn::Continue) - } - KeyCode::Left => { - app.logs.state.transition(TuiWidgetEvent::LeftKey); - Ok(AppReturn::Continue) - } - KeyCode::PageDown => { - app.logs.state.transition(TuiWidgetEvent::NextPageKey); - Ok(AppReturn::Continue) - } - - KeyCode::PageUp => { - app.logs.state.transition(TuiWidgetEvent::PrevPageKey); - Ok(AppReturn::Continue) - } - KeyCode::Char(c) if c.is_ascii_digit() => { - change_tab(c, app)?; - Ok(AppReturn::Continue) - } - _ => Ok(AppReturn::Continue), - }, - _ => Ok(AppReturn::Continue), - } -} - -fn change_tab(c: char, app: &mut App) -> Result { - match TabItem::try_from(c) { - Ok(tab_item) => { - app.tab_item = tab_item; - } - Err(e) => { - debug!("{}", e); - } - }; - Ok(AppReturn::Continue) -} diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index 481ef67..41076bd 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -1,110 +1,109 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. +use color_eyre::{eyre::eyre, Result}; +use crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers}; +use datafusion::physical_plan::execute_stream; +use tracing::{debug, error, info}; -pub mod edit; -pub mod logging; -pub mod normal; -pub mod rc; +use crate::{app::AppEvent, ui::SelectedTab}; -use arrow::util::pretty::pretty_format_batches; -use datafusion::error::DataFusionError; -use datafusion::prelude::DataFrame; -use log::{debug, error}; -use ratatui::crossterm::event::KeyEvent; -use std::sync::Arc; -use std::time::Instant; +use super::App; -use crate::app::core::{App, AppReturn, InputMode}; -use crate::app::datafusion::context::{QueryResults, QueryResultsMeta}; -use crate::app::error::{DftError, Result}; -use crate::app::ui::Scroll; -use crate::events::Key; - -pub async fn key_event_handler<'app>(app: &'app mut App<'app>, key: KeyEvent) -> Result { - match app.input_mode { - InputMode::Normal => normal::normal_mode_handler(app, key).await, - InputMode::Editing => edit::edit_mode_handler(app, key).await, - InputMode::Rc => rc::rc_mode_handler(app, key).await, +pub fn crossterm_event_handler(event: event::Event) -> Option { + debug!("crossterm::event: {:?}", event); + match event { + event::Event::Key(key) => { + if key.kind == event::KeyEventKind::Press { + Some(AppEvent::Key(key)) + } else { + None + } + } + _ => None, } } -pub async fn execute_query(app: &mut App<'_>) -> Result { - let sql: String = app.editor.input.lines().join("\n"); - handle_queries(app, sql).await?; - Ok(AppReturn::Continue) +fn tab_navigation_handler(app: &mut App, key: KeyCode) { + match key { + KeyCode::Char('e') => app.state.tabs.selected = SelectedTab::Explore, + _ => {} + }; } -async fn handle_queries(app: &mut App<'_>, sql: String) -> Result<()> { - let start = Instant::now(); - let queries = sql.split(';'); - for query in queries { - if !query.is_empty() { - let df = app.context.sql(query).await; - match df { - Ok(df) => handle_successful_query(app, start, query.to_string(), df).await?, - Err(err) => { - handle_failed_query(app, query.to_string(), err)?; - break; +fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { + match key.code { + KeyCode::Char('q') => app.state.should_quit = true, + KeyCode::Char('i') => app.state.explore_tab.edit(), + tab @ (KeyCode::Char('p') | KeyCode::Char('t') | KeyCode::Char('e')) => { + tab_navigation_handler(app, tab) + } + KeyCode::Enter => { + info!("Run query"); + let query = app.state.explore_tab.editor().lines().join(""); + info!("Query: {}", query); + let ctx = app.execution.session_ctx.clone(); + let _event_tx = app.app_event_tx.clone(); + // TODO: Maybe this should be on a separate runtime to prevent blocking main thread / + // runtime + tokio::spawn(async move { + if let Ok(df) = ctx.sql(&query).await { + if let Ok(res) = df.collect().await.map_err(|e| eyre!(e)) { + info!("Results: {:?}", res); + let _ = _event_tx.send(AppEvent::ExploreQueryResult(res)); + } + } else { + error!("Error creating dataframe") } - }; + }); } + _ => {} } - Ok(()) } -async fn handle_successful_query( - app: &mut App<'_>, - start: Instant, - sql: String, - df: Arc, -) -> Result<()> { - debug!("Successfully executed query"); - let batches = df.collect().await.map_err(DftError::DataFusionError)?; - let query_duration = start.elapsed().as_secs_f64(); - let rows: usize = batches.iter().map(|b| b.num_rows()).sum(); - let query_meta = QueryResultsMeta { - query: sql, - succeeded: true, - error: None, - rows, - query_duration, +fn explore_tab_editable_handler(app: &mut App, key: KeyEvent) { + info!("KeyEvent: {:?}", key); + match (key.code, key.modifiers) { + (KeyCode::Esc, _) => app.state.explore_tab.exit_edit(), + (KeyCode::Enter, KeyModifiers::CONTROL) => { + let query = app.state.explore_tab.editor().lines().join(""); + let ctx = app.execution.session_ctx.clone(); + let _event_tx = app.app_event_tx.clone(); + // TODO: Maybe this should be on a separate runtime to prevent blocking main thread / + // runtime + tokio::spawn(async move { + // TODO: Turn this into a match and return the error somehow + if let Ok(df) = ctx.sql(&query).await { + if let Ok(res) = df.collect().await.map_err(|e| eyre!(e)) { + info!("Results: {:?}", res); + let _ = _event_tx.send(AppEvent::ExploreQueryResult(res)); + } + } else { + error!("Error creating dataframe") + } + }); + } + _ => app.state.explore_tab.update_editor_content(key), + } +} + +fn explore_tab_app_event_handler(app: &mut App, event: AppEvent) { + match event { + AppEvent::Key(key) => match app.state.explore_tab.is_editable() { + true => explore_tab_editable_handler(app, key), + false => explore_tab_normal_mode_handler(app, key), + }, + AppEvent::ExploreQueryResult(r) => app.state.explore_tab.set_query_results(r), + AppEvent::Tick => {} + AppEvent::Error => {} + _ => {} }; - app.editor.history.push(query_meta.clone()); - let pretty_batches = pretty_format_batches(&batches).unwrap().to_string(); - app.query_results = Some(QueryResults { - batches, - pretty_batches, - meta: query_meta, - scroll: Scroll { x: 0, y: 0 }, - }); - Ok(()) } -fn handle_failed_query(app: &mut App, sql: String, error: DataFusionError) -> Result<()> { - error!("{}", error); - let err_msg = format!("{}", error); - app.query_results = None; - let query_meta = QueryResultsMeta { - query: sql, - succeeded: false, - error: Some(err_msg), - rows: 0, - query_duration: 0.0, +pub fn app_event_handler(app: &mut App, event: AppEvent) -> Result<()> { + let now = std::time::Instant::now(); + //TODO: Create event to action + debug!("Tui::Event: {:?}", event); + match app.state.tabs.selected { + SelectedTab::Explore => explore_tab_app_event_handler(app, event), }; - app.editor.history.push(query_meta); + debug!("Event handling took: {:?}", now.elapsed()); Ok(()) } diff --git a/src/app/handlers/normal.rs b/src/app/handlers/normal.rs deleted file mode 100644 index d8834b2..0000000 --- a/src/app/handlers/normal.rs +++ /dev/null @@ -1,123 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use crate::app::core::{App, AppReturn, InputMode, TabItem}; -use crate::app::error::Result; -use crate::app::handlers::execute_query; -use crate::app::handlers::logging; -use log::debug; -use ratatui::crossterm::event::{KeyCode, KeyEvent}; - -pub async fn normal_mode_handler<'app>( - app: &'app mut App<'app>, - key: KeyEvent, -) -> Result { - match app.tab_item { - TabItem::Editor => match key.code { - KeyCode::Enter => execute_query(app).await, - KeyCode::Char('c') => { - // app.editor.input.clear()?; - app.input_mode = InputMode::Editing; - Ok(AppReturn::Continue) - } - KeyCode::Char('e') => { - app.input_mode = InputMode::Editing; - Ok(AppReturn::Continue) - } - KeyCode::Char('q') => Ok(AppReturn::Exit), - KeyCode::Char('r') => { - app.input_mode = InputMode::Rc; - Ok(AppReturn::Continue) - } - KeyCode::Char(c) => { - if c.is_ascii_digit() { - change_tab(c, app) - } else { - Ok(AppReturn::Continue) - } - } - KeyCode::Down => { - if let Some(results) = &mut app.query_results { - results.scroll.x += 1 - } - Ok(AppReturn::Continue) - } - KeyCode::PageDown => { - if let Some(results) = &mut app.query_results { - results.scroll.x += 10 - } - Ok(AppReturn::Continue) - } - KeyCode::Up => { - if let Some(results) = &mut app.query_results { - let new_x = match results.scroll.x { - 0 => 0, - n => n - 1, - }; - results.scroll.x = new_x - } - Ok(AppReturn::Continue) - } - KeyCode::PageUp => { - if let Some(results) = &mut app.query_results { - let new_x = match results.scroll.x { - 0 => 0, - n => n - 10, - }; - results.scroll.x = new_x - } - Ok(AppReturn::Continue) - } - - KeyCode::Right => { - if let Some(results) = &mut app.query_results { - results.scroll.y += 3 - } - Ok(AppReturn::Continue) - } - KeyCode::Left => { - if let Some(results) = &mut app.query_results { - let new_y = match results.scroll.y { - 0..=2 => 0, - n => n - 3, - }; - results.scroll.y = new_y - } - Ok(AppReturn::Continue) - } - _ => Ok(AppReturn::Continue), - }, - TabItem::Logs => logging::logging_handler(app, key).await, - _ => match key.code { - KeyCode::Char('q') => Ok(AppReturn::Exit), - KeyCode::Char(c) if c.is_ascii_digit() => change_tab(c, app), - _ => Ok(AppReturn::Continue), - }, - } -} - -fn change_tab(c: char, app: &mut App) -> Result { - match TabItem::try_from(c) { - Ok(tab_item) => { - app.tab_item = tab_item; - } - Err(e) => { - debug!("{}", e); - } - }; - Ok(AppReturn::Continue) -} diff --git a/src/app/handlers/rc.rs b/src/app/handlers/rc.rs deleted file mode 100644 index e688f1c..0000000 --- a/src/app/handlers/rc.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use log::debug; -use ratatui::crossterm::event::{KeyCode, KeyEvent}; -use std::fs::File; - -use crate::app::core::{App, AppReturn, InputMode}; -use crate::app::error::Result; -use crate::events::Key; - -pub async fn rc_mode_handler(app: &mut App<'_>, key: KeyEvent) -> Result { - // debug!( - // "{} Entered, current row / col: {} / {}", - // key, app.editor.input.current_row, app.editor.input.cursor_column - // ); - match key.code { - KeyCode::Char(c) => match c { - 'e' => { - app.input_mode = InputMode::Editing; - Ok(AppReturn::Continue) - } - 'l' => { - let home = dirs::home_dir(); - if let Some(p) = home { - let home_rc = p.join(".datafusion/.datafusionrc"); - let rc = File::open(home_rc)?; - app.editor.load_file(rc)?; - }; - Ok(AppReturn::Continue) - } - 'r' => { - app.reload_rc().await; - Ok(AppReturn::Continue) - } - 'w' => { - // app.write_rc()?; - Ok(AppReturn::Continue) - } - _ => Ok(AppReturn::Continue), - }, - KeyCode::Esc => { - app.input_mode = InputMode::Normal; - Ok(AppReturn::Continue) - } - _ => Ok(AppReturn::Continue), - } -} diff --git a/src/app/mod.rs b/src/app/mod.rs index f9adc0b..f405766 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,25 +1,328 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -pub mod core; -pub mod datafusion; -pub mod editor; -pub mod error; +pub mod config; +pub mod execution; pub mod handlers; -pub mod ui; +pub mod state; -// pub use app::{App, AppReturn, InputMode}; +use crate::cli::DftCli; +use crate::{cli, ui}; +use color_eyre::eyre::eyre; +use color_eyre::Result; +use crossterm::{ + cursor, event, + terminal::{EnterAlternateScreen, LeaveAlternateScreen}, +}; +use datafusion::arrow::array::RecordBatch; +use futures::FutureExt; +use futures_util::StreamExt; +use ratatui::backend::CrosstermBackend; +use ratatui::{prelude::*, style::palette::tailwind, widgets::*}; +use strum::IntoEnumIterator; +use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use tokio::{sync::broadcast, task::JoinHandle}; +use tokio_stream::StreamMap; +use tokio_util::sync::CancellationToken; +use tracing::{debug, error, info}; + +use self::execution::ExecutionContext; +use self::handlers::{app_event_handler, crossterm_event_handler}; + +#[derive(Clone, Debug)] +pub enum AppEvent { + Key(event::KeyEvent), + Error, + Tick, + Quit, + FocusLost, + FocusGained, + Render, + Closed, + Init, + Paste(String), + Mouse(event::MouseEvent), + Resize(u16, u16), + ExploreQueryResult(Vec), +} + +pub struct App<'app> { + pub cli: DftCli, + pub state: state::AppState<'app>, + pub execution: ExecutionContext, + /// Client for making API calls with. Holds a connection pool + /// internally so it should be reused. + pub app_event_tx: UnboundedSender, + pub app_event_rx: UnboundedReceiver, + pub app_cancellation_token: CancellationToken, + pub task: JoinHandle<()>, + pub streams_task: JoinHandle<()>, +} + +impl<'app> App<'app> { + fn new(state: state::AppState<'app>, cli: DftCli) -> Self { + let (app_event_tx, app_event_rx) = mpsc::unbounded_channel(); + let app_cancellation_token = CancellationToken::new(); + let task = tokio::spawn(async {}); + let streams_task = tokio::spawn(async {}); + let execution = ExecutionContext::new(state.config.datafusion.clone()); + + Self { + cli, + state, + task, + streams_task, + app_event_rx, + app_event_tx, + app_cancellation_token, + execution, + } + } + + /// Enter app, optionally setup `crossterm` with UI settings such as alternative screen and + /// mouse capture, then start event loop. + pub fn enter(&mut self, ui: bool) -> Result<()> { + if ui { + crossterm::terminal::enable_raw_mode()?; + crossterm::execute!(std::io::stdout(), EnterAlternateScreen, cursor::Hide)?; + if self.state.config.interaction.mouse { + crossterm::execute!(std::io::stdout(), event::EnableMouseCapture)?; + } + if self.state.config.interaction.paste { + crossterm::execute!(std::io::stdout(), event::EnableBracketedPaste)?; + } + } + self.start_event_loop(); + Ok(()) + } + + /// Stop event loop. Waits for task to finish for up to 100ms. + pub fn stop(&self) -> Result<()> { + self.cancel(); + let mut counter = 0; + while !self.task.is_finished() { + std::thread::sleep(std::time::Duration::from_millis(1)); + counter += 1; + if counter > 50 { + self.task.abort(); + } + if counter > 100 { + tracing::error!("Failed to abort task in 100 milliseconds for unknown reason"); + break; + } + } + Ok(()) + } + + /// Exit app, disabling UI settings such as alternative screen and mouse capture. + pub fn exit(&mut self) -> Result<()> { + self.stop()?; + if crossterm::terminal::is_raw_mode_enabled()? { + if self.state.config.interaction.paste { + crossterm::execute!(std::io::stdout(), event::DisableBracketedPaste)?; + } + if self.state.config.interaction.mouse { + crossterm::execute!(std::io::stdout(), event::DisableMouseCapture)?; + } + crossterm::execute!(std::io::stdout(), LeaveAlternateScreen, cursor::Show)?; + crossterm::terminal::disable_raw_mode()?; + } + Ok(()) + } + + pub fn cancel(&self) { + self.app_cancellation_token.cancel(); + } + + /// Convert `crossterm::Event` into an application Event. If `None` is returned then the + /// crossterm event is not yet supported by application + fn handle_crossterm_event(event: event::Event) -> Option { + crossterm_event_handler(event) + } + + pub fn send_app_event(app_event: AppEvent, tx: &UnboundedSender) { + // TODO: Can maybe make tx optional, add a self param, and get tx from self + let res = tx.send(app_event); + match res { + Ok(_) => debug!("App event sent"), + Err(err) => error!("Error sending app event: {}", err), + }; + } + + /// Start tokio task which runs an event loop responsible for capturing + /// terminal events and triggering render / tick events based + /// on user configured rates. + fn start_app_event_loop(&mut self) { + let render_delay = + std::time::Duration::from_secs_f64(1.0 / self.state.config.display.frame_rate); + debug!("Render delay: {:?}", render_delay); + let tick_delay = + std::time::Duration::from_secs_f64(1.0 / self.state.config.display.tick_rate); + // TODO-V1: Add this to config + let realtime_graph_refresh_delay = std::time::Duration::from_millis(250); + debug!("Tick delay: {:?}", tick_delay); + self.cancel(); + self.app_cancellation_token = CancellationToken::new(); + let _cancellation_token = self.app_cancellation_token.clone(); + let _event_tx = self.app_event_tx.clone(); + + self.task = tokio::spawn(async move { + let mut reader = event::EventStream::new(); + let mut tick_interval = tokio::time::interval(tick_delay); + debug!("Tick interval: {:?}", tick_interval); + let mut render_interval = tokio::time::interval(render_delay); + debug!("Render interval: {:?}", render_interval); + let mut realtime_graph_refresh_interval = + tokio::time::interval(realtime_graph_refresh_delay); + debug!( + "Realtime graph refresh interval: {:?}", + realtime_graph_refresh_interval + ); + _event_tx.send(AppEvent::Init).unwrap(); + loop { + let tick_delay = tick_interval.tick(); + let render_delay = render_interval.tick(); + let realtime_graph_refresh_delay = realtime_graph_refresh_interval.tick(); + let crossterm_event = reader.next().fuse(); + tokio::select! { + _ = _cancellation_token.cancelled() => { + break; + } + maybe_event = crossterm_event => { + let maybe_app_event = match maybe_event { + Some(Ok(event)) => { + Self::handle_crossterm_event(event) + } + Some(Err(_)) => Some(AppEvent::Error), + None => unimplemented!() + }; + if let Some(app_event) = maybe_app_event { + Self::send_app_event(app_event, &_event_tx); + }; + }, + _ = tick_delay => Self::send_app_event(AppEvent::Tick, &_event_tx), + _ = render_delay => Self::send_app_event(AppEvent::Render, &_event_tx), + } + } + }); + } + + fn start_stream_event_loop(&mut self) { + self.cancel(); + self.app_cancellation_token = CancellationToken::new(); + let _cancellation_token = self.app_cancellation_token.clone(); + let _event_tx = self.app_event_tx.clone(); + + self.task = tokio::spawn(async move { + let mut reader = event::EventStream::new(); + _event_tx.send(AppEvent::Init).unwrap(); + loop { + let crossterm_event = reader.next().fuse(); + tokio::select! { + _ = _cancellation_token.cancelled() => { + break; + } + maybe_event = crossterm_event => { + let maybe_app_event = match maybe_event { + Some(Ok(event)) => { + Self::handle_crossterm_event(event) + } + Some(Err(_)) => Some(AppEvent::Error), + None => unimplemented!() + }; + if let Some(app_event) = maybe_app_event { + Self::send_app_event(app_event, &_event_tx); + }; + }, + } + } + }); + } + + /// Dispatch to the appropriate event loop based on the command + pub fn start_event_loop(&mut self) { + match self.cli.command { + Some(cli::Command::App(_)) | None => self.start_app_event_loop(), + } + } + + /// Get the next event from event loop + pub async fn next(&mut self) -> Result { + self.app_event_rx + .recv() + .await + .ok_or(eyre!("Unable to get event")) + } + + /// Load portfolio positions from disk, store them in application state with snapshot + /// information, and connect to the relevant websockets to stream data for the positions. + + fn handle_app_event(&mut self, event: AppEvent) -> Result<()> { + app_event_handler(self, event) + } + + fn render_footer(&self, area: Rect, buf: &mut Buffer) { + Line::raw(" Press q to quit ").centered().render(area, buf); + } + + fn render_tabs(&self, area: Rect, buf: &mut Buffer) { + let titles = ui::SelectedTab::iter().map(ui::SelectedTab::title); + let highlight_style = (Color::default(), tailwind::GRAY.c700); + let selected_tab_index = self.state.tabs.selected as usize; + Tabs::new(titles) + .highlight_style(highlight_style) + .select(selected_tab_index) + .padding("", "") + .divider(" ") + .render(area, buf); + } +} + +impl Widget for &App<'_> { + /// Note: Ratatui uses Immediate Mode rendering (i.e. the entire UI is redrawn) + /// on every frame based on application state. There is no permanent widget object + /// in memory. + fn render(self, area: Rect, buf: &mut Buffer) { + let vertical = Layout::vertical([ + Constraint::Length(1), + Constraint::Min(0), + Constraint::Length(1), + ]); + let [header_area, inner_area, footer_area] = vertical.areas(area); + + let horizontal = Layout::horizontal([Constraint::Min(0)]); + let [tabs_area] = horizontal.areas(header_area); + self.render_tabs(tabs_area, buf); + self.state.tabs.selected.render(inner_area, buf, self); + self.render_footer(footer_area, buf); + } +} + +pub async fn run_app(cli: cli::DftCli, state: state::AppState<'_>) -> Result<()> { + info!("Running app with state: {:?}", state); + let mut app = App::new(state, cli.clone()); + + match &cli.command { + Some(cli::Command::App(_)) | None => { + // app.load_data(false).await?; + let mut terminal = + ratatui::Terminal::new(CrosstermBackend::new(std::io::stdout())).unwrap(); + // Start event loop + app.enter(true)?; + // Main loop for handling events + loop { + let event = app.next().await?; + + if let AppEvent::Render = event.clone() { + terminal.draw(|f| f.render_widget(&app, f.size()))?; + }; + + app.handle_app_event(event)?; + + if app.state.should_quit { + break; + } + } + app.exit()?; + } + } + + Ok(()) +} diff --git a/src/app/state/mod.rs b/src/app/state/mod.rs new file mode 100644 index 0000000..83ee258 --- /dev/null +++ b/src/app/state/mod.rs @@ -0,0 +1,76 @@ +pub mod tabs; + +use crate::app::config::get_data_dir; +use crate::app::state::tabs::explore::ExploreTabState; +use crate::cli; +use crate::ui::SelectedTab; +use std::fs::File; +use std::io::Read; +use std::path::PathBuf; +use tracing::{debug, error, info, instrument}; + +use super::config::AppConfig; + +#[derive(Debug)] +pub struct Tabs { + pub selected: SelectedTab, +} + +impl Default for Tabs { + fn default() -> Self { + Self { + selected: SelectedTab::Explore, + } + } +} + +#[derive(Debug)] +pub struct AppState<'app> { + pub config: AppConfig, + pub should_quit: bool, + pub data_dir: PathBuf, + pub explore_tab: ExploreTabState<'app>, + pub tabs: Tabs, +} + +#[instrument] +pub fn initialize(args: &cli::DftCli) -> AppState { + debug!("Initializing state"); + let data_dir = get_data_dir(); + let config_path = args.get_config(); + let config = if config_path.clone().is_some_and(|p| p.exists()) { + debug!("Config exists"); + let maybe_config_contents = std::fs::read_to_string(config_path.unwrap()); + if let Ok(config_contents) = maybe_config_contents { + let maybe_parsed_config: std::result::Result = + toml::from_str(&config_contents); + match maybe_parsed_config { + Ok(parsed_config) => { + info!("Parsed config: {:?}", parsed_config); + parsed_config + } + Err(err) => { + error!("Error parsing config: {:?}", err); + AppConfig::default() + } + } + } else { + AppConfig::default() + } + } else { + debug!("No config, using default"); + AppConfig::default() + }; + + let tabs = Tabs::default(); + + let explore_tab_state = ExploreTabState::new(); + + AppState { + config, + data_dir, + tabs, + explore_tab: explore_tab_state, + should_quit: false, + } +} diff --git a/src/app/state/tabs/explore.rs b/src/app/state/tabs/explore.rs new file mode 100644 index 0000000..3e49be1 --- /dev/null +++ b/src/app/state/tabs/explore.rs @@ -0,0 +1,56 @@ +use crossterm::event::KeyEvent; +use datafusion::arrow::array::RecordBatch; +use ratatui::style::{palette::tailwind, Style}; +use tui_textarea::TextArea; + +#[derive(Debug)] +pub struct ExploreTabState<'app> { + editor: TextArea<'app>, + editor_editable: bool, + query_results: Option>, // query_handle: Option>>>, +} + +impl<'app> ExploreTabState<'app> { + pub fn new() -> Self { + let empty_text = vec!["Enter a query here.".to_string()]; + // TODO: Enable vim mode from config? + let mut textarea = TextArea::new(empty_text); + textarea.set_line_number_style(Style::default().bg(tailwind::GRAY.c400)); + Self { + editor: textarea, + editor_editable: false, + query_results: None, + // query_handle: None, + } + } + + pub fn editor(&self) -> TextArea { + // TODO: Figure out how to do this without clone. Probably need logic in handler to make + // updates to the Widget and then pass a ref + self.editor.clone() + } + + pub fn update_editor_content(&mut self, key: KeyEvent) { + self.editor.input(key); + } + + pub fn edit(&mut self) { + self.editor_editable = true; + } + + pub fn exit_edit(&mut self) { + self.editor_editable = false; + } + + pub fn is_editable(&self) -> bool { + self.editor_editable + } + + pub fn set_query_results(&mut self, query_results: Vec) { + self.query_results = Some(query_results); + } + + pub fn query_results(&self) -> &Option> { + &self.query_results + } +} diff --git a/src/app/state/tabs/mod.rs b/src/app/state/tabs/mod.rs new file mode 100644 index 0000000..5fb7aaa --- /dev/null +++ b/src/app/state/tabs/mod.rs @@ -0,0 +1 @@ +pub mod explore; diff --git a/src/app/ui.rs b/src/app/ui.rs deleted file mode 100644 index 557d9be..0000000 --- a/src/app/ui.rs +++ /dev/null @@ -1,369 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use ratatui::{ - buffer::Buffer, - layout::{Constraint, Direction, Layout, Rect}, - style::{Color, Modifier, Style}, - text::{Line, Span, Text}, - widgets::{Block, Borders, List, ListItem, Paragraph, Tabs, Widget}, - Frame, -}; -use tui_logger::TuiLoggerSmartWidget; - -use crate::app::core::{App, InputMode, TabItem}; - -pub struct Scroll { - pub x: u16, - pub y: u16, -} - -// pub fn draw_ui(f: &mut Frame, app: &App) { -// match app.tab_item { -// TabItem::Editor => draw_sql_editor_tab(f, app), -// TabItem::QueryHistory => draw_query_history_tab(f, app), -// TabItem::Context => draw_context_tab(f, app), -// TabItem::Logs => draw_logs_tab(f, app), -// } -// } - -pub fn draw_sql_editor_tab(app: &App, area: Rect, buf: &mut Buffer) { - let chunks = Layout::default() - .direction(Direction::Vertical) - .margin(2) - .constraints( - [ - Constraint::Length(1), - Constraint::Length(3), - Constraint::Length(20), - Constraint::Min(1), - ] - .as_ref(), - ) - .split(area); - - let help_message = draw_sql_editor_help(app); - help_message.render(chunks[0], buf); - // f.render_widget(help_message, chunks[0]); - - let tabs = draw_tabs(app); - tabs.render(chunks[1], buf); - // f.render_widget(tabs, chunks[1]); - // let editor = draw_editor(app); - // f.render_widget(editor, chunks[2]); - app.editor.input.render(chunks[2], buf); - // draw_cursor(app, f, &chunks); - let query_results = draw_query_results(app); - query_results.render(chunks[3], buf); - // f.render_widget(query_results, chunks[3]); -} - -pub fn draw_query_history_tab(app: &App, area: Rect, buf: &mut Buffer) { - let chunks = Layout::default() - .direction(Direction::Vertical) - .margin(2) - .constraints( - [ - Constraint::Length(1), - Constraint::Length(3), - Constraint::Min(1), - ] - .as_ref(), - ) - .split(area); - - let help_message = draw_default_help(); - help_message.render(chunks[0], buf); - // f.render_widget(help_message, chunks[0]); - - let tabs = draw_tabs(app); - tabs.render(chunks[1], buf); - // f.render_widget(tabs, chunks[1]); - let query_history = draw_query_history(app); - // f.render_widget(query_history, chunks[2]) - query_history.render(chunks[2], buf); -} - -pub fn draw_context_tab(app: &App, area: Rect, buf: &mut Buffer) { - let chunks = Layout::default() - .direction(Direction::Vertical) - .margin(2) - .constraints( - [ - Constraint::Length(1), - Constraint::Length(3), - Constraint::Min(1), - ] - .as_ref(), - ) - .split(area); - - let help_message = draw_default_help(); - help_message.render(chunks[0], buf); - // f.render_widget(help_message, chunks[0]); - - let tabs = draw_tabs(app); - // f.render_widget(tabs, chunks[1]); - tabs.render(chunks[1], buf); - - draw_context(app, chunks[2], buf); - - // draw_context(f, app, chunks[2]); -} - -pub fn draw_logs_tab(app: &App, area: Rect, buf: &mut Buffer) { - let chunks = Layout::default() - .direction(Direction::Vertical) - .margin(2) - .constraints( - [ - Constraint::Length(1), - Constraint::Length(3), - Constraint::Min(1), - ] - .as_ref(), - ) - .split(area); - - let help_message = draw_default_help(); - // f.render_widget(help_message, chunks[0]); - help_message.render(chunks[0], buf); - - let tabs = draw_tabs(app); - // f.render_widget(tabs, chunks[1]); - tabs.render(chunks[1], buf); - let logs = draw_logs(app); - // f.render_widget(logs, chunks[2]) - logs.render(chunks[2], buf); -} - -fn draw_sql_editor_help<'screen>(app: &App) -> Paragraph<'screen> { - let (msg, style) = match app.input_mode { - InputMode::Normal => ( - vec![ - Span::raw("Press "), - Span::styled("q", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to exit, "), - Span::styled("e", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to start editing, "), - Span::styled("c", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to clear the editor, "), - Span::styled("r", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" start 'rc' mode, "), - Span::styled("#", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to change tabs."), - ], - Style::default().add_modifier(Modifier::RAPID_BLINK), - ), - InputMode::Editing => ( - vec![ - Span::raw("Press "), - Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to stop editing, "), - Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to execute query after closing ';'"), - ], - Style::default(), - ), - InputMode::Rc => ( - vec![ - Span::raw("Press "), - Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to exit Rc mode, "), - Span::styled("l", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to load 'rc' file into editor, "), - Span::styled("r", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to reload 'rc' file, "), - Span::styled("w", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to write 'rc' editor contents to file."), - ], - Style::default(), - ), - }; - let text = Text::from(Line::from(msg)).patch_style(style); - Paragraph::new(text) -} - -fn draw_default_help<'screen>() -> Paragraph<'screen> { - let (msg, style) = ( - vec![ - Span::raw("Press "), - Span::styled("q", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to exit, "), - Span::styled("#", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to change tabs."), - ], - Style::default().add_modifier(Modifier::RAPID_BLINK), - ); - let text = Text::from(Line::from(msg)).patch_style(style); - Paragraph::new(text) -} - -// fn draw_editor<'screen>(app: &App, area: R) -> Paragraph<'screen> { -// app.editor.input.render(area, buf) -// Paragraph::new(app.editor.input.combine_visible_lines()) -// .style(match app.input_mode { -// InputMode::Editing => Style::default().fg(Color::Yellow), -// _ => Style::default(), -// }) -// .block(Block::default().borders(Borders::ALL).title("SQL Editor")) -// } - -// fn draw_cursor(app: &App, f: &mut Frame, chunks: &[Rect]) { -// if let InputMode::Editing = app.input_mode { -// // Make the cursor visible and ask tui-rs to put it at the specified coordinates after rendering -// f.set_cursor( -// // Put cursor past the end of the input text -// chunks[2].x + app.editor.get_cursor_column() + 1, -// // Move one line down, from the border to the input line -// chunks[2].y + app.editor.get_cursor_row() + 1, -// ) -// } -// } - -fn draw_query_results<'app>(app: &'app App) -> Paragraph<'app> { - // Query results not shown correctly on error. For example `show tables for x` - let (query_results, duration) = match &app.query_results { - Some(query_results) => { - let query_meta = app.editor.history.last().unwrap(); - let results = if query_meta.query.starts_with("CREATE") { - Paragraph::new(String::from("Table created")) - } else { - Paragraph::new(query_results.pretty_batches.as_str()) - .scroll((query_results.scroll.x, query_results.scroll.y)) - }; - let query_duration_info = query_results.format_timing_info(); - (results, query_duration_info) - } - None => { - let last_query = &app.editor.history.last(); - let no_queries_text = match last_query { - Some(query_meta) => { - if let Some(err) = query_meta.error.clone() { - Paragraph::new(err) - } else { - Paragraph::new(query_meta.query.clone()) - } - } - None => Paragraph::new("No queries yet"), - }; - (no_queries_text, String::new()) - } - }; - - let title = format!("Query Results {}", duration); - query_results.block(Block::default().borders(Borders::TOP).title(title)) -} - -fn draw_tabs<'screen>(app: &App) -> Tabs<'screen> { - let titles: Vec<_> = TabItem::all_values() - .iter() - .map(|tab| tab.title_with_key()) - .map(|t| Line::from(vec![Span::styled(t, Style::default())])) - .collect(); - - Tabs::new(titles) - .block(Block::default().borders(Borders::ALL).title("Tabs")) - .select(app.tab_item.list_index()) - .style(Style::default()) - .highlight_style(Style::default().add_modifier(Modifier::BOLD)) -} - -fn draw_query_history<'screen>(app: &App) -> List<'screen> { - let messages: Vec = app - .editor - .history - .iter() - .enumerate() - .map(|(i, m)| { - let content = vec![ - Line::from(Span::raw(format!( - "Query {} [ {} rows took {:.3} seconds ]", - i, m.rows, m.query_duration - ))), - Line::from(Span::raw(m.query.clone())), - Line::from(Span::raw(String::new())), - ]; - ListItem::new(content) - }) - .collect(); - - List::new(messages).block( - Block::default() - .borders(Borders::ALL) - .title("Query History"), - ) -} - -fn draw_logs<'app>(app: &'app App) -> TuiLoggerSmartWidget<'app> { - TuiLoggerSmartWidget::default() - .style_error(Style::default().fg(Color::Red)) - .style_debug(Style::default().fg(Color::Green)) - .style_warn(Style::default().fg(Color::Yellow)) - .style_trace(Style::default().fg(Color::Gray)) - .style_info(Style::default().fg(Color::Blue)) - .state(&app.logs.state) -} - -fn draw_context(app: &App, area: Rect, buf: &mut Buffer) { - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) - .split(area); - - let exec_config = draw_execution_config(app); - // f.render_widget(exec_config, context[0]); - exec_config.render(chunks[0], buf); - - let physical_opts = draw_physical_optimizers(app); - // f.render_widget(physical_opts, context[1]); - physical_opts.render(chunks[1], buf); -} - -fn draw_execution_config<'screen>(app: &App) -> List<'screen> { - let exec_config = app.context.format_execution_config().unwrap(); - let config: Vec = exec_config - .iter() - .map(|i| { - let content = vec![Line::from(Span::raw(i.to_string()))]; - ListItem::new(content) - }) - .collect(); - - List::new(config).block( - Block::default() - .borders(Borders::ALL) - .title("ExecutionConfig"), - ) -} - -fn draw_physical_optimizers<'screen>(app: &App) -> List<'screen> { - let physical_optimizers = app.context.format_physical_optimizers().unwrap(); - let opts: Vec = physical_optimizers - .iter() - .map(|i| { - let content = vec![Line::from(Span::raw(i.to_string()))]; - ListItem::new(content) - }) - .collect(); - - List::new(opts).block( - Block::default() - .borders(Borders::ALL) - .title("Physical Optimizers"), - ) -} diff --git a/src/cli/args.rs b/src/cli/args.rs deleted file mode 100644 index 3ea0fb3..0000000 --- a/src/cli/args.rs +++ /dev/null @@ -1,112 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use clap::Parser; -use std::path::Path; - -use super::print_format::PrintFormat; - -#[derive(Debug, Parser, PartialEq)] -#[clap(author, version, about, long_about= None)] -pub struct Args { - #[clap( - short = 'p', - long, - help = "Path to your data, default to current directory", - validator(is_valid_data_dir) - )] - pub data_path: Option, - - #[clap( - short = 'c', - long, - help = "The batch size of each query, or use DataFusion default", - validator(is_valid_batch_size) - )] - pub batch_size: Option, - - #[clap( - short, - long, - multiple_values = true, - help = "Execute commands from file(s), then exit", - validator(is_valid_file) - )] - pub file: Vec, - - #[clap( - short = 'r', - long, - multiple_values = true, - help = "Run the provided files on startup instead of ~/.datafusionrc", - validator(is_valid_file), - conflicts_with = "file" - )] - pub rc: Option>, - - #[clap(long, arg_enum, default_value_t = PrintFormat::Table)] - pub format: PrintFormat, - - #[clap(long, help = "Ballista scheduler host")] - pub host: Option, - - #[clap(long, help = "Ballista scheduler port")] - pub port: Option, - - #[clap( - short, - long, - help = "Reduce printing other than the results and work quietly" - )] - pub quiet: bool, -} - -fn is_valid_file(dir: &str) -> std::result::Result<(), String> { - if Path::new(dir).is_file() { - Ok(()) - } else { - Err(format!("Invalid file '{}'", dir)) - } -} - -fn is_valid_data_dir(dir: &str) -> std::result::Result<(), String> { - if Path::new(dir).is_dir() { - Ok(()) - } else { - Err(format!("Invalid data directory '{}'", dir)) - } -} - -fn is_valid_batch_size(size: &str) -> std::result::Result<(), String> { - match size.parse::() { - Ok(size) if size > 0 => Ok(()), - _ => Err(format!("Invalid batch size '{}'", size)), - } -} - -pub fn mock_standard_args() -> Args { - Args { - data_path: None, - batch_size: None, - file: Vec::new(), - rc: None, - format: PrintFormat::Table, - host: None, - port: None, - quiet: false, - } -} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 00e62f5..4747721 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,19 +1,51 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -pub mod args; -pub mod print_format; +use std::path::{Path, PathBuf}; + +use clap::{Parser, Subcommand}; + +use crate::app::config::get_data_dir; + +const LONG_ABOUT: &str = " +Dft + +Environment Variables +RUST_LOG { trace | debug | info | error }: Standard across rust ecosystem for determining log level of application. Default is info. +LOG_SINK { stdout | file }: Write logs to file or stdout. Default is file. +"; + +#[derive(Clone, Debug, Parser)] +#[command(author, version, about, long_about = LONG_ABOUT)] +pub struct DftCli { + #[command(subcommand)] + pub command: Option, +} + +fn get_config_path(cli_config_arg: &Option) -> PathBuf { + if let Some(config) = cli_config_arg { + Path::new(config).to_path_buf() + } else { + let mut config = get_data_dir(); + config.push("config.toml"); + config + } +} + +impl DftCli { + pub fn get_config(&self) -> Option { + match &self.command { + Some(Command::App(args)) => Some(get_config_path(&args.config)), + _ => None, + } + } +} +// TODO: Add a command to get schema / market information +#[derive(Clone, Debug, Subcommand)] +pub enum Command { + App(AppArgs), +} + +#[derive(Clone, Debug, Default, clap::Args)] +#[command(version, about, long_about = None, hide = true)] +pub struct AppArgs { + #[arg(short, long)] + pub config: Option, +} diff --git a/src/cli/print_format.rs b/src/cli/print_format.rs deleted file mode 100644 index 51e1b93..0000000 --- a/src/cli/print_format.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -//! Print format variants -use arrow::csv::writer::WriterBuilder; -use arrow::json::{ArrayWriter, LineDelimitedWriter}; -use datafusion::arrow::record_batch::RecordBatch; -use datafusion::arrow::util::pretty; -use datafusion::error::{DataFusionError, Result}; -use std::str::FromStr; - -/// Allow records to be printed in different formats -#[derive(Debug, PartialEq, Eq, clap::ArgEnum, Clone)] -pub enum PrintFormat { - Csv, - Tsv, - Table, - Json, - NdJson, -} - -impl FromStr for PrintFormat { - type Err = String; - - fn from_str(s: &str) -> std::result::Result { - clap::ArgEnum::from_str(s, true) - } -} - -macro_rules! batches_to_json { - ($WRITER: ident, $batches: expr) => {{ - let mut bytes = vec![]; - { - let mut writer = $WRITER::new(&mut bytes); - writer.write_batches($batches)?; - writer.finish()?; - } - String::from_utf8(bytes).map_err(|e| DataFusionError::Execution(e.to_string()))? - }}; -} - -fn print_batches_with_sep(batches: &[RecordBatch], delimiter: u8) -> Result { - let mut bytes = vec![]; - { - let builder = WriterBuilder::new() - .has_headers(true) - .with_delimiter(delimiter); - let mut writer = builder.build(&mut bytes); - for batch in batches { - writer.write(batch)?; - } - } - let formatted = - String::from_utf8(bytes).map_err(|e| DataFusionError::Execution(e.to_string()))?; - Ok(formatted) -} - -impl PrintFormat { - /// print the batches to stdout using the specified format - pub fn print_batches(&self, batches: &[RecordBatch]) -> Result<()> { - match self { - Self::Csv => println!("{}", print_batches_with_sep(batches, b',')?), - Self::Tsv => println!("{}", print_batches_with_sep(batches, b'\t')?), - Self::Table => pretty::print_batches(batches)?, - Self::Json => println!("{}", batches_to_json!(ArrayWriter, batches)), - Self::NdJson => { - println!("{}", batches_to_json!(LineDelimitedWriter, batches)) - } - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use arrow::array::Int32Array; - use arrow::datatypes::{DataType, Field, Schema}; - use datafusion::from_slice::FromSlice; - use std::sync::Arc; - - #[test] - fn test_print_batches_with_sep() { - let batches = vec![]; - assert_eq!("", print_batches_with_sep(&batches, b',').unwrap()); - - let schema = Arc::new(Schema::new(vec![ - Field::new("a", DataType::Int32, false), - Field::new("b", DataType::Int32, false), - Field::new("c", DataType::Int32, false), - ])); - - let batch = RecordBatch::try_new( - schema, - vec![ - Arc::new(Int32Array::from_slice([1, 2, 3])), - Arc::new(Int32Array::from_slice([4, 5, 6])), - Arc::new(Int32Array::from_slice([7, 8, 9])), - ], - ) - .unwrap(); - - let batches = vec![batch]; - let r = print_batches_with_sep(&batches, b',').unwrap(); - assert_eq!("a,b,c\n1,4,7\n2,5,8\n3,6,9\n", r); - } - - #[test] - fn test_print_batches_to_json_empty() -> Result<()> { - let batches = vec![]; - let r = batches_to_json!(ArrayWriter, &batches); - assert_eq!("", r); - - let r = batches_to_json!(LineDelimitedWriter, &batches); - assert_eq!("", r); - - let schema = Arc::new(Schema::new(vec![ - Field::new("a", DataType::Int32, false), - Field::new("b", DataType::Int32, false), - Field::new("c", DataType::Int32, false), - ])); - - let batch = RecordBatch::try_new( - schema, - vec![ - Arc::new(Int32Array::from_slice([1, 2, 3])), - Arc::new(Int32Array::from_slice([4, 5, 6])), - Arc::new(Int32Array::from_slice([7, 8, 9])), - ], - ) - .unwrap(); - - let batches = vec![batch]; - let r = batches_to_json!(ArrayWriter, &batches); - assert_eq!( - "[{\"a\":1,\"b\":4,\"c\":7},{\"a\":2,\"b\":5,\"c\":8},{\"a\":3,\"b\":6,\"c\":9}]", - r - ); - - let r = batches_to_json!(LineDelimitedWriter, &batches); - assert_eq!( - "{\"a\":1,\"b\":4,\"c\":7}\n{\"a\":2,\"b\":5,\"c\":8}\n{\"a\":3,\"b\":6,\"c\":9}\n", - r - ); - Ok(()) - } -} diff --git a/src/events/key.rs b/src/events/key.rs deleted file mode 100644 index c72af17..0000000 --- a/src/events/key.rs +++ /dev/null @@ -1,245 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -// -// MIT License - -// Copyright (c) 2021 Alexander Keliris - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// Link: https://github.com/Rigellute/spotify-tui/blob/master/src/event/key.rs - -use crossterm::event; -use std::fmt; - -/// Represents an key. -#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] -pub enum Key { - /// Both Enter (or Return) and numpad Enter - Enter, - /// Tabulation key - Tab, - /// Backspace key - Backspace, - /// Escape key - Esc, - - /// Left arrow - Left, - /// Right arrow - Right, - /// Up arrow - Up, - /// Down arrow - Down, - - /// Insert key - Ins, - /// Delete key - Delete, - /// Home key - Home, - /// End key - End, - /// Page Up key - PageUp, - /// Page Down key - PageDown, - - /// F0 key - F0, - /// F1 key - F1, - /// F2 key - F2, - /// F3 key - F3, - /// F4 key - F4, - /// F5 key - F5, - /// F6 key - F6, - /// F7 key - F7, - /// F8 key - F8, - /// F9 key - F9, - /// F10 key - F10, - /// F11 key - F11, - /// F12 key - F12, - Char(char), - Ctrl(char), - Alt(char), - Unknown, -} - -impl Key { - /// Returns the function key corresponding to the given number - /// - /// 1 -> F1, etc... - /// - /// # Panics - /// - /// If `n == 0 || n > 12` - pub fn from_f(n: u8) -> Key { - match n { - 0 => Key::F0, - 1 => Key::F1, - 2 => Key::F2, - 3 => Key::F3, - 4 => Key::F4, - 5 => Key::F5, - 6 => Key::F6, - 7 => Key::F7, - 8 => Key::F8, - 9 => Key::F9, - 10 => Key::F10, - 11 => Key::F11, - 12 => Key::F12, - _ => panic!("unknown function key: F{}", n), - } - } -} - -impl fmt::Display for Key { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Key::Alt(' ') => write!(f, ""), - Key::Ctrl(' ') => write!(f, ""), - Key::Char(' ') => write!(f, ""), - Key::Alt(c) => write!(f, "", c), - Key::Ctrl(c) => write!(f, "", c), - Key::Char(c) => write!(f, "{}", c), - Key::Left | Key::Right | Key::Up | Key::Down => write!(f, "<{:?} Arrow Key>", self), - Key::Enter - | Key::Tab - | Key::Backspace - | Key::Esc - | Key::Ins - | Key::Delete - | Key::Home - | Key::End - | Key::PageUp - | Key::PageDown => write!(f, "<{:?}>", self), - _ => write!(f, "{:?}", self), - } - } -} - -impl From for Key { - fn from(key_event: event::KeyEvent) -> Self { - match key_event { - event::KeyEvent { - code: event::KeyCode::Esc, - .. - } => Key::Esc, - event::KeyEvent { - code: event::KeyCode::Backspace, - .. - } => Key::Backspace, - event::KeyEvent { - code: event::KeyCode::Left, - .. - } => Key::Left, - event::KeyEvent { - code: event::KeyCode::Right, - .. - } => Key::Right, - event::KeyEvent { - code: event::KeyCode::Up, - .. - } => Key::Up, - event::KeyEvent { - code: event::KeyCode::Down, - .. - } => Key::Down, - event::KeyEvent { - code: event::KeyCode::Home, - .. - } => Key::Home, - event::KeyEvent { - code: event::KeyCode::End, - .. - } => Key::End, - event::KeyEvent { - code: event::KeyCode::PageUp, - .. - } => Key::PageUp, - event::KeyEvent { - code: event::KeyCode::PageDown, - .. - } => Key::PageDown, - event::KeyEvent { - code: event::KeyCode::Delete, - .. - } => Key::Delete, - event::KeyEvent { - code: event::KeyCode::Insert, - .. - } => Key::Ins, - event::KeyEvent { - code: event::KeyCode::F(n), - .. - } => Key::from_f(n), - event::KeyEvent { - code: event::KeyCode::Tab, - .. - } => Key::Tab, - - // First check for char + modifier - event::KeyEvent { - code: event::KeyCode::Char(c), - modifiers: event::KeyModifiers::ALT, - } => Key::Alt(c), - event::KeyEvent { - code: event::KeyCode::Char(c), - modifiers: event::KeyModifiers::CONTROL, - } => Key::Ctrl(c), - - event::KeyEvent { - code: event::KeyCode::Char(c), - .. - } => Key::Char(c), - event::KeyEvent { - code: event::KeyCode::Enter, - .. - } => Key::Enter, - _ => Key::Unknown, - } - } -} diff --git a/src/events/mod.rs b/src/events/mod.rs deleted file mode 100644 index bdaed4c..0000000 --- a/src/events/mod.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -mod key; - -use log::debug; - -pub use self::key::Key; - -use ratatui::crossterm::event::{self, KeyEvent}; -use std::time::Duration; -use std::{sync::mpsc, thread}; - -pub enum InputEvent { - /// Key event occured. - Input(KeyEvent), - /// Tick event occured - Tick, -} - -pub enum Event { - KeyInput(KeyEvent), - Tick, -} - -pub struct Events { - rx: mpsc::Receiver, - // Need to be kept around to prevent disposing the sender side. - _tx: mpsc::Sender, -} - -impl Events { - pub fn new(tick_rate: Duration) -> Events { - let (tx, rx) = mpsc::channel(); - - let event_tx = tx.clone(); // the thread::spawn own event_tx - thread::spawn(move || { - loop { - // poll for tick rate duration, if no event, sent tick event. - if ratatui::crossterm::event::poll(tick_rate).unwrap() { - if let event::Event::Key(key) = event::read().unwrap() { - debug!("Key Event: {:?}", key); - // let key = Key::from(key); - event_tx.send(Event::KeyInput(key)); - // event_tx.send(Event::KeyInput(key)).unwrap(); - } - } - event_tx.send(Event::Tick).unwrap(); - } - }); - - Events { rx, _tx: tx } - } - - /// Attempts to read an event. - /// This function block the current thread. - pub fn next(&self) -> Result { - self.rx.recv() - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index be2d8d7..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,109 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -pub mod app; -pub mod cli; -pub mod events; -pub mod utils; - -use std::sync::{Arc, Mutex}; -use std::time::Duration; -use std::{borrow::Borrow, io}; - -use app::editor::Editor; -use app::{ - core::{App, AppReturn}, - error::DftError, -}; -use cli::args::Args; -use ratatui::crossterm::{ - event::{DisableMouseCapture, EnableMouseCapture}, - execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, -}; -use ratatui::{backend::CrosstermBackend, Terminal}; -// use tokio::sync::Mutex; - -use crate::app::error::Result; -use crate::app::ui; - -use crate::events::{Event, Events}; - -fn draw_app( - terminal: &mut Terminal>, - app: &App<'_>, -) -> Result<()> { - terminal.draw(|f| { - f.render_widget(app, f.area()); - })?; - Ok(()) -} - -pub async fn run_app(args: Args, ed: Editor<'_>) -> Result<()> { - // let ed = Editor::default(); - let mut app = App::new(args, ed); - enable_raw_mode().unwrap(); - let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend).unwrap(); - - let tick_rate = Duration::from_millis(200); - let events = Events::new(tick_rate); - - loop { - let event = events.next().unwrap(); - - terminal.draw(|f| { - let area = f.area(); - f.render_widget(&app, area); - })?; - - // Ensure the immutable borrow is dropped before we proceed. - // Then handle the event (mutable borrow) - let result = { app.event_handler(event).await }; - - if result? == AppReturn::Exit { - break; - } - } - - // loop { - // let event = events.next().unwrap(); - // if let Event::Tick = event { - // terminal.draw(|f| f.render_widget(&app, f.area()))?; - // } - // // draw_app(&mut terminal, &app)?; - // // terminal.draw(|f| ui::draw_ui(f, &app))?; - // // terminal.draw(|f| ui::draw_ui(f, app.borrow()))?; - // - // // Handle events - // let result = app.event_handler(event).await?; - // if result == AppReturn::Exit { - // break; - // } - // } - - disable_raw_mode()?; - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - )?; - terminal.show_cursor()?; - Ok(()) -} diff --git a/src/main.rs b/src/main.rs index 48209c6..0590e66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,49 +1,18 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -// Tabs: Context, Catalog, Logs, Sql Editors, Query History, Help for commands / functions -use std::error::Error; -use std::sync::Arc; +mod app; +mod cli; +mod telemetry; +mod ui; +use crate::app::state; +use app::run_app; use clap::Parser; -use datafusion_tui::app::core::App; -use datafusion_tui::app::editor::Editor; -use datafusion_tui::cli::args::Args; -use datafusion_tui::run_app; -use log::LevelFilter; -use mimalloc::MiMalloc; -use tokio::sync::Mutex; - -#[global_allocator] -static GLOBAL: MiMalloc = MiMalloc; +use color_eyre::Result; #[tokio::main] -async fn main() -> Result<(), Box> { - tui_logger::init_logger(LevelFilter::Debug).unwrap(); - tui_logger::set_default_level(LevelFilter::Debug); - let args = Args::parse(); - // let app = Arc::new(Mutex::new(App::new(args).await)); - // let res = run_app(app).await; - let ed = Editor::default(); - run_app(args, ed).await; - - if let Err(err) = res { - println!("{:?}", err) - } - +async fn main() -> Result<()> { + telemetry::initialize_tracing()?; + let cli = cli::DftCli::parse(); + let state = state::initialize(&cli); + run_app(cli.clone(), state).await?; Ok(()) } diff --git a/src/telemetry/mod.rs b/src/telemetry/mod.rs new file mode 100644 index 0000000..266bc90 --- /dev/null +++ b/src/telemetry/mod.rs @@ -0,0 +1,46 @@ +use crate::app::config::{get_data_dir, LOG_ENV, LOG_FILE}; +use color_eyre::Result; +use std::collections::HashMap; +use std::fs::OpenOptions; +use tracing::debug; +use tracing_error::ErrorLayer; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{fmt, Layer}; + +pub fn initialize_tracing() -> Result<()> { + let directory = get_data_dir(); + debug!("Fig data directory: {:?}", directory); + std::fs::create_dir_all(directory.clone())?; + let log_path = directory.join(LOG_FILE.clone()); + debug!("Fig log path: {:?}", log_path); + let log_file = OpenOptions::new() + .create(true) + .append(true) + .open(log_path)?; + std::env::set_var( + "RUST_LOG", + std::env::var("RUST_LOG") + .or_else(|_| std::env::var(LOG_ENV.clone())) + .unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))), + ); + + let log_sink = std::env::var("LOG_SINK").unwrap_or("file".to_string()); + if log_sink == "stdout" { + fmt::init(); + } else { + let file_subscriber = fmt::layer() + .with_file(true) + .with_line_number(true) + .with_writer(log_file) + .with_target(false) + .with_ansi(false) + .with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); + tracing_subscriber::registry() + .with(file_subscriber) + .with(ErrorLayer::default()) + .init(); + } + + Ok(()) +} diff --git a/src/ui/convert.rs b/src/ui/convert.rs new file mode 100644 index 0000000..dd47c6c --- /dev/null +++ b/src/ui/convert.rs @@ -0,0 +1,230 @@ +use datafusion::arrow::{ + array::{ + Int16Array, Int32Array, Int64Array, Int8Array, RecordBatch, StringArray, UInt16Array, + UInt32Array, UInt64Array, UInt8Array, + }, + datatypes::DataType, +}; +use ratatui::{ + layout::Constraint, + style::{palette::tailwind, Stylize}, + widgets::{Block, Borders, Cell, Row, Table}, +}; + +macro_rules! convert_array_values_to_cells { + ($rows:expr, $arr:expr, $typ:ty) => { + if let Some(a) = $arr.as_any().downcast_ref::<$typ>() { + for i in 0..$rows.len() { + $rows[i].push(a.value(i).to_string().into()); + } + } + }; +} + +pub fn record_batch_to_table_header_cells(record_batch: &RecordBatch) -> Vec { + let fields = record_batch.schema().fields().clone(); + fields + .into_iter() + .map(|f| { + Cell::new(f.name().to_string()) + .bg(tailwind::LIME.c300) + .fg(tailwind::BLACK) + }) + .collect() +} + +pub fn record_batch_to_table_row_cells(record_batch: &RecordBatch) -> Vec> { + let row_count = record_batch.num_rows(); + let column_count = record_batch.num_columns(); + + let mut rows: Vec> = Vec::with_capacity(row_count); + for _ in 0..row_count { + rows.push(Vec::with_capacity(column_count)) + } + + for arr in record_batch.columns() { + match arr.data_type() { + DataType::Utf8 => convert_array_values_to_cells!(rows, arr, StringArray), + DataType::Int8 => convert_array_values_to_cells!(rows, arr, Int8Array), + DataType::Int16 => convert_array_values_to_cells!(rows, arr, Int16Array), + DataType::Int32 => convert_array_values_to_cells!(rows, arr, Int32Array), + DataType::Int64 => convert_array_values_to_cells!(rows, arr, Int64Array), + DataType::UInt8 => convert_array_values_to_cells!(rows, arr, UInt8Array), + DataType::UInt16 => convert_array_values_to_cells!(rows, arr, UInt16Array), + DataType::UInt32 => convert_array_values_to_cells!(rows, arr, UInt32Array), + DataType::UInt64 => convert_array_values_to_cells!(rows, arr, UInt64Array), + _ => {} + } + } + rows +} + +pub fn empty_results_table<'frame>() -> Table<'frame> { + let header_row = Row::new(vec!["Result"]); + let value_row = [Row::new(vec!["No results"])]; + let width = vec![Constraint::Percentage(100)]; + Table::new(value_row, width).header(header_row) +} + +pub fn record_batches_to_table<'frame, 'results>( + record_batches: &'results [RecordBatch], +) -> Table<'frame> +where + // The results come from explore_tab state which persists until the next query is run which is + // longer than a frame lifetime. + 'results: 'frame, +{ + if record_batches.len() == 0 { + empty_results_table() + } else { + let first_batch = &record_batches[0]; + let header_cells = record_batch_to_table_header_cells(first_batch); + let header_row = Row::from_iter(header_cells).bold(); + let rows: Vec = record_batches + .iter() + .flat_map(|b| { + let batch_row_cells = record_batch_to_table_row_cells(b); + let rows: Vec = batch_row_cells + .into_iter() + .map(|row_cells| Row::from_iter(row_cells)) + .collect(); + rows + }) + .collect(); + let column_count = first_batch.num_columns(); + let widths = (0..column_count).into_iter().map(|_| Constraint::Fill(1)); + let block = Block::default().borders(Borders::all()); + Table::new(rows, widths).header(header_row).block(block) + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use datafusion::arrow::array::{ + ArrayRef, Int16Array, Int32Array, Int64Array, Int8Array, RecordBatch, StringArray, + UInt16Array, UInt32Array, UInt64Array, UInt8Array, + }; + use ratatui::widgets::Cell; + + use super::{record_batch_to_table_header_cells, record_batch_to_table_row_cells}; + + #[test] + fn record_batch_to_header_test() { + let a: ArrayRef = Arc::new(Int8Array::from(vec![1, 2, 3])); + let b: ArrayRef = Arc::new(Int8Array::from(vec![1, 2, 3])); + + let batch = RecordBatch::try_from_iter(vec![("a", a), ("b", b)]).unwrap(); + let header_cells = record_batch_to_table_header_cells(&batch); + assert_eq!(header_cells, vec![Cell::new("a"), Cell::new("b")]); + } + + #[test] + fn single_column_record_batch_to_rows_test() { + let a: ArrayRef = Arc::new(StringArray::from(vec!["a", "b", "c"])); + + let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); + let table_cells = record_batch_to_table_row_cells(&batch); + let expected = vec![ + vec![Cell::new("a")], + vec![Cell::new("b")], + vec![Cell::new("c")], + ]; + assert_eq!(table_cells, expected); + + let a: ArrayRef = Arc::new(Int8Array::from(vec![1, 2, 3])); + let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); + let a_table_cells = record_batch_to_table_row_cells(&batch); + let expected = vec![ + vec![Cell::new("1")], + vec![Cell::new("2")], + vec![Cell::new("3")], + ]; + assert_eq!(a_table_cells, expected); + + let a: ArrayRef = Arc::new(Int16Array::from(vec![1, 2, 3])); + let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); + let a_table_cells = record_batch_to_table_row_cells(&batch); + let expected = vec![ + vec![Cell::new("1")], + vec![Cell::new("2")], + vec![Cell::new("3")], + ]; + assert_eq!(a_table_cells, expected); + + let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3])); + let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); + let a_table_cells = record_batch_to_table_row_cells(&batch); + let expected = vec![ + vec![Cell::new("1")], + vec![Cell::new("2")], + vec![Cell::new("3")], + ]; + assert_eq!(a_table_cells, expected); + + let a: ArrayRef = Arc::new(Int64Array::from(vec![1, 2, 3])); + let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); + let a_table_cells = record_batch_to_table_row_cells(&batch); + let expected = vec![ + vec![Cell::new("1")], + vec![Cell::new("2")], + vec![Cell::new("3")], + ]; + assert_eq!(a_table_cells, expected); + + let a: ArrayRef = Arc::new(UInt8Array::from(vec![1, 2, 3])); + let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); + let a_table_cells = record_batch_to_table_row_cells(&batch); + let expected = vec![ + vec![Cell::new("1")], + vec![Cell::new("2")], + vec![Cell::new("3")], + ]; + assert_eq!(a_table_cells, expected); + + let a: ArrayRef = Arc::new(UInt16Array::from(vec![1, 2, 3])); + let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); + let a_table_cells = record_batch_to_table_row_cells(&batch); + let expected = vec![ + vec![Cell::new("1")], + vec![Cell::new("2")], + vec![Cell::new("3")], + ]; + assert_eq!(a_table_cells, expected); + + let a: ArrayRef = Arc::new(UInt32Array::from(vec![1, 2, 3])); + let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); + let a_table_cells = record_batch_to_table_row_cells(&batch); + let expected = vec![ + vec![Cell::new("1")], + vec![Cell::new("2")], + vec![Cell::new("3")], + ]; + assert_eq!(a_table_cells, expected); + + let a: ArrayRef = Arc::new(UInt64Array::from(vec![1, 2, 3])); + let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); + let a_table_cells = record_batch_to_table_row_cells(&batch); + let expected = vec![ + vec![Cell::new("1")], + vec![Cell::new("2")], + vec![Cell::new("3")], + ]; + assert_eq!(a_table_cells, expected); + } + + #[test] + fn multi_column_record_batch_to_rows_test() { + let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3])); + let b: ArrayRef = Arc::new(StringArray::from(vec!["a", "b", "c"])); + let batch = RecordBatch::try_from_iter(vec![("a", a), ("b", b)]).unwrap(); + let a_table_cells = record_batch_to_table_row_cells(&batch); + let expected = vec![ + vec![Cell::new("1"), Cell::new("a")], + vec![Cell::new("2"), Cell::new("b")], + vec![Cell::new("3"), Cell::new("c")], + ]; + assert_eq!(a_table_cells, expected); + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..3cac2db --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,80 @@ +pub mod convert; +pub mod tabs; + +use ratatui::{prelude::*, style::palette::tailwind, widgets::*}; +use strum::{Display, EnumIter, FromRepr}; + +use crate::app::App; + +use self::tabs::explore; + +#[derive(Clone, Copy, Debug, Display, FromRepr, EnumIter)] +pub enum SelectedTab { + #[strum(to_string = "Explore")] + Explore, +} + +impl SelectedTab { + pub fn title(self) -> Line<'static> { + let padding = Span::from(" "); + match self { + SelectedTab::Explore => { + let bold_char = Span::from("E").bold(); + let remaining = Span::from("xplore"); + Line::from_iter(vec![padding.clone(), bold_char, remaining, padding.clone()]) + .fg(tailwind::SLATE.c200) + .bg(self.bg()) + } + } + } + + const fn bg(self) -> Color { + match self { + Self::Explore => tailwind::EMERALD.c700, + } + } + + const fn palette(self) -> tailwind::Palette { + match self { + Self::Explore => tailwind::STONE, + } + } + + /// Get the previous tab, if there is no previous tab return the current tab. + pub fn previous(self) -> Self { + let current_index: usize = self as usize; + let previous_index = current_index.saturating_sub(1); + Self::from_repr(previous_index).unwrap_or(self) + } + + /// Get the next tab, if there is no next tab return the current tab. + pub fn next(self) -> Self { + let current_index = self as usize; + let next_index = current_index.saturating_add(1); + Self::from_repr(next_index).unwrap_or(self) + } + + /// A block surrounding the tab's content + fn block(self) -> Block<'static> { + Block::default() + .borders(Borders::ALL) + .border_set(symbols::border::PROPORTIONAL_TALL) + .padding(Padding::horizontal(1)) + .border_style(self.palette().c700) + } + + fn render_explore(self, area: Rect, buf: &mut Buffer, app: &App) { + explore::render_explore(area, buf, app) + } + + /// Render the tab with the provided state. + /// + /// This used to be an impl of `Widget` but we weren't able to pass state + /// as a paramter to the render method so moved to impl on the SelectedTab. + /// It's not clear if this will have future impact. + pub fn render(self, area: Rect, buf: &mut Buffer, app: &App) { + match self { + Self::Explore => self.render_explore(area, buf, app), + } + } +} diff --git a/src/ui/tabs/explore.rs b/src/ui/tabs/explore.rs new file mode 100644 index 0000000..98e5c19 --- /dev/null +++ b/src/ui/tabs/explore.rs @@ -0,0 +1,45 @@ +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Direction, Layout, Rect}, + style::{palette::tailwind, Stylize}, + widgets::{Block, Borders, Row, Table, Widget}, +}; + +use crate::{app::App, ui::convert::record_batches_to_table}; + +pub fn render_sql_editor(area: Rect, buf: &mut Buffer, app: &App) { + let border_color = if app.state.explore_tab.is_editable() { + tailwind::GREEN.c300 + } else { + tailwind::WHITE + }; + let block = Block::default() + .title(" Editor ") + .borders(Borders::ALL) + .fg(border_color) + .title_bottom(" Cmd+Enter to run query "); + let mut editor = app.state.explore_tab.editor(); + editor.set_block(block); + editor.render(area, buf) +} + +pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) { + let block = Block::default().title(" Results ").borders(Borders::ALL); + if let Some(r) = app.state.explore_tab.query_results() { + record_batches_to_table(r).render(area, buf); + } else { + let row = Row::new(vec!["Run a query to generate results"]); + let widths = vec![Constraint::Percentage(100)]; + let table = Table::new(vec![row], widths).block(block); + table.render(area, buf); + } +} + +pub fn render_explore(area: Rect, buf: &mut Buffer, app: &App) { + let constraints = vec![Constraint::Percentage(50), Constraint::Percentage(50)]; + let layout = Layout::new(Direction::Vertical, constraints).split(area); + let editor = layout[0]; + let results = layout[1]; + render_sql_editor(editor, buf, app); + render_sql_results(results, buf, app); +} diff --git a/src/ui/tabs/mod.rs b/src/ui/tabs/mod.rs new file mode 100644 index 0000000..5fb7aaa --- /dev/null +++ b/src/ui/tabs/mod.rs @@ -0,0 +1 @@ +pub mod explore; diff --git a/src/utils/mod.rs b/src/utils/mod.rs deleted file mode 100644 index 1528db7..0000000 --- a/src/utils/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod test_util; diff --git a/src/utils/test_util.rs b/src/utils/test_util.rs deleted file mode 100644 index 5785666..0000000 --- a/src/utils/test_util.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::app::datafusion::context::QueryResults; - -/// Compares the `QueryResults` of an executed query with -/// the expected `QueryResults`. Given that `query_duration` -/// is a field in `QueryResultsMeta` that can vary between -/// executions this field is excluded when testing equality. -/// This is a macro so errors appear on the correct line. -pub fn assert_results_eq(actual: Option, expected: Option) { - if let (Some(act), Some(exp)) = (&actual, &expected) { - let a = StaticQueryFields::from(act); - let e = StaticQueryFields::from(exp); - assert_eq!(a, e) - } else if actual.is_none() && expected.is_none() { - // Nones match means test passed - } else { - panic!("QueryResults do not match") - } -} - -#[derive(Debug, PartialEq)] -struct StaticQueryFields { - query: String, - succeeded: bool, - error: Option, - rows: usize, -} - -impl From for StaticQueryFields { - fn from(query_results: QueryResults) -> Self { - StaticQueryFields { - query: query_results.meta.query, - succeeded: query_results.meta.succeeded, - error: query_results.meta.error, - rows: query_results.meta.rows, - } - } -} - -impl From<&QueryResults> for StaticQueryFields { - fn from(query_results: &QueryResults) -> Self { - StaticQueryFields { - query: query_results.meta.query.clone(), - succeeded: query_results.meta.succeeded, - error: query_results.meta.error.clone(), - rows: query_results.meta.rows, - } - } -} From 14e7df5ae8c8d96c5b6d3fded5c0c077ceb61a1f Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sat, 24 Aug 2024 15:32:28 -0400 Subject: [PATCH 03/17] Logs working --- Cargo.lock | 75 ++++++++++++++++++++++++----------- Cargo.toml | 9 +++-- src/app/handlers/mod.rs | 56 ++++++++++++++++++++++---- src/app/mod.rs | 64 +++++++----------------------- src/app/state/mod.rs | 2 +- src/app/state/tabs/explore.rs | 15 ++++++- src/main.rs | 2 +- src/telemetry/mod.rs | 71 ++++++++++++++++++--------------- src/ui/mod.rs | 30 ++++++++++---- src/ui/tabs/logs.rs | 53 +++++++++++++++++++++++++ src/ui/tabs/mod.rs | 1 + 11 files changed, 251 insertions(+), 127 deletions(-) create mode 100644 src/ui/tabs/logs.rs diff --git a/Cargo.lock b/Cargo.lock index 2706928..9d61f87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -680,13 +680,14 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" dependencies = [ "castaway", "cfg-if", "itoa", + "rustversion", "ryu", "static_assertions", ] @@ -743,16 +744,16 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.27.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", "futures-core", - "libc", "mio", "parking_lot", + "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -1136,6 +1137,7 @@ dependencies = [ "futures-util", "itertools 0.13.0", "lazy_static", + "log", "open", "ratatui", "serde", @@ -1147,6 +1149,7 @@ dependencies = [ "tracing", "tracing-error", "tracing-subscriber", + "tui-logger", "tui-textarea", ] @@ -1350,6 +1353,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1483,6 +1495,16 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instability" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +dependencies = [ + "quote", + "syn 2.0.76", +] + [[package]] name = "instant" version = "0.1.13" @@ -1756,14 +1778,15 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", "log", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2145,18 +2168,18 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" +checksum = "5ba6a365afbe5615999275bea2446b970b10a41102500e27ce7678d50d978303" dependencies = [ "bitflags 2.6.0", "cassowary", "compact_str", "crossterm", + "instability", "itertools 0.13.0", "lru", "paste", - "stability", "strum", "strum_macros", "unicode-segmentation", @@ -2462,16 +2485,6 @@ dependencies = [ "syn 2.0.76", ] -[[package]] -name = "stability" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" -dependencies = [ - "quote", - "syn 2.0.76", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -2765,11 +2778,27 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tui-logger" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f163885b0550ad9821f52700b6da2e0e249641722be4e2b615ee6c5010ec3e" +dependencies = [ + "chrono", + "fxhash", + "lazy_static", + "log", + "parking_lot", + "ratatui", + "tracing", + "tracing-subscriber", +] + [[package]] name = "tui-textarea" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00524c1366ee838839dd327d1f339ff51846ad4ea85bfa1332859e79adec612c" +checksum = "29c07084342a575cea919eea996b9658a358c800b03d435df581c1d7c60e065a" dependencies = [ "crossterm", "ratatui", diff --git a/Cargo.toml b/Cargo.toml index 75f6be5..09e84cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" async-trait = "0.1.80" clap = { version = "4.5.1", features = ["derive"] } color-eyre = "0.6.3" -crossterm = { version = "0.27.0", features = ["event-stream"] } +crossterm = { version = "0.28.1", features = ["event-stream"] } # datafusion = { path = "/Users/matth/OpenSource/arrow-datafusion/datafusion/core"} datafusion = "40.0.0" directories = "5.0.1" @@ -17,8 +17,9 @@ futures = "0.3.30" futures-util = "0.3.30" itertools = "0.13.0" lazy_static = "1.4.0" +log = "0.4.22" open = "5.1.4" -ratatui = "0.27.0" +ratatui = "0.28.0" serde = { version = "1.0.197", features = ["derive"] } strum = "0.26.2" tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } @@ -28,8 +29,8 @@ toml = "0.8.12" tracing = { version = "0.1.40", features = ["log"] } tracing-error = "0.2.0" tracing-subscriber = {version = "0.3.18", features = ["env-filter"] } -tui-logger = "0.12" -tui-textarea = "0.5.3" +tui-logger = {version = "0.12", features = ["tracing-support"]} +tui-textarea = "0.6.1" [lints.clippy] clone_on_ref_ptr = "deny" diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index 41076bd..da668c3 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -1,6 +1,6 @@ use color_eyre::{eyre::eyre, Result}; -use crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers}; use datafusion::physical_plan::execute_stream; +use ratatui::crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers}; use tracing::{debug, error, info}; use crate::{app::AppEvent, ui::SelectedTab}; @@ -23,7 +23,8 @@ pub fn crossterm_event_handler(event: event::Event) -> Option { fn tab_navigation_handler(app: &mut App, key: KeyCode) { match key { - KeyCode::Char('e') => app.state.tabs.selected = SelectedTab::Explore, + KeyCode::Char('e') => app.state.tabs.selected = SelectedTab::Queries, + KeyCode::Char('l') => app.state.tabs.selected = SelectedTab::Logs, _ => {} }; } @@ -31,10 +32,21 @@ fn tab_navigation_handler(app: &mut App, key: KeyCode) { fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { match key.code { KeyCode::Char('q') => app.state.should_quit = true, - KeyCode::Char('i') => app.state.explore_tab.edit(), - tab @ (KeyCode::Char('p') | KeyCode::Char('t') | KeyCode::Char('e')) => { - tab_navigation_handler(app, tab) + KeyCode::Char('i') => { + let editor = app.state.explore_tab.editor(); + let lines = editor.lines(); + let content = lines.join(""); + info!("Conent: {}", content); + let default = "Enter a query here."; + if content == default { + info!("Clearing default content"); + app.state.explore_tab.clear_placeholder(); + // editor.move_cursor(tui_textarea::CursorMove::Jump(0, 0)); + // editor.delete_str(default.len()); + } + app.state.explore_tab.edit(); } + tab @ (KeyCode::Char('e') | KeyCode::Char('l')) => tab_navigation_handler(app, tab), KeyCode::Enter => { info!("Run query"); let query = app.state.explore_tab.editor().lines().join(""); @@ -97,13 +109,41 @@ fn explore_tab_app_event_handler(app: &mut App, event: AppEvent) { }; } +fn logs_tab_app_event_handler(app: &mut App, event: AppEvent) { + match event { + AppEvent::Key(key) => match app.state.explore_tab.is_editable() { + true => explore_tab_editable_handler(app, key), + false => explore_tab_normal_mode_handler(app, key), + }, + AppEvent::ExploreQueryResult(r) => app.state.explore_tab.set_query_results(r), + AppEvent::Tick => {} + AppEvent::Error => {} + _ => {} + }; +} + pub fn app_event_handler(app: &mut App, event: AppEvent) -> Result<()> { let now = std::time::Instant::now(); //TODO: Create event to action debug!("Tui::Event: {:?}", event); - match app.state.tabs.selected { - SelectedTab::Explore => explore_tab_app_event_handler(app, event), - }; + if let AppEvent::ExecuteDDL(ddl) = event { + let queries: Vec = ddl.split(";").map(|s| s.to_string()).collect(); + queries.into_iter().for_each(|q| { + let ctx = app.execution.session_ctx.clone(); + tokio::spawn(async move { + if let Ok(df) = ctx.sql(&q).await { + if let Ok(_) = df.collect().await { + info!("Successful DDL"); + } + } + }); + }) + } else { + match app.state.tabs.selected { + SelectedTab::Queries => explore_tab_app_event_handler(app, event), + SelectedTab::Logs => logs_tab_app_event_handler(app, event), + }; + } debug!("Event handling took: {:?}", now.elapsed()); Ok(()) } diff --git a/src/app/mod.rs b/src/app/mod.rs index f405766..ff412e3 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -7,19 +7,19 @@ use crate::cli::DftCli; use crate::{cli, ui}; use color_eyre::eyre::eyre; use color_eyre::Result; -use crossterm::{ - cursor, event, - terminal::{EnterAlternateScreen, LeaveAlternateScreen}, -}; +use crossterm::event as ct; use datafusion::arrow::array::RecordBatch; use futures::FutureExt; use futures_util::StreamExt; use ratatui::backend::CrosstermBackend; +use ratatui::crossterm::{ + self, cursor, event, + terminal::{EnterAlternateScreen, LeaveAlternateScreen}, +}; use ratatui::{prelude::*, style::palette::tailwind, widgets::*}; use strum::IntoEnumIterator; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; -use tokio::{sync::broadcast, task::JoinHandle}; -use tokio_stream::StreamMap; +use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; use tracing::{debug, error, info}; @@ -40,6 +40,7 @@ pub enum AppEvent { Paste(String), Mouse(event::MouseEvent), Resize(u16, u16), + ExecuteDDL(String), ExploreQueryResult(Vec), } @@ -80,13 +81,13 @@ impl<'app> App<'app> { /// mouse capture, then start event loop. pub fn enter(&mut self, ui: bool) -> Result<()> { if ui { - crossterm::terminal::enable_raw_mode()?; - crossterm::execute!(std::io::stdout(), EnterAlternateScreen, cursor::Hide)?; + ratatui::crossterm::terminal::enable_raw_mode()?; + ratatui::crossterm::execute!(std::io::stdout(), EnterAlternateScreen, cursor::Hide)?; if self.state.config.interaction.mouse { - crossterm::execute!(std::io::stdout(), event::EnableMouseCapture)?; + ratatui::crossterm::execute!(std::io::stdout(), event::EnableMouseCapture)?; } if self.state.config.interaction.paste { - crossterm::execute!(std::io::stdout(), event::EnableBracketedPaste)?; + ratatui::crossterm::execute!(std::io::stdout(), event::EnableBracketedPaste)?; } } self.start_event_loop(); @@ -156,7 +157,6 @@ impl<'app> App<'app> { let tick_delay = std::time::Duration::from_secs_f64(1.0 / self.state.config.display.tick_rate); // TODO-V1: Add this to config - let realtime_graph_refresh_delay = std::time::Duration::from_millis(250); debug!("Tick delay: {:?}", tick_delay); self.cancel(); self.app_cancellation_token = CancellationToken::new(); @@ -164,22 +164,15 @@ impl<'app> App<'app> { let _event_tx = self.app_event_tx.clone(); self.task = tokio::spawn(async move { - let mut reader = event::EventStream::new(); + let mut reader = ct::EventStream::new(); let mut tick_interval = tokio::time::interval(tick_delay); debug!("Tick interval: {:?}", tick_interval); let mut render_interval = tokio::time::interval(render_delay); debug!("Render interval: {:?}", render_interval); - let mut realtime_graph_refresh_interval = - tokio::time::interval(realtime_graph_refresh_delay); - debug!( - "Realtime graph refresh interval: {:?}", - realtime_graph_refresh_interval - ); _event_tx.send(AppEvent::Init).unwrap(); loop { let tick_delay = tick_interval.tick(); let render_delay = render_interval.tick(); - let realtime_graph_refresh_delay = realtime_graph_refresh_interval.tick(); let crossterm_event = reader.next().fuse(); tokio::select! { _ = _cancellation_token.cancelled() => { @@ -204,36 +197,9 @@ impl<'app> App<'app> { }); } - fn start_stream_event_loop(&mut self) { - self.cancel(); - self.app_cancellation_token = CancellationToken::new(); - let _cancellation_token = self.app_cancellation_token.clone(); - let _event_tx = self.app_event_tx.clone(); - - self.task = tokio::spawn(async move { - let mut reader = event::EventStream::new(); - _event_tx.send(AppEvent::Init).unwrap(); - loop { - let crossterm_event = reader.next().fuse(); - tokio::select! { - _ = _cancellation_token.cancelled() => { - break; - } - maybe_event = crossterm_event => { - let maybe_app_event = match maybe_event { - Some(Ok(event)) => { - Self::handle_crossterm_event(event) - } - Some(Err(_)) => Some(AppEvent::Error), - None => unimplemented!() - }; - if let Some(app_event) = maybe_app_event { - Self::send_app_event(app_event, &_event_tx); - }; - }, - } - } - }); + pub fn execute_ddl(&mut self) { + let ddl = std::fs::read_to_string("~/.datafusionrc").unwrap(); + let _ = self.app_event_tx.send(AppEvent::ExecuteDDL(ddl)); } /// Dispatch to the appropriate event loop based on the command diff --git a/src/app/state/mod.rs b/src/app/state/mod.rs index 83ee258..1cabe7d 100644 --- a/src/app/state/mod.rs +++ b/src/app/state/mod.rs @@ -19,7 +19,7 @@ pub struct Tabs { impl Default for Tabs { fn default() -> Self { Self { - selected: SelectedTab::Explore, + selected: SelectedTab::Queries, } } } diff --git a/src/app/state/tabs/explore.rs b/src/app/state/tabs/explore.rs index 3e49be1..c16f91f 100644 --- a/src/app/state/tabs/explore.rs +++ b/src/app/state/tabs/explore.rs @@ -1,5 +1,5 @@ -use crossterm::event::KeyEvent; use datafusion::arrow::array::RecordBatch; +use ratatui::crossterm::event::KeyEvent; use ratatui::style::{palette::tailwind, Style}; use tui_textarea::TextArea; @@ -7,7 +7,7 @@ use tui_textarea::TextArea; pub struct ExploreTabState<'app> { editor: TextArea<'app>, editor_editable: bool, - query_results: Option>, // query_handle: Option>>>, + query_results: Option>, } impl<'app> ExploreTabState<'app> { @@ -30,6 +30,17 @@ impl<'app> ExploreTabState<'app> { self.editor.clone() } + pub fn clear_placeholder(&mut self) { + let default = "Enter a query here."; + let lines = self.editor.lines(); + let content = lines.join(""); + if content == default { + self.editor + .move_cursor(tui_textarea::CursorMove::Jump(0, 0)); + self.editor.delete_str(default.len()); + } + } + pub fn update_editor_content(&mut self, key: KeyEvent) { self.editor.input(key); } diff --git a/src/main.rs b/src/main.rs index 0590e66..7d3c95b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use color_eyre::Result; #[tokio::main] async fn main() -> Result<()> { - telemetry::initialize_tracing()?; + telemetry::initialize_logs()?; let cli = cli::DftCli::parse(); let state = state::initialize(&cli); run_app(cli.clone(), state).await?; diff --git a/src/telemetry/mod.rs b/src/telemetry/mod.rs index 266bc90..e1b8347 100644 --- a/src/telemetry/mod.rs +++ b/src/telemetry/mod.rs @@ -1,5 +1,6 @@ use crate::app::config::{get_data_dir, LOG_ENV, LOG_FILE}; use color_eyre::Result; +use log::LevelFilter; use std::collections::HashMap; use std::fs::OpenOptions; use tracing::debug; @@ -8,39 +9,45 @@ use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{fmt, Layer}; -pub fn initialize_tracing() -> Result<()> { - let directory = get_data_dir(); - debug!("Fig data directory: {:?}", directory); - std::fs::create_dir_all(directory.clone())?; - let log_path = directory.join(LOG_FILE.clone()); - debug!("Fig log path: {:?}", log_path); - let log_file = OpenOptions::new() - .create(true) - .append(true) - .open(log_path)?; - std::env::set_var( - "RUST_LOG", - std::env::var("RUST_LOG") - .or_else(|_| std::env::var(LOG_ENV.clone())) - .unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))), - ); +pub fn initialize_logs() -> Result<()> { + // let directory = get_data_dir(); + // debug!("Fig data directory: {:?}", directory); + // std::fs::create_dir_all(directory.clone())?; + // let log_path = directory.join(LOG_FILE.clone()); + // debug!("Fig log path: {:?}", log_path); + // let log_file = OpenOptions::new() + // .create(true) + // .append(true) + // .open(log_path)?; + // std::env::set_var( + // "RUST_LOG", + // std::env::var("RUST_LOG") + // .or_else(|_| std::env::var(LOG_ENV.clone())) + // .unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))), + // ); - let log_sink = std::env::var("LOG_SINK").unwrap_or("file".to_string()); - if log_sink == "stdout" { - fmt::init(); - } else { - let file_subscriber = fmt::layer() - .with_file(true) - .with_line_number(true) - .with_writer(log_file) - .with_target(false) - .with_ansi(false) - .with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); - tracing_subscriber::registry() - .with(file_subscriber) - .with(ErrorLayer::default()) - .init(); - } + tui_logger::init_logger(LevelFilter::Debug).unwrap(); + tui_logger::set_default_level(LevelFilter::Debug); + + // let l = tui_logger::tracing_subscriber_layer(); + // let l = l.with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); + // let log_sink = std::env::var("LOG_SINK").unwrap_or("file".to_string()); + // if log_sink == "stdout" { + // fmt::init(); + // } else { + // let file_subscriber = fmt::layer() + // .with_file(true) + // .with_line_number(true) + // .with_writer(log_file) + // .with_target(false) + // .with_ansi(false) + // .with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); + // tracing_subscriber::registry() + // .with(file_subscriber) + // .with(ErrorLayer::default()) + // .with(l) + // .init(); + // } Ok(()) } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 3cac2db..69e0828 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -6,37 +6,48 @@ use strum::{Display, EnumIter, FromRepr}; use crate::app::App; -use self::tabs::explore; +use self::tabs::{explore, logs}; #[derive(Clone, Copy, Debug, Display, FromRepr, EnumIter)] pub enum SelectedTab { - #[strum(to_string = "Explore")] - Explore, + #[strum(to_string = "Queries")] + Queries, + #[strum(to_string = "Logs")] + Logs, } impl SelectedTab { pub fn title(self) -> Line<'static> { let padding = Span::from(" "); match self { - SelectedTab::Explore => { + SelectedTab::Queries => { let bold_char = Span::from("E").bold(); let remaining = Span::from("xplore"); Line::from_iter(vec![padding.clone(), bold_char, remaining, padding.clone()]) .fg(tailwind::SLATE.c200) .bg(self.bg()) } + Self::Logs => { + let bold_char = Span::from("L").bold(); + let remaining = Span::from("ogs"); + Line::from_iter(vec![padding.clone(), bold_char, remaining, padding.clone()]) + .fg(tailwind::SLATE.c200) + .bg(self.bg()) + } } } const fn bg(self) -> Color { match self { - Self::Explore => tailwind::EMERALD.c700, + Self::Queries => tailwind::EMERALD.c700, + Self::Logs => tailwind::EMERALD.c700, } } const fn palette(self) -> tailwind::Palette { match self { - Self::Explore => tailwind::STONE, + Self::Queries => tailwind::STONE, + Self::Logs => tailwind::STONE, } } @@ -67,6 +78,10 @@ impl SelectedTab { explore::render_explore(area, buf, app) } + fn render_logs(self, area: Rect, buf: &mut Buffer, app: &App) { + logs::render_logs(area, buf, app) + } + /// Render the tab with the provided state. /// /// This used to be an impl of `Widget` but we weren't able to pass state @@ -74,7 +89,8 @@ impl SelectedTab { /// It's not clear if this will have future impact. pub fn render(self, area: Rect, buf: &mut Buffer, app: &App) { match self { - Self::Explore => self.render_explore(area, buf, app), + Self::Queries => self.render_explore(area, buf, app), + Self::Logs => self.render_logs(area, buf, app), } } } diff --git a/src/ui/tabs/logs.rs b/src/ui/tabs/logs.rs new file mode 100644 index 0000000..e6a1240 --- /dev/null +++ b/src/ui/tabs/logs.rs @@ -0,0 +1,53 @@ +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Direction, Layout, Rect}, + style::{palette::tailwind, Style, Stylize}, + widgets::{Block, Borders, Row, Table, Widget}, +}; +use tui_logger::TuiLoggerSmartWidget; + +use crate::{app::App, ui::convert::record_batches_to_table}; + +// pub fn render_sql_editor(area: Rect, buf: &mut Buffer, app: &App) { +// let border_color = if app.state.explore_tab.is_editable() { +// tailwind::GREEN.c300 +// } else { +// tailwind::WHITE +// }; +// let block = Block::default() +// .title(" Editor ") +// .borders(Borders::ALL) +// .fg(border_color) +// .title_bottom(" Cmd+Enter to run query "); +// let mut editor = app.state.explore_tab.editor(); +// editor.set_block(block); +// editor.render(area, buf) +// } + +// pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) { +// let block = Block::default().title(" Results ").borders(Borders::ALL); +// if let Some(r) = app.state.explore_tab.query_results() { +// record_batches_to_table(r).render(area, buf); +// } else { +// let row = Row::new(vec!["Run a query to generate results"]); +// let widths = vec![Constraint::Percentage(100)]; +// let table = Table::new(vec![row], widths).block(block); +// table.render(area, buf); +// } +// } + +pub fn render_logs(area: Rect, buf: &mut Buffer, app: &App) { + // let constraints = vec![Constraint::Percentage(50), Constraint::Percentage(50)]; + // let layout = Layout::new(Direction::Vertical, constraints).split(area); + // let editor = layout[0]; + // let results = layout[1]; + // render_sql_editor(editor, buf, app); + // render_sql_results(results, buf, app); + let logs = TuiLoggerSmartWidget::default() + .style_error(Style::default().fg(tailwind::RED.c700)) + .style_debug(Style::default().fg(tailwind::GREEN.c700)) + .style_warn(Style::default().fg(tailwind::YELLOW.c700)) + .style_trace(Style::default().fg(tailwind::GRAY.c700)) + .style_info(Style::default().fg(tailwind::BLUE.c700)); + logs.render(area, buf); +} diff --git a/src/ui/tabs/mod.rs b/src/ui/tabs/mod.rs index 5fb7aaa..4a859d8 100644 --- a/src/ui/tabs/mod.rs +++ b/src/ui/tabs/mod.rs @@ -1 +1,2 @@ pub mod explore; +pub mod logs; From 6c77ad47cdc6d54c5ee9bef0d5f9f9a22e488465 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 05:54:12 -0400 Subject: [PATCH 04/17] More working --- Cargo.lock | 79 +++------------------------------ Cargo.toml | 5 --- src/app/config.rs | 6 +-- src/app/execution.rs | 6 --- src/app/handlers/mod.rs | 90 +++++++++++++++++++++++++++++++------- src/app/mod.rs | 9 ++-- src/app/state/mod.rs | 10 +++-- src/app/state/tabs/logs.rs | 22 ++++++++++ src/app/state/tabs/mod.rs | 1 + src/telemetry/mod.rs | 44 ------------------- src/ui/tabs/logs.rs | 68 ++++++++++++---------------- 11 files changed, 141 insertions(+), 199 deletions(-) create mode 100644 src/app/state/tabs/logs.rs diff --git a/Cargo.lock b/Cargo.lock index 9d61f87..5109a39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,7 +357,7 @@ dependencies = [ "memchr", "num", "regex", - "regex-syntax 0.8.4", + "regex-syntax", ] [[package]] @@ -1025,7 +1025,7 @@ dependencies = [ "itertools 0.12.1", "log", "paste", - "regex-syntax 0.8.4", + "regex-syntax", ] [[package]] @@ -1138,7 +1138,6 @@ dependencies = [ "itertools 0.13.0", "lazy_static", "log", - "open", "ratatui", "serde", "strum", @@ -1146,9 +1145,6 @@ dependencies = [ "tokio-stream", "tokio-util", "toml", - "tracing", - "tracing-error", - "tracing-subscriber", "tui-logger", "tui-textarea", ] @@ -1523,25 +1519,6 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1733,15 +1710,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "md-5" version = "0.10.6" @@ -1919,17 +1887,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "open" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" -dependencies = [ - "is-wsl", - "libc", - "pathdiff", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -2031,12 +1988,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "percent-encoding" version = "2.3.1" @@ -2215,17 +2166,8 @@ checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -2236,15 +2178,9 @@ checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.4" @@ -2712,7 +2648,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2766,14 +2701,10 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ - "matchers", "nu-ansi-term", - "once_cell", - "regex", "sharded-slab", "smallvec", "thread_local", - "tracing", "tracing-core", "tracing-log", ] diff --git a/Cargo.toml b/Cargo.toml index 09e84cc..56ebdbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ async-trait = "0.1.80" clap = { version = "4.5.1", features = ["derive"] } color-eyre = "0.6.3" crossterm = { version = "0.28.1", features = ["event-stream"] } -# datafusion = { path = "/Users/matth/OpenSource/arrow-datafusion/datafusion/core"} datafusion = "40.0.0" directories = "5.0.1" futures = "0.3.30" @@ -18,7 +17,6 @@ futures-util = "0.3.30" itertools = "0.13.0" lazy_static = "1.4.0" log = "0.4.22" -open = "5.1.4" ratatui = "0.28.0" serde = { version = "1.0.197", features = ["derive"] } strum = "0.26.2" @@ -26,9 +24,6 @@ tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } tokio-stream = "0.1.15" tokio-util = "0.7.10" toml = "0.8.12" -tracing = { version = "0.1.40", features = ["log"] } -tracing-error = "0.2.0" -tracing-subscriber = {version = "0.3.18", features = ["env-filter"] } tui-logger = {version = "0.12", features = ["tracing-support"]} tui-textarea = "0.6.1" diff --git a/src/app/config.rs b/src/app/config.rs index 3ae501d..b73419a 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::path::PathBuf; use directories::{ProjectDirs, UserDirs}; use lazy_static::lazy_static; @@ -115,7 +115,3 @@ fn default_mouse() -> bool { fn default_paste() -> bool { false } - -fn default_fetch_limit() -> usize { - 100_usize -} diff --git a/src/app/execution.rs b/src/app/execution.rs index 298507f..092418d 100644 --- a/src/app/execution.rs +++ b/src/app/execution.rs @@ -1,14 +1,8 @@ -use std::sync::Arc; - use color_eyre::eyre::Result; -use datafusion::arrow::datatypes::{DataType, Field, Schema, TimeUnit}; use datafusion::arrow::util::pretty::pretty_format_batches; -use datafusion::common::Constraints; -use datafusion::datasource::stream::{StreamConfig, StreamTable}; use datafusion::execution::TaskContext; use datafusion::physical_plan::execute_stream; use datafusion::prelude::*; -use tokio::sync::broadcast::{self, Sender}; use tokio_stream::StreamExt; use tokio_util::sync::CancellationToken; diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index da668c3..b8d2c72 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -1,7 +1,7 @@ use color_eyre::{eyre::eyre, Result}; -use datafusion::physical_plan::execute_stream; +use log::{debug, error, info, trace}; use ratatui::crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers}; -use tracing::{debug, error, info}; +use tui_logger::TuiWidgetEvent; use crate::{app::AppEvent, ui::SelectedTab}; @@ -41,8 +41,6 @@ fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { if content == default { info!("Clearing default content"); app.state.explore_tab.clear_placeholder(); - // editor.move_cursor(tui_textarea::CursorMove::Jump(0, 0)); - // editor.delete_str(default.len()); } app.state.explore_tab.edit(); } @@ -56,14 +54,30 @@ fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { // TODO: Maybe this should be on a separate runtime to prevent blocking main thread / // runtime tokio::spawn(async move { - if let Ok(df) = ctx.sql(&query).await { - if let Ok(res) = df.collect().await.map_err(|e| eyre!(e)) { - info!("Results: {:?}", res); - let _ = _event_tx.send(AppEvent::ExploreQueryResult(res)); + match ctx.sql(&query).await { + Ok(df) => match df.collect().await { + Ok(res) => { + info!("Results: {:?}", res); + let _ = _event_tx.send(AppEvent::ExploreQueryResult(res)); + } + Err(e) => { + error!("Error collecting results: {:?}", e); + let _ = _event_tx.send(AppEvent::ExploreQueryError(e.to_string())); + } + }, + Err(e) => { + error!("Error creating dataframe: {:?}", e); + let _ = _event_tx.send(AppEvent::ExploreQueryError(e.to_string())); } - } else { - error!("Error creating dataframe") } + // if let Ok(df) = ctx.sql(&query).await { + // if let Ok(res) = df.collect().await.map_err(|e| eyre!(e)) { + // info!("Results: {:?}", res); + // let _ = _event_tx.send(AppEvent::ExploreQueryResult(res)); + // } + // } else { + // error!("Error creating dataframe") + // } }); } _ => {} @@ -109,12 +123,56 @@ fn explore_tab_app_event_handler(app: &mut App, event: AppEvent) { }; } +fn logs_tab_key_event_handler(app: &mut App, key: KeyEvent) { + match key.code { + KeyCode::Char('h') => { + app.state.logs_tab.transition(TuiWidgetEvent::HideKey); + } + KeyCode::Char('f') => { + app.state.logs_tab.transition(TuiWidgetEvent::FocusKey); + } + KeyCode::Char('+') => { + app.state.logs_tab.transition(TuiWidgetEvent::PlusKey); + } + KeyCode::Char('-') => { + app.state.logs_tab.transition(TuiWidgetEvent::MinusKey); + } + KeyCode::Char('q') => app.state.should_quit = true, + KeyCode::Char(' ') => { + app.state.logs_tab.transition(TuiWidgetEvent::SpaceKey); + } + KeyCode::Esc => { + app.state.logs_tab.transition(TuiWidgetEvent::EscapeKey); + } + KeyCode::Down => { + app.state.logs_tab.transition(TuiWidgetEvent::DownKey); + } + KeyCode::Up => { + app.state.logs_tab.transition(TuiWidgetEvent::UpKey); + } + KeyCode::Right => { + app.state.logs_tab.transition(TuiWidgetEvent::RightKey); + } + KeyCode::Left => { + app.state.logs_tab.transition(TuiWidgetEvent::LeftKey); + } + KeyCode::PageDown => { + app.state.logs_tab.transition(TuiWidgetEvent::NextPageKey); + } + + KeyCode::PageUp => { + app.state.logs_tab.transition(TuiWidgetEvent::PrevPageKey); + } + KeyCode::Char('e') => { + app.state.tabs.selected = SelectedTab::Queries; + } + _ => {} + } +} + fn logs_tab_app_event_handler(app: &mut App, event: AppEvent) { match event { - AppEvent::Key(key) => match app.state.explore_tab.is_editable() { - true => explore_tab_editable_handler(app, key), - false => explore_tab_normal_mode_handler(app, key), - }, + AppEvent::Key(key) => logs_tab_key_event_handler(app, key), AppEvent::ExploreQueryResult(r) => app.state.explore_tab.set_query_results(r), AppEvent::Tick => {} AppEvent::Error => {} @@ -125,7 +183,7 @@ fn logs_tab_app_event_handler(app: &mut App, event: AppEvent) { pub fn app_event_handler(app: &mut App, event: AppEvent) -> Result<()> { let now = std::time::Instant::now(); //TODO: Create event to action - debug!("Tui::Event: {:?}", event); + trace!("Tui::Event: {:?}", event); if let AppEvent::ExecuteDDL(ddl) = event { let queries: Vec = ddl.split(";").map(|s| s.to_string()).collect(); queries.into_iter().for_each(|q| { @@ -144,6 +202,6 @@ pub fn app_event_handler(app: &mut App, event: AppEvent) -> Result<()> { SelectedTab::Logs => logs_tab_app_event_handler(app, event), }; } - debug!("Event handling took: {:?}", now.elapsed()); + trace!("Event handling took: {:?}", now.elapsed()); Ok(()) } diff --git a/src/app/mod.rs b/src/app/mod.rs index ff412e3..b823d84 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -11,6 +11,7 @@ use crossterm::event as ct; use datafusion::arrow::array::RecordBatch; use futures::FutureExt; use futures_util::StreamExt; +use log::{debug, error, info, trace}; use ratatui::backend::CrosstermBackend; use ratatui::crossterm::{ self, cursor, event, @@ -21,7 +22,6 @@ use strum::IntoEnumIterator; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; -use tracing::{debug, error, info}; use self::execution::ExecutionContext; use self::handlers::{app_event_handler, crossterm_event_handler}; @@ -42,6 +42,7 @@ pub enum AppEvent { Resize(u16, u16), ExecuteDDL(String), ExploreQueryResult(Vec), + ExploreQueryError(String), } pub struct App<'app> { @@ -105,7 +106,7 @@ impl<'app> App<'app> { self.task.abort(); } if counter > 100 { - tracing::error!("Failed to abort task in 100 milliseconds for unknown reason"); + error!("Failed to abort task in 100 milliseconds for unknown reason"); break; } } @@ -142,7 +143,7 @@ impl<'app> App<'app> { // TODO: Can maybe make tx optional, add a self param, and get tx from self let res = tx.send(app_event); match res { - Ok(_) => debug!("App event sent"), + Ok(_) => trace!("App event sent"), Err(err) => error!("Error sending app event: {}", err), }; } @@ -277,7 +278,7 @@ pub async fn run_app(cli: cli::DftCli, state: state::AppState<'_>) -> Result<()> let event = app.next().await?; if let AppEvent::Render = event.clone() { - terminal.draw(|f| f.render_widget(&app, f.size()))?; + terminal.draw(|f| f.render_widget(&app, f.area()))?; }; app.handle_app_event(event)?; diff --git a/src/app/state/mod.rs b/src/app/state/mod.rs index 1cabe7d..001d736 100644 --- a/src/app/state/mod.rs +++ b/src/app/state/mod.rs @@ -4,10 +4,10 @@ use crate::app::config::get_data_dir; use crate::app::state::tabs::explore::ExploreTabState; use crate::cli; use crate::ui::SelectedTab; -use std::fs::File; -use std::io::Read; +use log::{debug, error, info}; use std::path::PathBuf; -use tracing::{debug, error, info, instrument}; + +use self::tabs::logs::LogsTabState; use super::config::AppConfig; @@ -30,10 +30,10 @@ pub struct AppState<'app> { pub should_quit: bool, pub data_dir: PathBuf, pub explore_tab: ExploreTabState<'app>, + pub logs_tab: LogsTabState, pub tabs: Tabs, } -#[instrument] pub fn initialize(args: &cli::DftCli) -> AppState { debug!("Initializing state"); let data_dir = get_data_dir(); @@ -65,12 +65,14 @@ pub fn initialize(args: &cli::DftCli) -> AppState { let tabs = Tabs::default(); let explore_tab_state = ExploreTabState::new(); + let logs_tab_state = LogsTabState::default(); AppState { config, data_dir, tabs, explore_tab: explore_tab_state, + logs_tab: logs_tab_state, should_quit: false, } } diff --git a/src/app/state/tabs/logs.rs b/src/app/state/tabs/logs.rs new file mode 100644 index 0000000..d17638b --- /dev/null +++ b/src/app/state/tabs/logs.rs @@ -0,0 +1,22 @@ +use tui_logger::TuiWidgetState; + +#[derive(Default)] +pub struct LogsTabState { + state: TuiWidgetState, +} + +impl LogsTabState { + pub fn state(&self) -> &TuiWidgetState { + &self.state + } + + pub fn transition(&mut self, event: tui_logger::TuiWidgetEvent) { + self.state.transition(event) + } +} + +impl std::fmt::Debug for LogsTabState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LogsTabState").finish() + } +} diff --git a/src/app/state/tabs/mod.rs b/src/app/state/tabs/mod.rs index 5fb7aaa..4a859d8 100644 --- a/src/app/state/tabs/mod.rs +++ b/src/app/state/tabs/mod.rs @@ -1 +1,2 @@ pub mod explore; +pub mod logs; diff --git a/src/telemetry/mod.rs b/src/telemetry/mod.rs index e1b8347..f70e38a 100644 --- a/src/telemetry/mod.rs +++ b/src/telemetry/mod.rs @@ -1,53 +1,9 @@ -use crate::app::config::{get_data_dir, LOG_ENV, LOG_FILE}; use color_eyre::Result; use log::LevelFilter; -use std::collections::HashMap; -use std::fs::OpenOptions; -use tracing::debug; -use tracing_error::ErrorLayer; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; -use tracing_subscriber::{fmt, Layer}; pub fn initialize_logs() -> Result<()> { - // let directory = get_data_dir(); - // debug!("Fig data directory: {:?}", directory); - // std::fs::create_dir_all(directory.clone())?; - // let log_path = directory.join(LOG_FILE.clone()); - // debug!("Fig log path: {:?}", log_path); - // let log_file = OpenOptions::new() - // .create(true) - // .append(true) - // .open(log_path)?; - // std::env::set_var( - // "RUST_LOG", - // std::env::var("RUST_LOG") - // .or_else(|_| std::env::var(LOG_ENV.clone())) - // .unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))), - // ); - tui_logger::init_logger(LevelFilter::Debug).unwrap(); tui_logger::set_default_level(LevelFilter::Debug); - // let l = tui_logger::tracing_subscriber_layer(); - // let l = l.with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); - // let log_sink = std::env::var("LOG_SINK").unwrap_or("file".to_string()); - // if log_sink == "stdout" { - // fmt::init(); - // } else { - // let file_subscriber = fmt::layer() - // .with_file(true) - // .with_line_number(true) - // .with_writer(log_file) - // .with_target(false) - // .with_ansi(false) - // .with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); - // tracing_subscriber::registry() - // .with(file_subscriber) - // .with(ErrorLayer::default()) - // .with(l) - // .init(); - // } - Ok(()) } diff --git a/src/ui/tabs/logs.rs b/src/ui/tabs/logs.rs index e6a1240..49db947 100644 --- a/src/ui/tabs/logs.rs +++ b/src/ui/tabs/logs.rs @@ -1,53 +1,39 @@ use ratatui::{ buffer::Buffer, - layout::{Constraint, Direction, Layout, Rect}, - style::{palette::tailwind, Style, Stylize}, - widgets::{Block, Borders, Row, Table, Widget}, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::{palette::tailwind, Style}, + widgets::{Block, Paragraph, Widget}, }; use tui_logger::TuiLoggerSmartWidget; -use crate::{app::App, ui::convert::record_batches_to_table}; +use crate::app::App; -// pub fn render_sql_editor(area: Rect, buf: &mut Buffer, app: &App) { -// let border_color = if app.state.explore_tab.is_editable() { -// tailwind::GREEN.c300 -// } else { -// tailwind::WHITE -// }; -// let block = Block::default() -// .title(" Editor ") -// .borders(Borders::ALL) -// .fg(border_color) -// .title_bottom(" Cmd+Enter to run query "); -// let mut editor = app.state.explore_tab.editor(); -// editor.set_block(block); -// editor.render(area, buf) -// } - -// pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) { -// let block = Block::default().title(" Results ").borders(Borders::ALL); -// if let Some(r) = app.state.explore_tab.query_results() { -// record_batches_to_table(r).render(area, buf); -// } else { -// let row = Row::new(vec!["Run a query to generate results"]); -// let widths = vec![Constraint::Percentage(100)]; -// let table = Table::new(vec![row], widths).block(block); -// table.render(area, buf); -// } -// } - -pub fn render_logs(area: Rect, buf: &mut Buffer, app: &App) { - // let constraints = vec![Constraint::Percentage(50), Constraint::Percentage(50)]; - // let layout = Layout::new(Direction::Vertical, constraints).split(area); - // let editor = layout[0]; - // let results = layout[1]; - // render_sql_editor(editor, buf, app); - // render_sql_results(results, buf, app); +fn render_smart_widget(area: Rect, buf: &mut Buffer, app: &App) { let logs = TuiLoggerSmartWidget::default() .style_error(Style::default().fg(tailwind::RED.c700)) - .style_debug(Style::default().fg(tailwind::GREEN.c700)) + .style_debug(Style::default().fg(tailwind::ORANGE.c700)) .style_warn(Style::default().fg(tailwind::YELLOW.c700)) .style_trace(Style::default().fg(tailwind::GRAY.c700)) - .style_info(Style::default().fg(tailwind::BLUE.c700)); + .style_info(Style::default().fg(tailwind::WHITE)) + .state(app.state.logs_tab.state()); logs.render(area, buf); } + +fn render_logs_help(area: Rect, buf: &mut Buffer) { + let help_text = vec!["f - Focus logs", "h - Hide logs", "⇧ ⇩ - Select target"].join(" | "); + + let block = Block::default(); + let help = Paragraph::new(help_text) + .block(block) + .alignment(Alignment::Center); + help.render(area, buf); +} + +pub fn render_logs(area: Rect, buf: &mut Buffer, app: &App) { + let block = Block::default(); + block.render(area, buf); + let constraints = vec![Constraint::Min(0), Constraint::Length(1)]; + let [logs_area, footer_area] = Layout::new(Direction::Vertical, constraints).areas(area); + render_smart_widget(logs_area, buf, app); + render_logs_help(footer_area, buf); +} From f149703559c1f8b0fa147dc1f9fce4e28ed68c56 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 07:02:58 -0400 Subject: [PATCH 05/17] Scrolling working --- src/app/handlers/mod.rs | 25 +++++++++++++++++++++++-- src/app/mod.rs | 22 ++++++++++++++++++++-- src/app/state/tabs/explore.rs | 30 +++++++++++++++++++++++++++++- src/ui/tabs/explore.rs | 17 +++++++++++++---- 4 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index b8d2c72..ed7bda4 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -44,6 +44,19 @@ fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { } app.state.explore_tab.edit(); } + KeyCode::Down => { + if let Some(s) = app.state.explore_tab.query_results_state_mut() { + info!("Select next"); + s.select_next(); + } + } + KeyCode::Up => { + if let Some(s) = app.state.explore_tab.query_results_state_mut() { + info!("Select previous"); + s.select_previous(); + } + } + tab @ (KeyCode::Char('e') | KeyCode::Char('l')) => tab_navigation_handler(app, tab), KeyCode::Enter => { info!("Run query"); @@ -116,7 +129,10 @@ fn explore_tab_app_event_handler(app: &mut App, event: AppEvent) { true => explore_tab_editable_handler(app, key), false => explore_tab_normal_mode_handler(app, key), }, - AppEvent::ExploreQueryResult(r) => app.state.explore_tab.set_query_results(r), + AppEvent::ExploreQueryResult(r) => { + app.state.explore_tab.set_query_results(r); + app.state.explore_tab.refresh_query_results_state(); + } AppEvent::Tick => {} AppEvent::Error => {} _ => {} @@ -173,7 +189,10 @@ fn logs_tab_key_event_handler(app: &mut App, key: KeyEvent) { fn logs_tab_app_event_handler(app: &mut App, event: AppEvent) { match event { AppEvent::Key(key) => logs_tab_key_event_handler(app, key), - AppEvent::ExploreQueryResult(r) => app.state.explore_tab.set_query_results(r), + AppEvent::ExploreQueryResult(r) => { + app.state.explore_tab.set_query_results(r); + app.state.explore_tab.refresh_query_results_state(); + } AppEvent::Tick => {} AppEvent::Error => {} _ => {} @@ -196,6 +215,8 @@ pub fn app_event_handler(app: &mut App, event: AppEvent) -> Result<()> { } }); }) + } else if let AppEvent::ExploreQueryError(e) = event { + app.state.explore_tab.set_query_error(e); } else { match app.state.tabs.selected { SelectedTab::Queries => explore_tab_app_event_handler(app, event), diff --git a/src/app/mod.rs b/src/app/mod.rs index b823d84..96c355f 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3,6 +3,8 @@ pub mod execution; pub mod handlers; pub mod state; +use std::path::PathBuf; + use crate::cli::DftCli; use crate::{cli, ui}; use color_eyre::eyre::eyre; @@ -199,8 +201,23 @@ impl<'app> App<'app> { } pub fn execute_ddl(&mut self) { - let ddl = std::fs::read_to_string("~/.datafusionrc").unwrap(); - let _ = self.app_event_tx.send(AppEvent::ExecuteDDL(ddl)); + if let Some(user_dirs) = directories::UserDirs::new() { + let datafusion_rc_path = user_dirs.home_dir().join(".datafusionrc"); + let maybe_ddl = std::fs::read_to_string(datafusion_rc_path); + let ddl = match maybe_ddl { + Ok(ddl) => { + info!("DDL: {:?}", ddl); + ddl + } + Err(err) => { + error!("Error reading DDL: {:?}", err); + return; + } + }; + let _ = self.app_event_tx.send(AppEvent::ExecuteDDL(ddl)); + } else { + error!("No user directories found"); + } } /// Dispatch to the appropriate event loop based on the command @@ -268,6 +285,7 @@ pub async fn run_app(cli: cli::DftCli, state: state::AppState<'_>) -> Result<()> match &cli.command { Some(cli::Command::App(_)) | None => { + app.execute_ddl(); // app.load_data(false).await?; let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(std::io::stdout())).unwrap(); diff --git a/src/app/state/tabs/explore.rs b/src/app/state/tabs/explore.rs index c16f91f..617a3a4 100644 --- a/src/app/state/tabs/explore.rs +++ b/src/app/state/tabs/explore.rs @@ -1,6 +1,7 @@ use datafusion::arrow::array::RecordBatch; use ratatui::crossterm::event::KeyEvent; use ratatui::style::{palette::tailwind, Style}; +use ratatui::widgets::TableState; use tui_textarea::TextArea; #[derive(Debug)] @@ -8,6 +9,8 @@ pub struct ExploreTabState<'app> { editor: TextArea<'app>, editor_editable: bool, query_results: Option>, + query_results_state: Option, + query_error: Option, } impl<'app> ExploreTabState<'app> { @@ -20,10 +23,35 @@ impl<'app> ExploreTabState<'app> { editor: textarea, editor_editable: false, query_results: None, - // query_handle: None, + query_results_state: None, + query_error: None, } } + pub fn query_results_state_clone(&self) -> Option { + self.query_results_state.clone() + } + + pub fn query_results_state_mut(&mut self) -> &mut Option { + &mut self.query_results_state + } + + pub fn refresh_query_results_state(&mut self) { + self.query_results_state = Some(TableState::default()); + } + + // pub fn query_results_state_mut(&mut self) -> &mut Option { + // &mut self.query_results_state + // } + + pub fn query_error(&self) -> &Option { + &self.query_error + } + + pub fn set_query_error(&mut self, error: String) { + self.query_error = Some(error); + } + pub fn editor(&self) -> TextArea { // TODO: Figure out how to do this without clone. Probably need logic in handler to make // updates to the Widget and then pass a ref diff --git a/src/ui/tabs/explore.rs b/src/ui/tabs/explore.rs index 98e5c19..3e4af67 100644 --- a/src/ui/tabs/explore.rs +++ b/src/ui/tabs/explore.rs @@ -1,8 +1,8 @@ use ratatui::{ buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, - style::{palette::tailwind, Stylize}, - widgets::{Block, Borders, Row, Table, Widget}, + style::{palette::tailwind, Style, Stylize}, + widgets::{Block, Borders, Row, StatefulWidget, Table, Widget}, }; use crate::{app::App, ui::convert::record_batches_to_table}; @@ -26,12 +26,21 @@ pub fn render_sql_editor(area: Rect, buf: &mut Buffer, app: &App) { pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) { let block = Block::default().title(" Results ").borders(Borders::ALL); if let Some(r) = app.state.explore_tab.query_results() { - record_batches_to_table(r).render(area, buf); + if let Some(mut s) = app.state.explore_tab.query_results_state_clone() { + let table = record_batches_to_table(r) + .highlight_style(Style::default().bg(tailwind::WHITE).fg(tailwind::BLACK)); + StatefulWidget::render(table, area, buf, &mut s); + } + } else if let Some(e) = app.state.explore_tab.query_error() { + let row = Row::new(vec![e.to_string()]); + let widths = vec![Constraint::Percentage(100)]; + let table = Table::new(vec![row], widths).block(block); + Widget::render(table, area, buf); } else { let row = Row::new(vec!["Run a query to generate results"]); let widths = vec![Constraint::Percentage(100)]; let table = Table::new(vec![row], widths).block(block); - table.render(area, buf); + Widget::render(table, area, buf); } } From 4bb34d6df02b22aa29497d83632047a231ba9760 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 07:10:25 -0400 Subject: [PATCH 06/17] Cleanup --- src/app/config.rs | 4 ++-- src/app/mod.rs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/config.rs b/src/app/config.rs index b73419a..a7898e4 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -66,11 +66,11 @@ pub struct DisplayConfig { } fn default_tick_rate() -> f64 { - 5.0 + 60.0 } fn default_frame_rate() -> f64 { - 5.0 + 60.0 } impl Default for DisplayConfig { diff --git a/src/app/mod.rs b/src/app/mod.rs index 96c355f..a35e900 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3,8 +3,6 @@ pub mod execution; pub mod handlers; pub mod state; -use std::path::PathBuf; - use crate::cli::DftCli; use crate::{cli, ui}; use color_eyre::eyre::eyre; From 0d9c41219a3c1e3e72a04b6e32a27ee0e4408ab8 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 07:18:32 -0400 Subject: [PATCH 07/17] License --- src/app/config.rs | 17 +++++++++++++++++ src/app/execution.rs | 17 +++++++++++++++++ src/app/handlers/mod.rs | 17 +++++++++++++++++ src/app/mod.rs | 17 +++++++++++++++++ src/app/state/mod.rs | 17 +++++++++++++++++ src/app/state/tabs/explore.rs | 17 +++++++++++++++++ src/app/state/tabs/logs.rs | 17 +++++++++++++++++ src/app/state/tabs/mod.rs | 17 +++++++++++++++++ src/cli/mod.rs | 18 +++++++++++++++++- src/main.rs | 17 +++++++++++++++++ src/telemetry/mod.rs | 17 +++++++++++++++++ src/ui/convert.rs | 17 +++++++++++++++++ src/ui/mod.rs | 17 +++++++++++++++++ src/ui/tabs/explore.rs | 17 +++++++++++++++++ src/ui/tabs/logs.rs | 17 +++++++++++++++++ src/ui/tabs/mod.rs | 17 +++++++++++++++++ 16 files changed, 272 insertions(+), 1 deletion(-) diff --git a/src/app/config.rs b/src/app/config.rs index a7898e4..35820b8 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use std::path::PathBuf; use directories::{ProjectDirs, UserDirs}; diff --git a/src/app/execution.rs b/src/app/execution.rs index 092418d..58a9663 100644 --- a/src/app/execution.rs +++ b/src/app/execution.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use color_eyre::eyre::Result; use datafusion::arrow::util::pretty::pretty_format_batches; use datafusion::execution::TaskContext; diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index ed7bda4..839027c 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use color_eyre::{eyre::eyre, Result}; use log::{debug, error, info, trace}; use ratatui::crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers}; diff --git a/src/app/mod.rs b/src/app/mod.rs index a35e900..216521e 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + pub mod config; pub mod execution; pub mod handlers; diff --git a/src/app/state/mod.rs b/src/app/state/mod.rs index 001d736..62ac54b 100644 --- a/src/app/state/mod.rs +++ b/src/app/state/mod.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + pub mod tabs; use crate::app::config::get_data_dir; diff --git a/src/app/state/tabs/explore.rs b/src/app/state/tabs/explore.rs index 617a3a4..d756407 100644 --- a/src/app/state/tabs/explore.rs +++ b/src/app/state/tabs/explore.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use datafusion::arrow::array::RecordBatch; use ratatui::crossterm::event::KeyEvent; use ratatui::style::{palette::tailwind, Style}; diff --git a/src/app/state/tabs/logs.rs b/src/app/state/tabs/logs.rs index d17638b..4f37e55 100644 --- a/src/app/state/tabs/logs.rs +++ b/src/app/state/tabs/logs.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use tui_logger::TuiWidgetState; #[derive(Default)] diff --git a/src/app/state/tabs/mod.rs b/src/app/state/tabs/mod.rs index 4a859d8..45a0623 100644 --- a/src/app/state/tabs/mod.rs +++ b/src/app/state/tabs/mod.rs @@ -1,2 +1,19 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + pub mod explore; pub mod logs; diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 4747721..a4ba2c3 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use std::path::{Path, PathBuf}; use clap::{Parser, Subcommand}; @@ -9,7 +26,6 @@ Dft Environment Variables RUST_LOG { trace | debug | info | error }: Standard across rust ecosystem for determining log level of application. Default is info. -LOG_SINK { stdout | file }: Write logs to file or stdout. Default is file. "; #[derive(Clone, Debug, Parser)] diff --git a/src/main.rs b/src/main.rs index 7d3c95b..e7a3351 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + mod app; mod cli; mod telemetry; diff --git a/src/telemetry/mod.rs b/src/telemetry/mod.rs index f70e38a..07184ef 100644 --- a/src/telemetry/mod.rs +++ b/src/telemetry/mod.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use color_eyre::Result; use log::LevelFilter; diff --git a/src/ui/convert.rs b/src/ui/convert.rs index dd47c6c..eb257ae 100644 --- a/src/ui/convert.rs +++ b/src/ui/convert.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use datafusion::arrow::{ array::{ Int16Array, Int32Array, Int64Array, Int8Array, RecordBatch, StringArray, UInt16Array, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 69e0828..1a63c2e 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + pub mod convert; pub mod tabs; diff --git a/src/ui/tabs/explore.rs b/src/ui/tabs/explore.rs index 3e4af67..bf002d3 100644 --- a/src/ui/tabs/explore.rs +++ b/src/ui/tabs/explore.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use ratatui::{ buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, diff --git a/src/ui/tabs/logs.rs b/src/ui/tabs/logs.rs index 49db947..6582e98 100644 --- a/src/ui/tabs/logs.rs +++ b/src/ui/tabs/logs.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use ratatui::{ buffer::Buffer, layout::{Alignment, Constraint, Direction, Layout, Rect}, diff --git a/src/ui/tabs/mod.rs b/src/ui/tabs/mod.rs index 4a859d8..45a0623 100644 --- a/src/ui/tabs/mod.rs +++ b/src/ui/tabs/mod.rs @@ -1,2 +1,19 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + pub mod explore; pub mod logs; From b7fa88696ff02bdd14700071a94584649d4da71b Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 07:19:54 -0400 Subject: [PATCH 08/17] Cargo.toml cleanup --- Cargo.toml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 56ebdbc..0d826a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,12 @@ [package] -name = "dft" +name = "datafusion-tui" +description = "Terminal based, extensible, interactive data analysis tool using SQL " +homepage = "https://github.com/datafusion-contrib/datafusion-tui" +repository = "https://github.com/datafusion-contrib/datafusion-tui" +readme = "README.md" +authors = ["Matthew Turner "] +license = "Apache-2.0" +keywords = ["arrow", "query", "sql", "datafusion"] version = "0.1.0" edition = "2021" @@ -27,5 +34,9 @@ toml = "0.8.12" tui-logger = {version = "0.12", features = ["tracing-support"]} tui-textarea = "0.6.1" +[[bin]] +name = "dft" +path = "src/main.rs" + [lints.clippy] clone_on_ref_ptr = "deny" From 6638d11a93c6e5ec8cdd66c65eec0cd69809ccdf Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 07:34:18 -0400 Subject: [PATCH 09/17] Add clear editor --- Cargo.lock | 2 +- src/app/handlers/mod.rs | 7 ++++--- src/app/state/tabs/explore.rs | 4 ++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5109a39..dad5e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1124,7 +1124,7 @@ dependencies = [ ] [[package]] -name = "dft" +name = "datafusion-tui" version = "0.1.0" dependencies = [ "async-trait", diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index 839027c..5fb1d80 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -40,7 +40,7 @@ pub fn crossterm_event_handler(event: event::Event) -> Option { fn tab_navigation_handler(app: &mut App, key: KeyCode) { match key { - KeyCode::Char('e') => app.state.tabs.selected = SelectedTab::Queries, + KeyCode::Char('s') => app.state.tabs.selected = SelectedTab::Queries, KeyCode::Char('l') => app.state.tabs.selected = SelectedTab::Logs, _ => {} }; @@ -49,7 +49,8 @@ fn tab_navigation_handler(app: &mut App, key: KeyCode) { fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { match key.code { KeyCode::Char('q') => app.state.should_quit = true, - KeyCode::Char('i') => { + KeyCode::Char('c') => app.state.explore_tab.clear_editor(), + KeyCode::Char('e') => { let editor = app.state.explore_tab.editor(); let lines = editor.lines(); let content = lines.join(""); @@ -74,7 +75,7 @@ fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { } } - tab @ (KeyCode::Char('e') | KeyCode::Char('l')) => tab_navigation_handler(app, tab), + tab @ (KeyCode::Char('s') | KeyCode::Char('l')) => tab_navigation_handler(app, tab), KeyCode::Enter => { info!("Run query"); let query = app.state.explore_tab.editor().lines().join(""); diff --git a/src/app/state/tabs/explore.rs b/src/app/state/tabs/explore.rs index d756407..dee5e06 100644 --- a/src/app/state/tabs/explore.rs +++ b/src/app/state/tabs/explore.rs @@ -86,6 +86,10 @@ impl<'app> ExploreTabState<'app> { } } + pub fn clear_editor(&mut self) { + self.editor = TextArea::new(vec!["".to_string()]); + } + pub fn update_editor_content(&mut self, key: KeyEvent) { self.editor.input(key); } From 3c6aaca3ca1b0eab0f160b6c5970904aaad10740 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 07:44:43 -0400 Subject: [PATCH 10/17] More cleanup --- src/app/handlers/mod.rs | 2 +- src/app/state/tabs/explore.rs | 6 +----- src/ui/mod.rs | 18 +---------------- src/ui/tabs/explore.rs | 37 ++++++++++++++++++++++++++--------- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index 5fb1d80..87576c0 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -143,7 +143,7 @@ fn explore_tab_editable_handler(app: &mut App, key: KeyEvent) { fn explore_tab_app_event_handler(app: &mut App, event: AppEvent) { match event { - AppEvent::Key(key) => match app.state.explore_tab.is_editable() { + AppEvent::Key(key) => match app.state.explore_tab.editor_editable() { true => explore_tab_editable_handler(app, key), false => explore_tab_normal_mode_handler(app, key), }, diff --git a/src/app/state/tabs/explore.rs b/src/app/state/tabs/explore.rs index dee5e06..db6cbab 100644 --- a/src/app/state/tabs/explore.rs +++ b/src/app/state/tabs/explore.rs @@ -57,10 +57,6 @@ impl<'app> ExploreTabState<'app> { self.query_results_state = Some(TableState::default()); } - // pub fn query_results_state_mut(&mut self) -> &mut Option { - // &mut self.query_results_state - // } - pub fn query_error(&self) -> &Option { &self.query_error } @@ -102,7 +98,7 @@ impl<'app> ExploreTabState<'app> { self.editor_editable = false; } - pub fn is_editable(&self) -> bool { + pub fn editor_editable(&self) -> bool { self.editor_editable } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1a63c2e..0bc86e1 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -18,7 +18,7 @@ pub mod convert; pub mod tabs; -use ratatui::{prelude::*, style::palette::tailwind, widgets::*}; +use ratatui::{prelude::*, style::palette::tailwind}; use strum::{Display, EnumIter, FromRepr}; use crate::app::App; @@ -61,13 +61,6 @@ impl SelectedTab { } } - const fn palette(self) -> tailwind::Palette { - match self { - Self::Queries => tailwind::STONE, - Self::Logs => tailwind::STONE, - } - } - /// Get the previous tab, if there is no previous tab return the current tab. pub fn previous(self) -> Self { let current_index: usize = self as usize; @@ -82,15 +75,6 @@ impl SelectedTab { Self::from_repr(next_index).unwrap_or(self) } - /// A block surrounding the tab's content - fn block(self) -> Block<'static> { - Block::default() - .borders(Borders::ALL) - .border_set(symbols::border::PROPORTIONAL_TALL) - .padding(Padding::horizontal(1)) - .border_style(self.palette().c700) - } - fn render_explore(self, area: Rect, buf: &mut Buffer, app: &App) { explore::render_explore(area, buf, app) } diff --git a/src/ui/tabs/explore.rs b/src/ui/tabs/explore.rs index bf002d3..907c1e5 100644 --- a/src/ui/tabs/explore.rs +++ b/src/ui/tabs/explore.rs @@ -17,15 +17,15 @@ use ratatui::{ buffer::Buffer, - layout::{Constraint, Direction, Layout, Rect}, + layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{palette::tailwind, Style, Stylize}, - widgets::{Block, Borders, Row, StatefulWidget, Table, Widget}, + widgets::{Block, Borders, Paragraph, Row, StatefulWidget, Table, Widget}, }; use crate::{app::App, ui::convert::record_batches_to_table}; pub fn render_sql_editor(area: Rect, buf: &mut Buffer, app: &App) { - let border_color = if app.state.explore_tab.is_editable() { + let border_color = if app.state.explore_tab.editor_editable() { tailwind::GREEN.c300 } else { tailwind::WHITE @@ -61,11 +61,30 @@ pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) { } } +pub fn render_sql_help(area: Rect, buf: &mut Buffer, app: &App) { + let block = Block::default(); + let help = if app.state.explore_tab.editor_editable() { + vec!["'Esc' to exit edit mode"] + } else { + vec!["'e' to edit", "'c' to clear editor", "'Enter' to run query"] + }; + + let help_text = help.join(" | "); + let p = Paragraph::new(help_text) + .block(block) + .alignment(Alignment::Center); + p.render(area, buf); +} + pub fn render_explore(area: Rect, buf: &mut Buffer, app: &App) { - let constraints = vec![Constraint::Percentage(50), Constraint::Percentage(50)]; - let layout = Layout::new(Direction::Vertical, constraints).split(area); - let editor = layout[0]; - let results = layout[1]; - render_sql_editor(editor, buf, app); - render_sql_results(results, buf, app); + let constraints = vec![ + Constraint::Fill(1), + Constraint::Fill(1), + Constraint::Length(1), + ]; + let [editor_area, results_area, help_area] = + Layout::new(Direction::Vertical, constraints).areas(area); + render_sql_editor(editor_area, buf, app); + render_sql_results(results_area, buf, app); + render_sql_help(help_area, buf, app); } From 7807ab853e488cdd8d4af8df29db148c5257c8ea Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 07:50:22 -0400 Subject: [PATCH 11/17] Clippy --- src/app/handlers/mod.rs | 2 +- src/app/state/tabs/explore.rs | 2 +- src/ui/convert.rs | 9 +++------ src/ui/tabs/logs.rs | 3 +-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index 87576c0..9221da8 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -227,7 +227,7 @@ pub fn app_event_handler(app: &mut App, event: AppEvent) -> Result<()> { let ctx = app.execution.session_ctx.clone(); tokio::spawn(async move { if let Ok(df) = ctx.sql(&q).await { - if let Ok(_) = df.collect().await { + if df.collect().await.is_ok() { info!("Successful DDL"); } } diff --git a/src/app/state/tabs/explore.rs b/src/app/state/tabs/explore.rs index db6cbab..ad192b0 100644 --- a/src/app/state/tabs/explore.rs +++ b/src/app/state/tabs/explore.rs @@ -21,7 +21,7 @@ use ratatui::style::{palette::tailwind, Style}; use ratatui::widgets::TableState; use tui_textarea::TextArea; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct ExploreTabState<'app> { editor: TextArea<'app>, editor_editable: bool, diff --git a/src/ui/convert.rs b/src/ui/convert.rs index eb257ae..b549d4a 100644 --- a/src/ui/convert.rs +++ b/src/ui/convert.rs @@ -91,7 +91,7 @@ where // longer than a frame lifetime. 'results: 'frame, { - if record_batches.len() == 0 { + if record_batches.is_empty() { empty_results_table() } else { let first_batch = &record_batches[0]; @@ -101,15 +101,12 @@ where .iter() .flat_map(|b| { let batch_row_cells = record_batch_to_table_row_cells(b); - let rows: Vec = batch_row_cells - .into_iter() - .map(|row_cells| Row::from_iter(row_cells)) - .collect(); + let rows: Vec = batch_row_cells.into_iter().map(Row::from_iter).collect(); rows }) .collect(); let column_count = first_batch.num_columns(); - let widths = (0..column_count).into_iter().map(|_| Constraint::Fill(1)); + let widths = (0..column_count).map(|_| Constraint::Fill(1)); let block = Block::default().borders(Borders::all()); Table::new(rows, widths).header(header_row).block(block) } diff --git a/src/ui/tabs/logs.rs b/src/ui/tabs/logs.rs index 6582e98..1f2238a 100644 --- a/src/ui/tabs/logs.rs +++ b/src/ui/tabs/logs.rs @@ -37,8 +37,7 @@ fn render_smart_widget(area: Rect, buf: &mut Buffer, app: &App) { } fn render_logs_help(area: Rect, buf: &mut Buffer) { - let help_text = vec!["f - Focus logs", "h - Hide logs", "⇧ ⇩ - Select target"].join(" | "); - + let help_text = ["f - Focus logs", "h - Hide logs", "⇧ ⇩ - Select target"].join(" | "); let block = Block::default(); let help = Paragraph::new(help_text) .block(block) From 5f3e73cbff6a2b452f368fbbe801e773d7ec3b65 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 08:01:23 -0400 Subject: [PATCH 12/17] Fix test --- README.md | 13 ------------- src/app/mod.rs | 5 ++++- src/ui/convert.rs | 13 +++++++++++-- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 6bd1222..91336b1 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,6 @@ Some of the current and planned features are: - There are ongoing conversations in DataFusion about adopting a new `ObjectStore` interface that would come with bindings to S3, ADLS, and GCP. I am monitoring this and plan on updating to use that interface when it is available. - `TableProvider` data sources - Delta Table => TODO - - Google Big Table => (currently in the bigtable branch which isnt up to date with latest DataFusion ) - - ApiTable => Will allow treating API endpoints as tables by handling pagination and authentication. Currently being prototyped in [#85](https://github.com/datafusion-contrib/datafusion-tui/pull/85) - Preloading DDL from `~/.datafusion/.datafusionrc` for local database available on startup ## User Guide @@ -61,15 +59,6 @@ CREATE VIEW OR REPLACE users_listings AS SELECT * FROM users LEFT JOIN listings This would make the tables `users`, `taxis`, `listings`, and the view `users_listings` available at startup. Any of these DDL statements could also be run interactively from the SQL editor as well to create the tables. -### Catalog Providers - -You can run `dft` with `--features=glue,s3` to registered databases and tables from your AWS Glue Catalogs. You can optionally use a configuration file to specify which databases you want to register. To use a configuration create `~/.datafusion/catalog_providers/glue.json`. In that file you can specify the databases that you want to register: - -```json -{ - "databases": ["my_database", "my_other_database"] -} -``` ### Key Mappings @@ -109,5 +98,3 @@ The interface is split into several tabs so that relevant information can be vie - S3: run / install with `--features=s3` - If you want to use your default AWS credentials, then no further action is required. For example your credentials in `~/.aws/credentials` will automatically be picked up. - If you want to use a custom S3 provider, such as MinIO, then you must create a `s3.json` configuration file in `~/.datafusion/object_stores/` with the fields `endpoint`, `access_key_id`, and `secret_access_key`. -- Use Ballista as execution engine - - Run / install with `--features=ballista` and provide `--host` and `--port` of the ballista scheduler when running `dft` diff --git a/src/app/mod.rs b/src/app/mod.rs index 216521e..2c77ca8 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -217,7 +217,10 @@ impl<'app> App<'app> { pub fn execute_ddl(&mut self) { if let Some(user_dirs) = directories::UserDirs::new() { - let datafusion_rc_path = user_dirs.home_dir().join(".datafusionrc"); + let datafusion_rc_path = user_dirs + .home_dir() + .join(".datafusion") + .join(".datafusionrc"); let maybe_ddl = std::fs::read_to_string(datafusion_rc_path); let ddl = match maybe_ddl { Ok(ddl) => { diff --git a/src/ui/convert.rs b/src/ui/convert.rs index b549d4a..a29c9e3 100644 --- a/src/ui/convert.rs +++ b/src/ui/convert.rs @@ -120,7 +120,10 @@ mod tests { ArrayRef, Int16Array, Int32Array, Int64Array, Int8Array, RecordBatch, StringArray, UInt16Array, UInt32Array, UInt64Array, UInt8Array, }; - use ratatui::widgets::Cell; + use ratatui::{ + style::{palette::tailwind, Stylize}, + widgets::Cell, + }; use super::{record_batch_to_table_header_cells, record_batch_to_table_row_cells}; @@ -131,7 +134,13 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a), ("b", b)]).unwrap(); let header_cells = record_batch_to_table_header_cells(&batch); - assert_eq!(header_cells, vec![Cell::new("a"), Cell::new("b")]); + assert_eq!( + header_cells, + vec![ + Cell::new("a").bg(tailwind::LIME.c300).fg(tailwind::BLACK), + Cell::new("b").bg(tailwind::LIME.c300).fg(tailwind::BLACK) + ] + ); } #[test] From 48661c8bf030de6f821c16fa8dfcf80500196098 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 09:10:05 -0400 Subject: [PATCH 13/17] Cleanup deps --- Cargo.toml | 1 - src/app/mod.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0d826a8..f2de5b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ crossterm = { version = "0.28.1", features = ["event-stream"] } datafusion = "40.0.0" directories = "5.0.1" futures = "0.3.30" -futures-util = "0.3.30" itertools = "0.13.0" lazy_static = "1.4.0" log = "0.4.22" diff --git a/src/app/mod.rs b/src/app/mod.rs index 2c77ca8..f82f188 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -27,7 +27,6 @@ use color_eyre::Result; use crossterm::event as ct; use datafusion::arrow::array::RecordBatch; use futures::FutureExt; -use futures_util::StreamExt; use log::{debug, error, info, trace}; use ratatui::backend::CrosstermBackend; use ratatui::crossterm::{ @@ -38,6 +37,7 @@ use ratatui::{prelude::*, style::palette::tailwind, widgets::*}; use strum::IntoEnumIterator; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::task::JoinHandle; +use tokio_stream::StreamExt; use tokio_util::sync::CancellationToken; use self::execution::ExecutionContext; From b324b39f2da4c53362fe28745a0cb6a51e1acf00 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 12:03:19 -0400 Subject: [PATCH 14/17] More cleanup --- Cargo.lock | 1 - src/app/handlers/mod.rs | 40 +++++++++++---- src/app/mod.rs | 4 +- src/app/state/tabs/explore.rs | 75 ++++++++++++++++++++++++++++ src/ui/convert.rs | 92 ++++++++++++++++++++--------------- src/ui/tabs/explore.rs | 18 +++++-- 6 files changed, 175 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dad5e30..f50192e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1134,7 +1134,6 @@ dependencies = [ "datafusion", "directories", "futures", - "futures-util", "itertools 0.13.0", "lazy_static", "log", diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index 9221da8..14851dc 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -15,12 +15,17 @@ // specific language governing permissions and limitations // under the License. +use std::time::{Duration, Instant}; + use color_eyre::{eyre::eyre, Result}; use log::{debug, error, info, trace}; use ratatui::crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers}; use tui_logger::TuiWidgetEvent; -use crate::{app::AppEvent, ui::SelectedTab}; +use crate::{ + app::{state::tabs::explore::Query, AppEvent}, + ui::SelectedTab, +}; use super::App; @@ -78,29 +83,43 @@ fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { tab @ (KeyCode::Char('s') | KeyCode::Char('l')) => tab_navigation_handler(app, tab), KeyCode::Enter => { info!("Run query"); - let query = app.state.explore_tab.editor().lines().join(""); - info!("Query: {}", query); + let sql = app.state.explore_tab.editor().lines().join(""); + info!("SQL: {}", sql); + let mut query = Query::new(sql.clone(), None, None, None, Duration::default()); let ctx = app.execution.session_ctx.clone(); let _event_tx = app.app_event_tx.clone(); // TODO: Maybe this should be on a separate runtime to prevent blocking main thread / // runtime tokio::spawn(async move { - match ctx.sql(&query).await { + let start = std::time::Instant::now(); + match ctx.sql(&sql).await { Ok(df) => match df.collect().await { Ok(res) => { - info!("Results: {:?}", res); - let _ = _event_tx.send(AppEvent::ExploreQueryResult(res)); + let elapsed = start.elapsed(); + let rows: usize = res.iter().map(|r| r.num_rows()).sum(); + query.set_results(Some(res)); + query.set_num_rows(Some(rows)); + query.set_elapsed_time(elapsed); + // let query = Query::new(sql, Some(res), Some(rows), None, elapsed); + // let _ = _event_tx.send(AppEvent::ExploreQueryResult(res)); } Err(e) => { error!("Error collecting results: {:?}", e); + let elapsed = start.elapsed(); + query.set_error(Some(e.to_string())); + query.set_elapsed_time(elapsed); let _ = _event_tx.send(AppEvent::ExploreQueryError(e.to_string())); } }, Err(e) => { error!("Error creating dataframe: {:?}", e); + let elapsed = start.elapsed(); + query.set_error(Some(e.to_string())); + query.set_elapsed_time(elapsed); let _ = _event_tx.send(AppEvent::ExploreQueryError(e.to_string())); } } + let _ = _event_tx.send(AppEvent::ExploreQueryResult(query)); // if let Ok(df) = ctx.sql(&query).await { // if let Ok(res) = df.collect().await.map_err(|e| eyre!(e)) { // info!("Results: {:?}", res); @@ -127,10 +146,13 @@ fn explore_tab_editable_handler(app: &mut App, key: KeyEvent) { // runtime tokio::spawn(async move { // TODO: Turn this into a match and return the error somehow + let start = Instant::now(); if let Ok(df) = ctx.sql(&query).await { if let Ok(res) = df.collect().await.map_err(|e| eyre!(e)) { info!("Results: {:?}", res); - let _ = _event_tx.send(AppEvent::ExploreQueryResult(res)); + let elapsed = start.elapsed(); + let query = Query::new(query, Some(res), None, None, elapsed); + let _ = _event_tx.send(AppEvent::ExploreQueryResult(query)); } } else { error!("Error creating dataframe") @@ -148,7 +170,7 @@ fn explore_tab_app_event_handler(app: &mut App, event: AppEvent) { false => explore_tab_normal_mode_handler(app, key), }, AppEvent::ExploreQueryResult(r) => { - app.state.explore_tab.set_query_results(r); + app.state.explore_tab.set_query(r); app.state.explore_tab.refresh_query_results_state(); } AppEvent::Tick => {} @@ -208,7 +230,7 @@ fn logs_tab_app_event_handler(app: &mut App, event: AppEvent) { match event { AppEvent::Key(key) => logs_tab_key_event_handler(app, key), AppEvent::ExploreQueryResult(r) => { - app.state.explore_tab.set_query_results(r); + app.state.explore_tab.set_query(r); app.state.explore_tab.refresh_query_results_state(); } AppEvent::Tick => {} diff --git a/src/app/mod.rs b/src/app/mod.rs index f82f188..b938695 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -25,7 +25,6 @@ use crate::{cli, ui}; use color_eyre::eyre::eyre; use color_eyre::Result; use crossterm::event as ct; -use datafusion::arrow::array::RecordBatch; use futures::FutureExt; use log::{debug, error, info, trace}; use ratatui::backend::CrosstermBackend; @@ -42,6 +41,7 @@ use tokio_util::sync::CancellationToken; use self::execution::ExecutionContext; use self::handlers::{app_event_handler, crossterm_event_handler}; +use self::state::tabs::explore::Query; #[derive(Clone, Debug)] pub enum AppEvent { @@ -58,7 +58,7 @@ pub enum AppEvent { Mouse(event::MouseEvent), Resize(u16, u16), ExecuteDDL(String), - ExploreQueryResult(Vec), + ExploreQueryResult(Query), ExploreQueryError(String), } diff --git a/src/app/state/tabs/explore.rs b/src/app/state/tabs/explore.rs index ad192b0..dd212e1 100644 --- a/src/app/state/tabs/explore.rs +++ b/src/app/state/tabs/explore.rs @@ -15,16 +15,82 @@ // specific language governing permissions and limitations // under the License. +use std::time::Duration; + use datafusion::arrow::array::RecordBatch; use ratatui::crossterm::event::KeyEvent; use ratatui::style::{palette::tailwind, Style}; use ratatui::widgets::TableState; use tui_textarea::TextArea; +#[derive(Clone, Debug)] +pub struct Query { + sql: String, + results: Option>, + num_rows: Option, + error: Option, + elapsed_time: Duration, +} + +impl Query { + pub fn new( + sql: String, + results: Option>, + num_rows: Option, + error: Option, + elapsed_time: Duration, + ) -> Self { + Self { + sql, + results, + num_rows, + error, + elapsed_time, + } + } + + pub fn sql(&self) -> &String { + &self.sql + } + + pub fn set_results(&mut self, results: Option>) { + self.results = results; + } + + pub fn results(&self) -> &Option> { + &self.results + } + + pub fn set_num_rows(&mut self, num_rows: Option) { + self.num_rows = num_rows; + } + + pub fn num_rows(&self) -> &Option { + &self.num_rows + } + + pub fn set_error(&mut self, error: Option) { + self.error = error; + } + + pub fn error(&self) -> &Option { + &self.error + } + + pub fn set_elapsed_time(&mut self, elapsed_time: Duration) { + self.elapsed_time = elapsed_time; + } + + pub fn elapsed_time(&self) -> Duration { + self.elapsed_time + } +} + #[derive(Debug, Default)] pub struct ExploreTabState<'app> { editor: TextArea<'app>, editor_editable: bool, + query: Option, query_results: Option>, query_results_state: Option, query_error: Option, @@ -39,6 +105,7 @@ impl<'app> ExploreTabState<'app> { Self { editor: textarea, editor_editable: false, + query: None, query_results: None, query_results_state: None, query_error: None, @@ -102,6 +169,14 @@ impl<'app> ExploreTabState<'app> { self.editor_editable } + pub fn set_query(&mut self, query: Query) { + self.query = Some(query); + } + + pub fn query(&self) -> &Option { + &self.query + } + pub fn set_query_results(&mut self, query_results: Vec) { self.query_results = Some(query_results); } diff --git a/src/ui/convert.rs b/src/ui/convert.rs index a29c9e3..9b43681 100644 --- a/src/ui/convert.rs +++ b/src/ui/convert.rs @@ -40,14 +40,21 @@ macro_rules! convert_array_values_to_cells { pub fn record_batch_to_table_header_cells(record_batch: &RecordBatch) -> Vec { let fields = record_batch.schema().fields().clone(); - fields - .into_iter() - .map(|f| { - Cell::new(f.name().to_string()) - .bg(tailwind::LIME.c300) - .fg(tailwind::BLACK) - }) - .collect() + let mut cells = vec![Cell::new("#").bg(tailwind::LIME.c300).fg(tailwind::BLACK)]; + fields.iter().for_each(|f| { + let cell = Cell::new(f.name().to_string()) + .bg(tailwind::LIME.c300) + .fg(tailwind::BLACK); + cells.push(cell); + }); + cells +} + +pub fn create_row_number_cells(record_batch: &RecordBatch) -> Vec { + let cells: Vec = (0..record_batch.num_rows()) + .map(|i| Cell::new(i.to_string())) + .collect(); + cells } pub fn record_batch_to_table_row_cells(record_batch: &RecordBatch) -> Vec> { @@ -59,6 +66,12 @@ pub fn record_batch_to_table_row_cells(record_batch: &RecordBatch) -> Vec convert_array_values_to_cells!(rows, arr, StringArray), @@ -105,7 +118,7 @@ where rows }) .collect(); - let column_count = first_batch.num_columns(); + let column_count = first_batch.num_columns() + 1; let widths = (0..column_count).map(|_| Constraint::Fill(1)); let block = Block::default().borders(Borders::all()); Table::new(rows, widths).header(header_row).block(block) @@ -137,6 +150,7 @@ mod tests { assert_eq!( header_cells, vec![ + Cell::new("#").bg(tailwind::LIME.c300).fg(tailwind::BLACK), Cell::new("a").bg(tailwind::LIME.c300).fg(tailwind::BLACK), Cell::new("b").bg(tailwind::LIME.c300).fg(tailwind::BLACK) ] @@ -150,9 +164,9 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); let table_cells = record_batch_to_table_row_cells(&batch); let expected = vec![ - vec![Cell::new("a")], - vec![Cell::new("b")], - vec![Cell::new("c")], + vec![Cell::new("0"), Cell::new("a")], + vec![Cell::new("1"), Cell::new("b")], + vec![Cell::new("2"), Cell::new("c")], ]; assert_eq!(table_cells, expected); @@ -160,9 +174,9 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); let a_table_cells = record_batch_to_table_row_cells(&batch); let expected = vec![ - vec![Cell::new("1")], - vec![Cell::new("2")], - vec![Cell::new("3")], + vec![Cell::new("0"), Cell::new("1")], + vec![Cell::new("1"), Cell::new("2")], + vec![Cell::new("2"), Cell::new("3")], ]; assert_eq!(a_table_cells, expected); @@ -170,9 +184,9 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); let a_table_cells = record_batch_to_table_row_cells(&batch); let expected = vec![ - vec![Cell::new("1")], - vec![Cell::new("2")], - vec![Cell::new("3")], + vec![Cell::new("0"), Cell::new("1")], + vec![Cell::new("1"), Cell::new("2")], + vec![Cell::new("2"), Cell::new("3")], ]; assert_eq!(a_table_cells, expected); @@ -180,9 +194,9 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); let a_table_cells = record_batch_to_table_row_cells(&batch); let expected = vec![ - vec![Cell::new("1")], - vec![Cell::new("2")], - vec![Cell::new("3")], + vec![Cell::new("0"), Cell::new("1")], + vec![Cell::new("1"), Cell::new("2")], + vec![Cell::new("2"), Cell::new("3")], ]; assert_eq!(a_table_cells, expected); @@ -190,9 +204,9 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); let a_table_cells = record_batch_to_table_row_cells(&batch); let expected = vec![ - vec![Cell::new("1")], - vec![Cell::new("2")], - vec![Cell::new("3")], + vec![Cell::new("0"), Cell::new("1")], + vec![Cell::new("1"), Cell::new("2")], + vec![Cell::new("2"), Cell::new("3")], ]; assert_eq!(a_table_cells, expected); @@ -200,9 +214,9 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); let a_table_cells = record_batch_to_table_row_cells(&batch); let expected = vec![ - vec![Cell::new("1")], - vec![Cell::new("2")], - vec![Cell::new("3")], + vec![Cell::new("0"), Cell::new("1")], + vec![Cell::new("1"), Cell::new("2")], + vec![Cell::new("2"), Cell::new("3")], ]; assert_eq!(a_table_cells, expected); @@ -210,9 +224,9 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); let a_table_cells = record_batch_to_table_row_cells(&batch); let expected = vec![ - vec![Cell::new("1")], - vec![Cell::new("2")], - vec![Cell::new("3")], + vec![Cell::new("0"), Cell::new("1")], + vec![Cell::new("1"), Cell::new("2")], + vec![Cell::new("2"), Cell::new("3")], ]; assert_eq!(a_table_cells, expected); @@ -220,9 +234,9 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); let a_table_cells = record_batch_to_table_row_cells(&batch); let expected = vec![ - vec![Cell::new("1")], - vec![Cell::new("2")], - vec![Cell::new("3")], + vec![Cell::new("0"), Cell::new("1")], + vec![Cell::new("1"), Cell::new("2")], + vec![Cell::new("2"), Cell::new("3")], ]; assert_eq!(a_table_cells, expected); @@ -230,9 +244,9 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a)]).unwrap(); let a_table_cells = record_batch_to_table_row_cells(&batch); let expected = vec![ - vec![Cell::new("1")], - vec![Cell::new("2")], - vec![Cell::new("3")], + vec![Cell::new("0"), Cell::new("1")], + vec![Cell::new("1"), Cell::new("2")], + vec![Cell::new("2"), Cell::new("3")], ]; assert_eq!(a_table_cells, expected); } @@ -244,9 +258,9 @@ mod tests { let batch = RecordBatch::try_from_iter(vec![("a", a), ("b", b)]).unwrap(); let a_table_cells = record_batch_to_table_row_cells(&batch); let expected = vec![ - vec![Cell::new("1"), Cell::new("a")], - vec![Cell::new("2"), Cell::new("b")], - vec![Cell::new("3"), Cell::new("c")], + vec![Cell::new("0"), Cell::new("1"), Cell::new("a")], + vec![Cell::new("1"), Cell::new("2"), Cell::new("b")], + vec![Cell::new("2"), Cell::new("3"), Cell::new("c")], ]; assert_eq!(a_table_cells, expected); } diff --git a/src/ui/tabs/explore.rs b/src/ui/tabs/explore.rs index 907c1e5..1cb3600 100644 --- a/src/ui/tabs/explore.rs +++ b/src/ui/tabs/explore.rs @@ -42,11 +42,21 @@ pub fn render_sql_editor(area: Rect, buf: &mut Buffer, app: &App) { pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) { let block = Block::default().title(" Results ").borders(Borders::ALL); - if let Some(r) = app.state.explore_tab.query_results() { + if let Some(q) = app.state.explore_tab.query() { if let Some(mut s) = app.state.explore_tab.query_results_state_clone() { - let table = record_batches_to_table(r) - .highlight_style(Style::default().bg(tailwind::WHITE).fg(tailwind::BLACK)); - StatefulWidget::render(table, area, buf, &mut s); + if let Some(r) = q.results() { + let block = block + .title_bottom(format!( + " {} rows in {}ms", + q.num_rows().unwrap_or(0), + q.elapsed_time().as_millis() + )) + .fg(tailwind::GREEN.c300); + let table = record_batches_to_table(r) + .highlight_style(Style::default().bg(tailwind::WHITE).fg(tailwind::BLACK)) + .block(block); + StatefulWidget::render(table, area, buf, &mut s); + } } } else if let Some(e) = app.state.explore_tab.query_error() { let row = Row::new(vec![e.to_string()]); From 3f70bd3effefa693a2c2e15b9544397f4bc55c5f Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 13:00:17 -0400 Subject: [PATCH 15/17] Improve scrolling --- src/app/execution.rs | 3 --- src/app/handlers/mod.rs | 6 ++++-- src/app/state/tabs/explore.rs | 13 +++++-------- src/ui/tabs/explore.rs | 5 ++++- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/app/execution.rs b/src/app/execution.rs index 58a9663..4b3e83e 100644 --- a/src/app/execution.rs +++ b/src/app/execution.rs @@ -28,9 +28,6 @@ use super::config::DataFusionConfig; pub struct ExecutionContext { pub session_ctx: SessionContext, pub config: DataFusionConfig, - /// Sends WSMessages to DataFusion. We have a separate sender for this, rather than piggy - /// backing on `app_event_tx` because we are only concerned about `WSMessage` and not other - /// `AppEvent`'s. pub cancellation_token: CancellationToken, } diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index 14851dc..1265229 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -68,14 +68,16 @@ fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { app.state.explore_tab.edit(); } KeyCode::Down => { - if let Some(s) = app.state.explore_tab.query_results_state_mut() { + if let Some(s) = app.state.explore_tab.query_results_state() { info!("Select next"); + let mut s = s.borrow_mut(); s.select_next(); } } KeyCode::Up => { - if let Some(s) = app.state.explore_tab.query_results_state_mut() { + if let Some(s) = app.state.explore_tab.query_results_state() { info!("Select previous"); + let mut s = s.borrow_mut(); s.select_previous(); } } diff --git a/src/app/state/tabs/explore.rs b/src/app/state/tabs/explore.rs index dd212e1..e50f6c7 100644 --- a/src/app/state/tabs/explore.rs +++ b/src/app/state/tabs/explore.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use core::cell::RefCell; use std::time::Duration; use datafusion::arrow::array::RecordBatch; @@ -92,7 +93,7 @@ pub struct ExploreTabState<'app> { editor_editable: bool, query: Option, query_results: Option>, - query_results_state: Option, + query_results_state: Option>, query_error: Option, } @@ -112,16 +113,12 @@ impl<'app> ExploreTabState<'app> { } } - pub fn query_results_state_clone(&self) -> Option { - self.query_results_state.clone() - } - - pub fn query_results_state_mut(&mut self) -> &mut Option { - &mut self.query_results_state + pub fn query_results_state(&self) -> &Option> { + &self.query_results_state } pub fn refresh_query_results_state(&mut self) { - self.query_results_state = Some(TableState::default()); + self.query_results_state = Some(RefCell::new(TableState::default())); } pub fn query_error(&self) -> &Option { diff --git a/src/ui/tabs/explore.rs b/src/ui/tabs/explore.rs index 1cb3600..eb98e1a 100644 --- a/src/ui/tabs/explore.rs +++ b/src/ui/tabs/explore.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +use std::borrow::BorrowMut; + use ratatui::{ buffer::Buffer, layout::{Alignment, Constraint, Direction, Layout, Rect}, @@ -43,7 +45,7 @@ pub fn render_sql_editor(area: Rect, buf: &mut Buffer, app: &App) { pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) { let block = Block::default().title(" Results ").borders(Borders::ALL); if let Some(q) = app.state.explore_tab.query() { - if let Some(mut s) = app.state.explore_tab.query_results_state_clone() { + if let Some(s) = app.state.explore_tab.query_results_state() { if let Some(r) = q.results() { let block = block .title_bottom(format!( @@ -55,6 +57,7 @@ pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) { let table = record_batches_to_table(r) .highlight_style(Style::default().bg(tailwind::WHITE).fg(tailwind::BLACK)) .block(block); + let mut s = s.borrow_mut(); StatefulWidget::render(table, area, buf, &mut s); } } From f39a113c505e8bafb51293b5f9b1e6aa6ce5c8d9 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 14:36:21 -0400 Subject: [PATCH 16/17] Small fix --- src/app/handlers/mod.rs | 8 +++----- src/app/mod.rs | 8 -------- src/ui/convert.rs | 3 +-- src/ui/tabs/explore.rs | 16 +++++++--------- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index 1265229..cfbf71b 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -110,7 +110,7 @@ fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { let elapsed = start.elapsed(); query.set_error(Some(e.to_string())); query.set_elapsed_time(elapsed); - let _ = _event_tx.send(AppEvent::ExploreQueryError(e.to_string())); + // let _ = _event_tx.send(AppEvent::ExploreQueryError(e.to_string())); } }, Err(e) => { @@ -118,7 +118,7 @@ fn explore_tab_normal_mode_handler(app: &mut App, key: KeyEvent) { let elapsed = start.elapsed(); query.set_error(Some(e.to_string())); query.set_elapsed_time(elapsed); - let _ = _event_tx.send(AppEvent::ExploreQueryError(e.to_string())); + // let _ = _event_tx.send(AppEvent::ExploreQueryError(e.to_string())); } } let _ = _event_tx.send(AppEvent::ExploreQueryResult(query)); @@ -172,6 +172,7 @@ fn explore_tab_app_event_handler(app: &mut App, event: AppEvent) { false => explore_tab_normal_mode_handler(app, key), }, AppEvent::ExploreQueryResult(r) => { + info!("Query results: {:?}", r); app.state.explore_tab.set_query(r); app.state.explore_tab.refresh_query_results_state(); } @@ -243,7 +244,6 @@ fn logs_tab_app_event_handler(app: &mut App, event: AppEvent) { pub fn app_event_handler(app: &mut App, event: AppEvent) -> Result<()> { let now = std::time::Instant::now(); - //TODO: Create event to action trace!("Tui::Event: {:?}", event); if let AppEvent::ExecuteDDL(ddl) = event { let queries: Vec = ddl.split(";").map(|s| s.to_string()).collect(); @@ -257,8 +257,6 @@ pub fn app_event_handler(app: &mut App, event: AppEvent) -> Result<()> { } }); }) - } else if let AppEvent::ExploreQueryError(e) = event { - app.state.explore_tab.set_query_error(e); } else { match app.state.tabs.selected { SelectedTab::Queries => explore_tab_app_event_handler(app, event), diff --git a/src/app/mod.rs b/src/app/mod.rs index b938695..16a36ec 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -59,15 +59,12 @@ pub enum AppEvent { Resize(u16, u16), ExecuteDDL(String), ExploreQueryResult(Query), - ExploreQueryError(String), } pub struct App<'app> { pub cli: DftCli, pub state: state::AppState<'app>, pub execution: ExecutionContext, - /// Client for making API calls with. Holds a connection pool - /// internally so it should be reused. pub app_event_tx: UnboundedSender, pub app_event_rx: UnboundedReceiver, pub app_cancellation_token: CancellationToken, @@ -253,9 +250,6 @@ impl<'app> App<'app> { .ok_or(eyre!("Unable to get event")) } - /// Load portfolio positions from disk, store them in application state with snapshot - /// information, and connect to the relevant websockets to stream data for the positions. - fn handle_app_event(&mut self, event: AppEvent) -> Result<()> { app_event_handler(self, event) } @@ -304,10 +298,8 @@ pub async fn run_app(cli: cli::DftCli, state: state::AppState<'_>) -> Result<()> match &cli.command { Some(cli::Command::App(_)) | None => { app.execute_ddl(); - // app.load_data(false).await?; let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(std::io::stdout())).unwrap(); - // Start event loop app.enter(true)?; // Main loop for handling events loop { diff --git a/src/ui/convert.rs b/src/ui/convert.rs index 9b43681..786a46f 100644 --- a/src/ui/convert.rs +++ b/src/ui/convert.rs @@ -39,9 +39,8 @@ macro_rules! convert_array_values_to_cells { } pub fn record_batch_to_table_header_cells(record_batch: &RecordBatch) -> Vec { - let fields = record_batch.schema().fields().clone(); let mut cells = vec![Cell::new("#").bg(tailwind::LIME.c300).fg(tailwind::BLACK)]; - fields.iter().for_each(|f| { + record_batch.schema().fields().iter().for_each(|f| { let cell = Cell::new(f.name().to_string()) .bg(tailwind::LIME.c300) .fg(tailwind::BLACK); diff --git a/src/ui/tabs/explore.rs b/src/ui/tabs/explore.rs index eb98e1a..5eb54a0 100644 --- a/src/ui/tabs/explore.rs +++ b/src/ui/tabs/explore.rs @@ -15,8 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::borrow::BorrowMut; - use ratatui::{ buffer::Buffer, layout::{Alignment, Constraint, Direction, Layout, Rect}, @@ -45,8 +43,8 @@ pub fn render_sql_editor(area: Rect, buf: &mut Buffer, app: &App) { pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) { let block = Block::default().title(" Results ").borders(Borders::ALL); if let Some(q) = app.state.explore_tab.query() { - if let Some(s) = app.state.explore_tab.query_results_state() { - if let Some(r) = q.results() { + if let Some(r) = q.results() { + if let Some(s) = app.state.explore_tab.query_results_state() { let block = block .title_bottom(format!( " {} rows in {}ms", @@ -60,12 +58,12 @@ pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) { let mut s = s.borrow_mut(); StatefulWidget::render(table, area, buf, &mut s); } + } else if let Some(e) = q.error() { + let row = Row::new(vec![e.to_string()]); + let widths = vec![Constraint::Percentage(100)]; + let table = Table::new(vec![row], widths).block(block); + Widget::render(table, area, buf); } - } else if let Some(e) = app.state.explore_tab.query_error() { - let row = Row::new(vec![e.to_string()]); - let widths = vec![Constraint::Percentage(100)]; - let table = Table::new(vec![row], widths).block(block); - Widget::render(table, area, buf); } else { let row = Row::new(vec!["Run a query to generate results"]); let widths = vec![Constraint::Percentage(100)]; From 4e0025b076081c2648a02f7b5aca6428c4f5c6d8 Mon Sep 17 00:00:00 2001 From: Matthew Turner Date: Sun, 25 Aug 2024 14:38:38 -0400 Subject: [PATCH 17/17] Clippy --- src/app/handlers/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/handlers/mod.rs b/src/app/handlers/mod.rs index cfbf71b..41b9e47 100644 --- a/src/app/handlers/mod.rs +++ b/src/app/handlers/mod.rs @@ -246,7 +246,7 @@ pub fn app_event_handler(app: &mut App, event: AppEvent) -> Result<()> { let now = std::time::Instant::now(); trace!("Tui::Event: {:?}", event); if let AppEvent::ExecuteDDL(ddl) = event { - let queries: Vec = ddl.split(";").map(|s| s.to_string()).collect(); + let queries: Vec = ddl.split(';').map(|s| s.to_string()).collect(); queries.into_iter().for_each(|q| { let ctx = app.execution.session_ctx.clone(); tokio::spawn(async move {