datafusion_dft/tui/ui/tabs/
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 ratatui::{
19    buffer::Buffer,
20    layout::{Alignment, Constraint, Direction, Layout, Rect},
21    style::{palette::tailwind, Style, Stylize},
22    text::Span,
23    widgets::{block::Title, Block, Borders, Paragraph, Row, StatefulWidget, Table, Widget, Wrap},
24};
25
26use crate::tui::App;
27use crate::tui::{state::tabs::sql::SQLTabMode, ui::convert::record_batches_to_table};
28
29pub fn render_sql_editor(area: Rect, buf: &mut Buffer, app: &App) {
30    let sql_tab = &app.state.sql_tab;
31    let border_color = if app.state.sql_tab.editor_editable() {
32        tailwind::ORANGE.c500
33    } else {
34        tailwind::WHITE
35    };
36    let title = if sql_tab.ddl_error() {
37        vec![
38            Span::from(" Editor ").fg(tailwind::WHITE),
39            Span::from("(DDL Error) ").red(),
40        ]
41    } else {
42        vec![Span::from(" Editor ").fg(tailwind::WHITE)]
43    };
44    let mode_text = format!(" {:?} ", sql_tab.mode());
45    let mode = Title::from(mode_text.as_str()).alignment(Alignment::Right);
46    let block = Block::default()
47        .title(title)
48        .title(mode)
49        .borders(Borders::ALL)
50        .fg(border_color);
51    let mut editor = app.state.sql_tab.active_editor_cloned();
52    editor.set_style(Style::default().fg(tailwind::WHITE));
53    editor.set_block(block);
54    editor.render(area, buf)
55}
56
57pub fn render_sql_results(area: Rect, buf: &mut Buffer, app: &App) {
58    // TODO: Change this to a match on state and batch
59    let sql_tab = &app.state.sql_tab;
60    match (
61        sql_tab.current_batch(),
62        sql_tab.current_page(),
63        sql_tab.query_results_state(),
64        sql_tab.execution_error(),
65    ) {
66        (Some(batch), Some(p), Some(s), None) => {
67            let block = Block::default()
68                .title(" Results ")
69                .borders(Borders::ALL)
70                .title(Title::from(format!(" Page {p} ")).alignment(Alignment::Right));
71            let batches = vec![batch];
72            let maybe_table = record_batches_to_table(&batches);
73
74            let block = block.title_bottom("Stats").fg(tailwind::ORANGE.c500);
75            match maybe_table {
76                Ok(table) => {
77                    let table = table
78                        .highlight_style(Style::default().bg(tailwind::WHITE).fg(tailwind::BLACK))
79                        .block(block);
80
81                    let mut s = s.borrow_mut();
82                    StatefulWidget::render(table, area, buf, &mut s);
83                }
84                Err(e) => {
85                    let row = Row::new(vec![e.to_string()]);
86                    let widths = vec![Constraint::Percentage(100)];
87                    let table = Table::new(vec![row], widths).block(block);
88                    Widget::render(table, area, buf);
89                }
90            }
91        }
92        (_, _, _, Some(e)) => {
93            let dur = e.duration().as_millis();
94            let block = Block::default()
95                .title(" Results ")
96                .borders(Borders::ALL)
97                .title(Title::from(" Page ").alignment(Alignment::Right))
98                .title_bottom(format!(" {}ms ", dur));
99            let p = Paragraph::new(e.error().to_string())
100                .block(block)
101                .wrap(Wrap { trim: true });
102            Widget::render(p, area, buf);
103        }
104        _ => {
105            let block = Block::default()
106                .title(" Results ")
107                .borders(Borders::ALL)
108                .title(Title::from(" Page ").alignment(Alignment::Right));
109            let row = Row::new(vec!["Run a query to generate results"]);
110            let widths = vec![Constraint::Percentage(100)];
111            let table = Table::new(vec![row], widths).block(block);
112            Widget::render(table, area, buf);
113        }
114    }
115}
116
117pub fn render_sql_help(area: Rect, buf: &mut Buffer, app: &App) {
118    let block = Block::default();
119
120    let help = match app.state.sql_tab.mode() {
121        SQLTabMode::Normal => {
122            if app.state.sql_tab.editor_editable() {
123                vec!["'Esc' to exit edit mode"]
124            } else {
125                vec![
126                    "'e' to edit",
127                    "'c' to clear editor",
128                    "'d' for DDL mode",
129                    "'q' to exit app",
130                    "'Enter' to run query",
131                ]
132            }
133        }
134        SQLTabMode::DDL => {
135            if app.state.sql_tab.editor_editable() {
136                vec!["'Esc' to exit edit mode"]
137            } else {
138                vec![
139                    "'e' to edit",
140                    "'c' to clear editor",
141                    "'n' for Normal mode",
142                    "'s' to save DDL",
143                    "'Enter' to run DDL",
144                ]
145            }
146        }
147    };
148
149    let help_text = help.join(" | ");
150    let p = Paragraph::new(help_text)
151        .block(block)
152        .alignment(Alignment::Center);
153    p.render(area, buf);
154}
155
156pub fn render_sql(area: Rect, buf: &mut Buffer, app: &App) {
157    let mode = app.state.sql_tab.mode();
158
159    match mode {
160        SQLTabMode::Normal => {
161            let constraints = vec![
162                Constraint::Fill(1),
163                Constraint::Fill(1),
164                Constraint::Length(1),
165            ];
166            let [editor_area, results_area, help_area] =
167                Layout::new(Direction::Vertical, constraints).areas(area);
168
169            render_sql_editor(editor_area, buf, app);
170            render_sql_results(results_area, buf, app);
171            render_sql_help(help_area, buf, app);
172        }
173        SQLTabMode::DDL => {
174            let constraints = vec![Constraint::Fill(1), Constraint::Length(1)];
175            let [editor_area, help_area] =
176                Layout::new(Direction::Vertical, constraints).areas(area);
177
178            render_sql_editor(editor_area, buf, app);
179            render_sql_help(help_area, buf, app);
180        }
181    };
182}