Skip to content

Commit

Permalink
feat: add execute query
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Ionov committed Jul 20, 2023
1 parent a799908 commit e1a2915
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 54 deletions.
8 changes: 8 additions & 0 deletions src-tauri/src/database/connections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,14 @@ impl ConnectedConnection {
// ConnectionPool::Sqlite(_pool) => todo!(),
}
}

pub async fn execute_query(&self, q: String) -> Result<Value> {
match &self.pool {
ConnectionPool::Mysql(pool) => engine::mysql::query::execute_query(pool, q),
// ConnectionPool::Postgres(_pool) => todo!(),
// ConnectionPool::Sqlite(_pool) => todo!(),
}
}
}

pub fn add_connection(db: &Connection, conn: &ConnectionConfig) -> Result<()> {
Expand Down
22 changes: 20 additions & 2 deletions src-tauri/src/database/engine/mysql/query.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use mysql::prelude::Queryable;
use mysql::{PooledConn, Row};
use mysql::{PooledConn, Row, Pool};
use serde_json::json;

use super::utils::convert_value;
Expand All @@ -17,6 +17,24 @@ pub fn raw_query(mut conn: PooledConn, query: String) -> Result<serde_json::Valu
}
result.push(object);
}
let result = json!({ "result ": result });
let result = json!({ "result": result });
return Ok(result);
}

pub fn execute_query(pool: &Pool, query: String) -> Result<serde_json::Value> {
let mut conn = pool.get_conn()?;
let rows: Vec<Row> = conn.query(&query)?;
let mut result = Vec::new();
for row in rows {
let mut object = json!({});
for column in row.columns_ref() {
let column_value = &row[column.name_str().as_ref()];
let value = convert_value(column_value);
object[column.name_str().as_ref()] = value;
}
result.push(object);
}
let result = json!({ "result": result });
return Ok(result);
}

25 changes: 12 additions & 13 deletions src-tauri/src/handlers/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,42 @@ use crate::{
state::ServiceAccess,
utils::error::CommandResult,
};
use log::info;
use serde_json::Value;
use tauri::{command, AppHandle};

#[command]
pub fn execute_query(_app_handle: AppHandle, query: String) -> CommandResult<()> {
info!("execute_query: {}", query);
println!("{}", sql_lexer::sanitize_string(query.to_string()));
Ok(())
pub async fn execute_query(app_handle: AppHandle, conn_id: String, query: String) -> CommandResult<Value> {
let connection = app_handle.acquire_connection(conn_id);
let result = connection.execute_query(query).await?;
Ok(result)
}

#[command]
pub async fn get_columns(app_handle: AppHandle, conn_id: String) -> CommandResult<Value> {
let connection = app_handle.acquire_connection(conn_id);
let stats = connection.get_columns().await?;
Ok(stats)
let result = connection.get_columns().await?;
Ok(result)
}

#[command]
pub async fn get_constraints(app_handle: AppHandle, conn_id: String) -> CommandResult<Value> {
let connection = app_handle.acquire_connection(conn_id);
let stats = connection.get_constraints().await?;
Ok(stats)
let result = connection.get_constraints().await?;
Ok(result)
}

#[command]
pub async fn get_triggers(app_handle: AppHandle, conn_id: String) -> CommandResult<Value> {
let connection = app_handle.acquire_connection(conn_id);
let stats = connection.get_triggers().await?;
Ok(stats)
let result = connection.get_triggers().await?;
Ok(result)
}

#[command]
pub async fn get_functions(app_handle: AppHandle, conn_id: String) -> CommandResult<Value> {
let connection = app_handle.acquire_connection(conn_id);
let stats = connection.get_functions().await?;
Ok(stats)
let result = connection.get_functions().await?;
Ok(result)
}

#[command]
Expand Down
11 changes: 2 additions & 9 deletions src/components/Screens/Console/Content/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AddIcon, CloseIcon } from "components/UI/Icons";
import { t } from "utils/i18n";
import { QueryTab } from "./QueryTab/QueryTab";
import { TableStructureTab } from "./TableStructureTab";
import { ContentComponent, ContentTab, ContentTabData, NEW_QUERY_TAB } from "services/ConnectionTabs";
import { ContentComponent, NEW_QUERY_TAB } from "services/ConnectionTabs";

