diff --git a/src-tauri/src/database/connections.rs b/src-tauri/src/database/connections.rs index 3ab511d..e598348 100644 --- a/src-tauri/src/database/connections.rs +++ b/src-tauri/src/database/connections.rs @@ -168,6 +168,14 @@ impl ConnectionConfig { } } +#[derive(Debug, Serialize, Deserialize)] +pub struct ResultSet { + pub affected_rows: u64, + pub warnings: u16, + pub info: String, + pub rows: Vec, +} + impl ConnectedConnection { pub async fn new(config: ConnectionConfig) -> Result { match &config.scheme { @@ -245,7 +253,7 @@ impl ConnectedConnection { } } - pub async fn execute_query(&self, q: &str) -> Result { + pub async fn execute_query(&self, q: &str) -> Result { match &self.pool { ConnectionPool::Mysql(pool) => engine::mysql::query::execute_query(pool, q), // ConnectionPool::Postgres(_pool) => todo!(), diff --git a/src-tauri/src/database/engine/mysql/query.rs b/src-tauri/src/database/engine/mysql/query.rs index 479ef8d..ad36502 100644 --- a/src-tauri/src/database/engine/mysql/query.rs +++ b/src-tauri/src/database/engine/mysql/query.rs @@ -1,17 +1,11 @@ use anyhow::Result; use mysql::prelude::Queryable; use mysql::{from_row, Pool, PooledConn, Row}; -use serde::{Deserialize, Serialize}; use serde_json::json; +use crate::database::connections::ResultSet; + use super::utils::convert_value; -#[derive(Debug, Serialize, Deserialize)] -struct ResultSet { - affected_rows: u64, - warnings: u16, - info: String, - rows: Vec, -} fn row_to_object(row: Row) -> serde_json::Value { let mut object = json!({}); @@ -34,10 +28,9 @@ pub fn raw_query(mut conn: PooledConn, query: String) -> Result Result { +pub fn execute_query(pool: &Pool, query: &str) -> Result { let mut conn = pool.get_conn()?; let mut results = conn.query_iter(query)?; - let mut sets: Vec = vec![]; while let Some(result_set) = results.iter() { let affected_rows = result_set.affected_rows(); let warnings = result_set.warnings(); @@ -52,9 +45,13 @@ pub fn execute_query(pool: &Pool, query: &str) -> Result { info: info.to_string(), rows, }; - sets.push(set); + return Ok(set); } - let result = json!(sets); - return Ok(result); + return Ok(ResultSet { + affected_rows: 0, + warnings: 0, + info: "".to_string(), + rows: Vec::new(), + }); } diff --git a/src-tauri/src/handlers/queries.rs b/src-tauri/src/handlers/queries.rs index fd696e8..7a07ccf 100644 --- a/src-tauri/src/handlers/queries.rs +++ b/src-tauri/src/handlers/queries.rs @@ -8,7 +8,7 @@ use crate::{ }; use anyhow::anyhow; use serde::{Deserialize, Serialize}; -use serde_json::Value; +use serde_json::{Value, json}; use sqlparser::{dialect::dialect_from_str, parser::Parser}; use tauri::{command, AppHandle, State}; use tracing::info; @@ -91,7 +91,7 @@ pub async fn execute_query( ) -> CommandResult { let connection = app_handle.acquire_connection(conn_id); let result = connection.execute_query(&query).await?; - Ok(result) + Ok(json!({ "result": result })) } #[command] diff --git a/src-tauri/src/queues/query.rs b/src-tauri/src/queues/query.rs index 9c4f9c7..4d44f09 100644 --- a/src-tauri/src/queues/query.rs +++ b/src-tauri/src/queues/query.rs @@ -72,6 +72,7 @@ pub struct QueryTaskEnqueueResult { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct QueryTaskResult { + pub conn_id: String, pub status: QueryTaskStatus, pub query: String, pub id: String, @@ -88,11 +89,12 @@ pub async fn async_process_model( while let Some(input) = input_rx.recv().await { let task = input; match task.conn.execute_query(&task.query).await { - Ok(content) => { - match write_query(&task.id, &content.to_string()) { + Ok(result_set) => { + match write_query(&task.id, result_set) { Ok(path) => { output_tx .send(QueryTaskResult { + conn_id: task.conn.config.id.to_string(), status: QueryTaskStatus::Completed, query: task.query, id: task.id, @@ -106,6 +108,7 @@ pub async fn async_process_model( Err(e) => { output_tx .send(QueryTaskResult { + conn_id: task.conn.config.id.to_string(), status: QueryTaskStatus::Error, query: task.query, id: task.id, @@ -121,6 +124,7 @@ pub async fn async_process_model( Err(e) => { output_tx .send(QueryTaskResult { + conn_id: task.conn.config.id.to_string(), status: QueryTaskStatus::Error, query: task.query, id: task.id, diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index 40f0bf2..459b668 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -1,7 +1,8 @@ use std::{fs, path::PathBuf}; -use crate::database::database::create_app_db; +use crate::database::{database::create_app_db, connections::ResultSet}; use anyhow::Result; +use serde_json::json; use tauri::api::dir::with_temp_dir; use tracing::{debug, error}; @@ -45,9 +46,19 @@ pub fn write_file(path: &PathBuf, content: &str) -> Result<()> { Ok(()) } -pub fn write_query(id: &str, content: &str) -> Result { +pub fn write_query(id: &str, result_set: ResultSet) -> Result { + let rows = json!(result_set.rows).to_string(); + let metadata = json!({ + "rows": result_set.rows.len(), + "affected_rows": result_set.affected_rows, + "warnings": result_set.warnings, + "info": result_set.info, + }) + .to_string(); let tmp_dir = get_tmp_dir()?; - let path = tmp_dir + "/" + id; - write_file(&PathBuf::from(&path), content)?; + let path = tmp_dir.clone() + "/" + id; + let metadata_path = tmp_dir + "/" + id + ".metadata"; + write_file(&PathBuf::from(&path), &rows)?; + write_file(&PathBuf::from(&metadata_path), &metadata)?; Ok(path) } diff --git a/src/App.tsx b/src/App.tsx index abdeb6d..2517533 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,11 +6,19 @@ import { CommandPaletteContext } from "components/CommandPalette/CommandPaletteC import { Loader } from "components/UI"; import { createEffect, createSignal, onMount } from "solid-js"; import { useAppSelector } from "services/Context"; +import { listen } from "@tauri-apps/api/event"; +import { Events, QueryTaskResult } from "interfaces"; +import { log } from "utils/utils"; function App() { const [loading, setLoading] = createSignal(true); const { - connections: { restoreConnectionStore }, + connections: { + restoreConnectionStore, + getConnection, + contentStore: { idx }, + queryIdx, + }, app: { restoreAppStore }, } = useAppSelector(); @@ -20,6 +28,25 @@ function App() { }, 500); }); + const getQueryResults = () => { }; + + const compareAndAssign = (event: QueryTaskResult) => { + if ( + getConnection().id === event.conn_id && + idx === event.tab_idx && + queryIdx() === event.query_idx + ) { + // TODO: should get 0 page of results + } + }; + + onMount(async () => { + await listen(Events.QueryFinished, (event) => { + log(event); + compareAndAssign(event.payload); + }); + }); + onMount(async () => { const theme = localStorage.getItem("theme") || "dark"; document.documentElement.dataset.theme = theme; diff --git a/src/components/CommandPalette/actions.ts b/src/components/CommandPalette/actions.ts index 4bd6f9e..e1c97ec 100644 --- a/src/components/CommandPalette/actions.ts +++ b/src/components/CommandPalette/actions.ts @@ -3,18 +3,18 @@ import { t } from "utils/i18n"; const focusOn = defineAction({ id: "focus_on", - title: t("command_palette.focus_on"), + title: t('command_palette.focus_on'), }); const focusQueryTextArea = defineAction({ id: "focus_query_text_area", - title: t("command_palette.focus_query_text_area"), + title: t('command_palette.focus_query_text_area'), parentActionId: focusOn.id, /* Condition for allowing action */ shortcut: "$mod+l", // $mod = Command on Mac & Control on Windows. run: () => { // document.dispatchEvent( - // new KeyboardEvent("keydown", { + // new KeyboardEvent('keydown', { // code: "KeyL", // put everything you need in this object. // ctrlKey: true, // if you aren't going to use them. // }) diff --git a/src/components/Screens/Console/Content/Content.tsx b/src/components/Screens/Console/Content/Content.tsx index 733c8fa..23b0244 100644 --- a/src/components/Screens/Console/Content/Content.tsx +++ b/src/components/Screens/Console/Content/Content.tsx @@ -30,7 +30,7 @@ export const Content = () => { tabindex={0} onClick={() => setContentIdx(idx())} > - {t("components.console.query")} #{idx() + 1} + {t('console.query')} #{idx() + 1} 0}> - {"#" + (props.idx() + 1)} - - {t("components.console.out_of") + - getContentData("Query").result_sets.length + - ". "} - - {result_set.info + ""} - - - )} - -
diff --git a/src/components/Screens/Console/Content/QueryTab/ResultesTable.tsx b/src/components/Screens/Console/Content/QueryTab/ResultesTable.tsx index 3603314..df30147 100644 --- a/src/components/Screens/Console/Content/QueryTab/ResultesTable.tsx +++ b/src/components/Screens/Console/Content/QueryTab/ResultesTable.tsx @@ -1,4 +1,3 @@ -import { Row } from "interfaces"; import { createEffect, createSignal } from "solid-js"; import { TabulatorFull as Tabulator } from "tabulator-tables"; import { dracula } from "@uiw/codemirror-theme-dracula"; @@ -9,6 +8,8 @@ import { createCodeMirror, createEditorControlledValue, } from "solid-codemirror"; +import { Pagination } from "./components/Pagination"; +import { useAppSelector } from "services/Context"; const parseObjRecursive = (obj: any): Record => { if (typeof obj === "object") { @@ -30,12 +31,15 @@ const parseObjRecursive = (obj: any): Record => { } }; -export const ResultsTable = (props: { rows: Row[] }) => { +export const ResultsTable = () => { + const { + connections: { queryIdx }, + } = useAppSelector(); const [code, setCode] = createSignal(""); const { ref, editorView, createExtension } = createCodeMirror({ onValueChange: setCode, }); - const [paginationSize] = createSignal(20); + const [table, setTable] = createSignal(null); createEditorControlledValue(editorView, code); createExtension(() => search()); createExtension(dracula); @@ -46,80 +50,87 @@ export const ResultsTable = (props: { rows: Row[] }) => { createExtension(lineWrapping); createEffect(() => { - let columns: { title: string; field: string; resizeable: boolean }[] = []; - if (props.rows.length) { - columns = Object.keys(props.rows[0]).map((k) => ({ - title: k, - field: k, - resizeable: true, - // editor: "input" as const, // this will make the whole table navigable - })); - } - - new Tabulator("#results-table", { - data: props.rows, - columns, - columnDefaults: { - title: "", - width: 350, - }, - layout: "fitDataStretch", - autoResize: true, - clipboard: true, - pagination: true, - paginationSize: paginationSize(), - height: "100%", - paginationCounter: "rows", - debugInvalidOptions: false, - rowContextMenu: [ - { - label: "Show row in JSON", - action: function(_e, row) { - // @ts-ignore - const data = parseObjRecursive(row._row.data); - setCode(JSON.stringify(data, null, 4)); - // @ts-ignore - document.getElementById("my_modal_1").showModal(); - }, - }, - { - label: "Copy row to clipboard", - action: function(_e, row) { - // @ts-ignore - navigator.clipboard.writeText(JSON.stringify(row._row.data)); - }, - }, - { - separator: true, - }, - ], - // only relevant if editor is set to "input" - // keybindings: { - // //@ts-ignore - // navUp: ["ctrl + shift + k", 38], - // //@ts-ignore - // navDown: ["ctrl + shift + j", 40], - // //@ts-ignore - // navLeft: ["ctrl + shift + h", 37], - // //@ts-ignore - // navRight: ["ctrl + shift + l", 39], - // }, - }); + console.log(queryIdx); + // let columns: { title: string; field: string; resizeable: boolean }[] = []; + // if (length) { + // columns = Object.keys(props.rows[0]).map((k) => ({ + // title: k, + // field: k, + // resizeable: true, + // // editor: "input" as const, // this will make the whole table navigable + // })); + // } + // + // const _table = new Tabulator("#results-table", { + // data: props.rows, + // columns, + // columnDefaults: { + // title: "", + // width: 350, + // }, + // layout: "fitDataStretch", + // autoResize: true, + // clipboard: true, + // pagination: false, + // paginationSize: paginationSize(), + // height: "100%", + // paginationCounter: "rows", + // debugInvalidOptions: false, + // rowContextMenu: [ + // { + // label: "Show row in JSON", + // action: function(_e, row) { + // // @ts-ignore + // const data = parseObjRecursive(row._row.data); + // setCode(JSON.stringify(data, null, 4)); + // // @ts-ignore + // document.getElementById("my_modal_1").showModal(); + // }, + // }, + // { + // label: "Copy row to clipboard", + // action: function(_e, row) { + // // @ts-ignore + // navigator.clipboard.writeText(JSON.stringify(row._row.data)); + // }, + // }, + // { + // separator: true, + // }, + // ], + // // only relevant if editor is set to "input" + // // keybindings: { + // // //@ts-ignore + // // navUp: ["ctrl + shift + k", 38], + // // //@ts-ignore + // // navDown: ["ctrl + shift + j", 40], + // // //@ts-ignore + // // navLeft: ["ctrl + shift + h", 37], + // // //@ts-ignore + // // navRight: ["ctrl + shift + l", 39], + // // }, + // }); + // setTable(_table); }); return ( - <> +
-
- + +
+
+
+
+
+
); }; diff --git a/src/components/Screens/Console/Content/QueryTab/ActionRowButton.tsx b/src/components/Screens/Console/Content/QueryTab/components/ActionRowButton.tsx similarity index 100% rename from src/components/Screens/Console/Content/QueryTab/ActionRowButton.tsx rename to src/components/Screens/Console/Content/QueryTab/components/ActionRowButton.tsx diff --git a/src/components/Screens/Console/Content/QueryTab/components/Pagination.tsx b/src/components/Screens/Console/Content/QueryTab/components/Pagination.tsx new file mode 100644 index 0000000..dff77a0 --- /dev/null +++ b/src/components/Screens/Console/Content/QueryTab/components/Pagination.tsx @@ -0,0 +1,55 @@ +import { TabulatorFull as Tabulator } from "tabulator-tables"; +import { Alert } from "components/UI"; +import { useAppSelector } from "services/Context"; +import { createShortcut } from "@solid-primitives/keyboard"; +import { ChevronLeft, ChevronRight } from "components/UI/Icons"; +import { t } from "utils/i18n"; + +type PaginationProps = { + table: Tabulator | null; +}; + +export const Pagination = (_props: PaginationProps) => { + const { + connections: { + selectNextQuery, + selectPrevQuery, + queryIdx + }, + } = useAppSelector(); + + createShortcut(["Control", "Shift", "N"], selectNextQuery); + createShortcut(["Control", "Shift", "P"], selectPrevQuery); + + return ( +
+
+
+ + + +
+
+ + Lorem ipsum dolor sit amet, qui minim labore adipisicing minim + +
+
+
+ + + + + +
+
+ ); +}; diff --git a/src/components/Screens/Console/Content/TableStructure/TableStructureTab.tsx b/src/components/Screens/Console/Content/TableStructure/TableStructureTab.tsx index 91f0b2c..a93b3ad 100644 --- a/src/components/Screens/Console/Content/TableStructure/TableStructureTab.tsx +++ b/src/components/Screens/Console/Content/TableStructure/TableStructureTab.tsx @@ -28,7 +28,7 @@ export const TableStructureTab = () => { classList={{ "tab-active": tab() === ta }} class="tab" > - {t(`components.table_structure_tab.${ta}`)} + {t(`table_structure_tab.${ta}`)} ))}
diff --git a/src/components/Screens/Console/Sidebar/Sidebar.tsx b/src/components/Screens/Console/Sidebar/Sidebar.tsx index ce36912..13dfd36 100644 --- a/src/components/Screens/Console/Sidebar/Sidebar.tsx +++ b/src/components/Screens/Console/Sidebar/Sidebar.tsx @@ -90,7 +90,7 @@ export const Sidebar = () => {
- {t("components.sidebar.tables")} + {t('sidebar.tables')}
{(table) => ( diff --git a/src/components/Screens/Console/Sidebar/TableColumnsCollapse.tsx b/src/components/Screens/Console/Sidebar/TableColumnsCollapse.tsx index f815064..3aa0dd4 100644 --- a/src/components/Screens/Console/Sidebar/TableColumnsCollapse.tsx +++ b/src/components/Screens/Console/Sidebar/TableColumnsCollapse.tsx @@ -63,7 +63,7 @@ export const TableColumnsCollapse = (props: { query, autoLimit: false, }); - notify(t("components.sidebar.table_was_truncated", { table }), "success"); + notify(t('sidebar.table_was_truncated', { table }), "success"); } catch (error) { notify(error); } @@ -73,13 +73,13 @@ export const TableColumnsCollapse = (props: {
show(e)}> addTableStructureTab(props.table)}> - {t("components.sidebar.show_table_structure")} + {t('sidebar.show_table_structure')} listData(props.table)}> - {t("components.sidebar.list_data")} + {t('sidebar.list_data')} truncateTable(props.table)}> - {t("components.sidebar.truncate_table")} + {t('sidebar.truncate_table')}

- {t("components.add_connection_form.title")} + {t('add_connection_form.title')}

@@ -184,7 +184,7 @@ const AddConnectionForm = (props: { ({ value: md, label: titleCase(md) }) )} @@ -226,14 +226,14 @@ const AddConnectionForm = (props: {