datafusion_dft/tui/handlers/
sql.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::sync::Arc;
19
20use log::{error, info};
21use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
22
23use super::App;
24use crate::tui::{handlers::tab_navigation_handler, state::tabs::sql::SQLTabMode, AppEvent};
25
26pub fn normal_mode_handler(app: &mut App, key: KeyEvent) {
27    match key.code {
28        KeyCode::Char('q') => app.state.should_quit = true,
29        tab @ (KeyCode::Char('1')
30        | KeyCode::Char('2')
31        | KeyCode::Char('3')
32        | KeyCode::Char('4')
33        | KeyCode::Char('5')) => tab_navigation_handler(app, tab),
34        KeyCode::Char('c') => app.state.sql_tab.clear_editor(&app.state.config),
35        KeyCode::Char('e') => {
36            let editor = app.state.sql_tab.editor();
37            let lines = editor.lines();
38            let content = lines.join("");
39            let default = "Enter a query here.";
40            if content == default {
41                app.state.sql_tab.clear_placeholder();
42            }
43            app.state.sql_tab.edit();
44        }
45        KeyCode::Char('d') => app.state.sql_tab.set_mode(SQLTabMode::DDL),
46        KeyCode::Char('n') => app.state.sql_tab.set_mode(SQLTabMode::Normal),
47        KeyCode::Char('s') => {
48            if *app.state.sql_tab.mode() == SQLTabMode::DDL {
49                let textarea = app.state.sql_tab.active_editor_cloned();
50                let ddl = textarea.lines().join("\n");
51                app.execution.save_ddl(ddl)
52            }
53        }
54        KeyCode::Down => {
55            if let Some(s) = app.state.sql_tab.query_results_state() {
56                info!("Select next");
57                let mut s = s.borrow_mut();
58                s.select_next();
59            }
60        }
61        KeyCode::Up => {
62            if let Some(s) = app.state.sql_tab.query_results_state() {
63                info!("Select previous");
64                let mut s = s.borrow_mut();
65                s.select_previous();
66            }
67        }
68
69        KeyCode::Enter => match app.state.sql_tab.mode() {
70            SQLTabMode::Normal => {
71                let sql = app.state.sql_tab.sql();
72                info!("Running query: {}", sql);
73                let _event_tx = app.event_tx().clone();
74                let execution = Arc::clone(&app.execution);
75                let sqls: Vec<String> = sql.split(';').map(|s| s.to_string()).collect();
76                let handle = tokio::spawn(execution.run_sqls(sqls, _event_tx));
77                app.state.sql_tab.set_execution_task(handle);
78            }
79            SQLTabMode::DDL => {
80                let _event_tx = app.event_tx().clone();
81                // TODO: Probably want this to load from Editor instead of the file so that
82                // it is the latest content.
83                let ddl = app.execution.load_ddl().unwrap_or_default();
84                if let Err(e) = _event_tx.send(AppEvent::ExecuteDDL(ddl)) {
85                    error!("Error sending ExecuteDDL event: {:?}", e);
86                }
87            }
88        },
89        KeyCode::Right => {
90            let _event_tx = app.event_tx().clone();
91            if let (Some(p), c) = (
92                app.state().sql_tab.current_page(),
93                app.state().sql_tab.batches_count(),
94            ) {
95                // We don't need to fetch the next batch if moving forward a page and we're not
96                // on the last page since we would have already fetched it.
97                if p < c - 1 {
98                    app.state.sql_tab.next_page();
99                    app.state.sql_tab.refresh_query_results_state();
100                    return;
101                }
102            }
103            if let Some(p) = app.state.history_tab.history().last() {
104                let execution = Arc::clone(&app.execution);
105                let sql = p.sql().clone();
106                tokio::spawn(async move {
107                    execution.next_batch(sql, _event_tx).await;
108                });
109            }
110        }
111        KeyCode::Left => {
112            app.state.sql_tab.previous_page();
113            app.state.sql_tab.refresh_query_results_state();
114        }
115        _ => {}
116    }
117}
118
119pub fn editable_handler(app: &mut App, key: KeyEvent) {
120    match (key.code, key.modifiers) {
121        (KeyCode::Left, KeyModifiers::ALT) => app.state.sql_tab.previous_word(),
122        (KeyCode::Right, KeyModifiers::ALT) => app.state.sql_tab.next_word(),
123        (KeyCode::Backspace, KeyModifiers::ALT) => app.state.sql_tab.delete_word(),
124        (KeyCode::Esc, _) => app.state.sql_tab.exit_edit(),
125        (KeyCode::Enter, KeyModifiers::ALT) => {
126            match app.state.sql_tab.mode() {
127                // TODO: Encapsulate this logic
128                SQLTabMode::Normal => {
129                    let sql = app.state.sql_tab.sql();
130                    info!("Running query: {}", sql);
131                    let _event_tx = app.event_tx().clone();
132                    let execution = Arc::clone(&app.execution);
133                    let sqls: Vec<String> = sql.split(';').map(|s| s.to_string()).collect();
134                    let handle = tokio::spawn(execution.run_sqls(sqls, _event_tx));
135                    app.state.sql_tab.set_execution_task(handle);
136                }
137                SQLTabMode::DDL => {
138                    let _event_tx = app.event_tx().clone();
139                    // TODO: Probably want this to load from Editor instead of the file so that
140                    // it is the latest content.
141                    let ddl = app.execution.load_ddl().unwrap_or_default();
142                    if let Err(e) = _event_tx.send(AppEvent::ExecuteDDL(ddl)) {
143                        error!("Error sending ExecuteDDL event: {:?}", e);
144                    }
145                }
146            }
147        }
148        _ => app.state.sql_tab.update_editor_content(key),
149    }
150}
151
152pub fn app_event_handler(app: &mut App, event: AppEvent) {
153    match event {
154        AppEvent::Key(key) => match app.state.sql_tab.editable() {
155            true => editable_handler(app, key),
156            false => normal_mode_handler(app, key),
157        },
158        AppEvent::Error => {}
159        _ => {}
160    };
161}