export const Content = () => {
const { connectionsService: { contentStore, setContentStore } } = useAppSelector()
Expand All @@ -14,13 +14,6 @@ export const Content = () => {
setContentStore('idx', contentStore.tabs.length - 1)
}

const updateQueryText = async (query: string) => {
setContentStore('tabs', contentStore.tabs.map((t, idx) => idx === contentStore.idx ? {
...t,
data: { ...t.data, query }
} as ContentTab<'QueryTab'> : t))
}

const closeTab = async (idx: number) => {
if (!idx) return;
setContentStore('tabs', contentStore.tabs.filter((_t, i) => i !== idx));
Expand Down Expand Up @@ -48,7 +41,7 @@ export const Content = () => {
<div class="flex-1">
<Switch>
<Match when={contentStore.tabs[contentStore.idx]?.key === ContentComponent.QueryTab}>
<QueryTab query={(contentStore.tabs[contentStore.idx]?.data as ContentTabData['QueryTab']).query} updateQueryText={updateQueryText} />
<QueryTab />
</Match>
<Match when={contentStore.tabs[contentStore.idx]?.key === ContentComponent.TableStructureTab}>
<TableStructureTab />
Expand Down
4 changes: 2 additions & 2 deletions src/components/Screens/Console/Content/QueryTab/QueryTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Split from "split.js"
import { QueryTextArea } from "./QueryTextArea"
import { ResultsArea } from "./ResultsArea"

export const QueryTab = (props: { query: string, updateQueryText: (s: string) => void }) => {
export const QueryTab = () => {

createEffect(() => {
const q = Split(['#query', '#results'], {
Expand All @@ -20,7 +20,7 @@ export const QueryTab = (props: { query: string, updateQueryText: (s: string) =>
return (
<div class="flex flex-col h-full">
<div id="query" class="flex">
<QueryTextArea query={props.query} updateQueryText={props.updateQueryText} />
<QueryTextArea />
</div>
<div id="results" class="bg-base-200 p-3">
<ResultsArea />
Expand Down
30 changes: 25 additions & 5 deletions src/components/Screens/Console/Content/QueryTab/QueryTextArea.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
import { createCodeMirror, createEditorControlledValue } from "solid-codemirror";
import { createSignal } from "solid-js";
import { createEffect, createSignal } from "solid-js";
import { lineNumbers } from '@codemirror/view';
import { sql } from '@codemirror/lang-sql'
import { dracula } from '@uiw/codemirror-theme-dracula'
import { format } from 'sql-formatter';
import { t } from "i18next";
import { invoke } from '@tauri-apps/api';
import { EditIcon, FireIcon } from "components/UI/Icons";
import { useAppSelector } from "services/Context";
import { ContentTab, ContentTabData } from "services/ConnectionTabs";
import { QueryResult } from "interfaces";

export const QueryTextArea = () => {
const { connectionsService: { setActiveContentQueryTabData, contentStore,
setContentStore, getActiveConnection, getActiveContentTab } } =
useAppSelector()

const updateQueryText = async (query: string) => {
setContentStore('tabs', contentStore.tabs.map((t, idx) => idx === contentStore.idx ? {
...t,
data: { ...t.data, query }
} as ContentTab<'QueryTab'> : t))
}

export const QueryTextArea = (props: { query: string, updateQueryText: (s: string) => void }) => {
const onInput = (q: string) => {
props.updateQueryText(q)
updateQueryText(q)
setCode(q)
}

const [code, setCode] = createSignal(props.query);
const [code, setCode] = createSignal('');
const { ref, editorView, createExtension } = createCodeMirror({ onValueChange: onInput });
createEditorControlledValue(editorView, code);
createExtension(() => lineNumbers());
Expand All @@ -27,9 +41,15 @@ export const QueryTextArea = (props: { query: string, updateQueryText: (s: strin
}

const onExecute = async () => {
await invoke('execute_query', { query: code() })
const activeConnection = getActiveConnection()
const { result } = await invoke<QueryResult>('execute_query', { connId: activeConnection.id, query: code() })
setActiveContentQueryTabData({ data: { query: code, results: result } })
}

createEffect(() => {
setCode((getActiveContentTab()?.data as ContentTabData['QueryTab']).query ?? '')
})

return (
<div class="flex-1 flex flex-col">
<div class="w-full p-1 bg-base-100">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { For } from "solid-js"
import { useAppSelector } from "services/Context"
import { createEffect, For } from "solid-js"

const rows = [
{ id: 1, name: 'Snow', calories: 159, fat: 6.0, carbs: 24, protein: 4.0 },
Expand All @@ -14,6 +15,10 @@ const rows = [
{ id: 11, name: 'Lannister', calories: 237, fat: 9.0, carbs: 37, protein: 4.3 },
]
export const ResultsArea = () => {
const { connectionsService: { getActiveContentTab } } = useAppSelector()
createEffect(() => {
console.log(getActiveContentTab().data)
})
return (
<div class="p-3">
<div class="text-xs font-bold text-primary">Results</div>
Expand Down
44 changes: 24 additions & 20 deletions src/components/Screens/Console/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Collapse } from "components/UI"
import { useAppSelector } from "services/Context"
import { createSignal, For, onMount } from "solid-js"
import { createStore } from "solid-js/store"
Expand All @@ -10,18 +11,24 @@ type Table = {
}[]
}

// TODO: change to icons
const TypeToEmoji = {
int: '💯',
char: '🅰️',
json: '📄',
decimal: '💰',
enum: '📚',
varbinary: '📟',
int: '#',
char: 'C',
json: 'J',
decimal: '%',
enum: '@',
varbinary: '*',
datetime: 'D',
tinyint: 'T',
bigint: 'B',
varchar: 'V',
text: 'T',
}

export const Sidebar = () => {
const { connectionsService: { connectionStore } } = useAppSelector()
const getSchema = () => connectionStore.tabs[connectionStore.idx - 1]?.schema
const { connectionsService: { getActiveConnection } } = useAppSelector()
const getSchema = () => getActiveConnection().schema
const [displayedSchema, setDisplayedSchema] = createSignal('')
const [tables, setTables] = createStore<Table[]>([])

Expand All @@ -48,11 +55,11 @@ export const Sidebar = () => {

const emojiType = (t: string) => {
const type = t.split('(')[0]
return TypeToEmoji[type as keyof typeof TypeToEmoji] || '📄'
return TypeToEmoji[type as keyof typeof TypeToEmoji] || ''
}

return (
<div class="p-2 bg-base-200 h-full rounded-tr-lg">
<div class="p-2 bg-base-200 h-full rounded-tr-lg overflow-y-scroll">
<div class="pb-2 rounded-md">
<select value={displayedSchema()} onChange={(e) => select(e.currentTarget.value)} class="select select-accent select-bordered select-xs w-full">
<For each={Object.keys(getSchema())}>
Expand All @@ -63,22 +70,19 @@ export const Sidebar = () => {
<div class="text-xs font-bold text-primary">Tables</div>
<For each={tables}>
{(table) => (
<div class="flex items-center">
<div tabindex="1" class="collapse rounded-sm bg-base-300 mb-1 px-2">
<div class="collapse rounded-sm text-sm font-small">
{table.name}
</div>
<div class="collapse-content pl-1 pr-0">
<div class="rounded-sm bg-base-300 mb-1 px-2">
<Collapse title={table.name}>
<div class="overflow-x-scroll w-full">
<For each={table.columns}>
{(column) => (
<div class="flex justify-between items-center">
<span class="text-sm"><span class="px-1">{emojiType(column.props.COLUMN_TYPE)}</span>{column.name}</span>
<span class="text-xs text-accentt">{column.props.COLUMN_TYPE}</span>
<div class="flex justify-between items-center w-full">
<span class="text-sm mr-2"><span class="px-1">{emojiType(column.props.COLUMN_TYPE)}</span>{column.name}</span>
<span class="text-xs text-accent ml-2">{column.props.COLUMN_TYPE}</span>
</div>
)}
</For>
</div>
</div>
</Collapse>
</div>
)}
</For>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api';
import { ConnectionConfig } from 'interfaces';
import { ConnectionConfig, QueryResult } from 'interfaces';
import { useAppSelector } from 'services/Context';

const columnsToSchema = (columns: Record<string, any>[]) => {
Expand All @@ -24,7 +24,7 @@ export const ActionsMenu = (props: { connection: ConnectionConfig }) => {
const onConnect = async () => {
try {
await invoke('init_connection', { config: props.connection })
const { result } = await invoke('get_columns', { connId: props.connection.id }) as { result: Record<string, any>[] }
const { result } = await invoke<QueryResult>('get_columns', { connId: props.connection.id });
const schema = columnsToSchema(result)
await addTab({
id: props.connection.id,
Expand Down
13 changes: 13 additions & 0 deletions src/components/UI/Collapse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createSignal, JSXElement } from "solid-js"

export const Collapse = (props: { title: string, children: JSXElement }) => {
const [open, setOpen] = createSignal(false)
return (
<div class="w-full">
<div onClick={() => setOpen(!open())} class="collapse rounded-sm text-sm font-small pointer">
{props.title}
</div>
{open() && props.children}
</div>
)
}
1 change: 1 addition & 0 deletions src/components/UI/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './Alert'
export * from './Alerts'
export * from './Checkbox'
export * from './Collapse'
export * from './ColorCircle'
export * from './FileInput'
export * from './Label'
Expand Down
1 change: 1 addition & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,4 @@ export type DbSchema = {
}
}

export type QueryResult = { result: Record<string, any>[], message?: string }
19 changes: 19 additions & 0 deletions src/services/ConnectionTabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,22 @@ export const ConnectionTabsService = () => {
await store.clear();
}

const getActiveConnection = () => {
return connectionStore.tabs[connectionStore.idx - 1]
}

const getActiveContentTab = () => {
return contentStore.tabs[contentStore.idx]
}

const setActiveContentQueryTabData = (data: ContentTabData['QueryTab']) => {
const tab = getActiveContentTab()
if (!tab) return;
const tabs = contentStore.tabs;
tabs[contentStore.idx].data = data;
setContentStore('tabs', tabs);
}

return {
connectionStore,
setConnectionStore,
Expand All @@ -113,6 +129,9 @@ export const ConnectionTabsService = () => {
addTab,
removeTab,
clearStore,
getActiveConnection,
getActiveContentTab,
setActiveContentQueryTabData,
}
}

0 comments on commit e1a2915

Please sign in to comment.