feat: execute selected sql (#13)

Co-authored-by: SuLe <sule@SuLedeMacBook-Air.local>
This commit is contained in:
SuLea-IT
2026-04-30 14:38:26 +08:00
committed by GitHub
co-authored by SuLe
parent 62ae308914
commit e3b8f57326
8 changed files with 87 additions and 17 deletions
+33 -12
View File
@@ -33,6 +33,7 @@ import { setLocale, currentLocale, type Locale } from "@/i18n";
import { getCurrentWindow, type Theme } from "@tauri-apps/api/window";
import { getCurrentWebview } from "@tauri-apps/api/webview";
import * as api from "@/lib/tauri";
import { resolveExecutableSql } from "@/lib/sqlExecutionTarget";
const { t } = useI18n();
const connectionStore = useConnectionStore();
@@ -44,6 +45,8 @@ const { message: toastMessage, visible: toastVisible, toast } = useToast();
const showConnectionDialog = ref(false);
const showHistory = ref(false);
const dangerSql = ref("");
const pendingDangerSql = ref("");
const selectedSql = ref("");
const showDangerDialog = ref(false);
const databaseOptions = ref<Record<string, string[]>>({});
const loadingDatabaseOptions = ref<Record<string, boolean>>({});
@@ -66,6 +69,16 @@ const activeTab = computed(() =>
queryStore.tabs.find((t) => t.id === queryStore.activeTabId)
);
const executableSql = computed(() => {
const tab = activeTab.value;
return tab ? resolveExecutableSql(tab.sql, selectedSql.value) : "";
});
watch(() => queryStore.activeTabId, () => {
selectedSql.value = "";
pendingDangerSql.value = "";
});
const activeConnection = computed(() => {
const tab = activeTab.value;
return tab ? connectionStore.getConfig(tab.connectionId) : undefined;
@@ -191,6 +204,10 @@ function onEditorUpdate(val: string) {
}
}
function onEditorSelectionChange(val: string) {
selectedSql.value = val;
}
function newQuery() {
if (!connectionStore.activeConnectionId) return;
const conn = connectionStore.connections.find(
@@ -222,24 +239,25 @@ function isDangerousSql(sql: string): boolean {
return DANGER_RE.test(stripSqlComments(sql));
}
function tryExecute() {
function tryExecute(sqlOverride?: string) {
const tab = activeTab.value;
if (!tab || !tab.sql.trim()) return;
if (isDangerousSql(tab.sql)) {
dangerSql.value = tab.sql;
const sql = sqlOverride ?? executableSql.value;
if (!tab || !sql.trim()) return;
if (isDangerousSql(sql)) {
dangerSql.value = sql;
pendingDangerSql.value = sql;
showDangerDialog.value = true;
} else {
doExecute();
doExecute(sql);
}
}
async function doExecute() {
async function doExecute(sql = executableSql.value) {
const tab = activeTab.value;
if (!tab) return;
if (!tab || !sql.trim()) return;
const connName = connectionStore.getConfig(tab.connectionId)?.name || "";
const sql = tab.sql;
const start = Date.now();
await queryStore.executeCurrentTab();
await queryStore.executeCurrentSql(sql);
const elapsed = Date.now() - start;
const success = !tab.result?.columns.includes("Error");
historyStore.add({
@@ -253,7 +271,9 @@ async function doExecute() {
}
function onDangerConfirm() {
doExecute();
const sql = pendingDangerSql.value || executableSql.value;
pendingDangerSql.value = "";
doExecute(sql);
}
function onHistoryRestore(sql: string) {
@@ -658,7 +678,8 @@ async function setupFileDrop() {
class="flex-1"
:model-value="activeTab.sql"
@update:model-value="onEditorUpdate"
@execute="tryExecute()"
@selection-change="onEditorSelectionChange"
@execute="tryExecute"
/>
</div>
</Pane>
@@ -670,7 +691,7 @@ async function setupFileDrop() {
@replace-sql="replaceActiveSql"
@append-sql="appendActiveSql"
/>
<DataGrid v-if="activeTab.result" :key="activeTab.id" class="flex-1 min-h-0" :result="activeTab.result" :sql="activeTab.sql" />
<DataGrid v-if="activeTab.result" :key="activeTab.id" class="flex-1 min-h-0" :result="activeTab.result" :sql="activeTab.lastExecutedSql || activeTab.sql" />
<div v-else class="flex-1 min-h-0 flex items-center justify-center text-muted-foreground text-sm">
{{ t('editor.pressToExecute') }}
</div>
+16 -2
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch, shallowRef } from "vue";
import type { EditorView as EditorViewType } from "@codemirror/view";
import { resolveExecutableSql } from "@/lib/sqlExecutionTarget";
const props = defineProps<{
modelValue: string;
@@ -9,7 +10,8 @@ const props = defineProps<{
const emit = defineEmits<{
"update:modelValue": [value: string];
execute: [];
"selectionChange": [value: string];
execute: [sql: string];
}>();
const editorRef = ref<HTMLDivElement>();
@@ -58,6 +60,15 @@ function resetZoom() {
setFontSize(DEFAULT_FONT_SIZE);
}
function selectedSqlFromView(currentView: EditorViewType): string {
const selection = currentView.state.selection.main;
return currentView.state.sliceDoc(selection.from, selection.to);
}
function executableSqlFromView(currentView: EditorViewType): string {
return resolveExecutableSql(currentView.state.doc.toString(), selectedSqlFromView(currentView));
}
onMounted(async () => {
if (!editorRef.value) return;
@@ -111,7 +122,7 @@ onMounted(async () => {
{
key: "Mod-Enter",
run: () => {
emit("execute");
if (view.value) emit("execute", executableSqlFromView(view.value));
return true;
},
},
@@ -128,6 +139,9 @@ onMounted(async () => {
if (update.docChanged) {
emit("update:modelValue", update.state.doc.toString());
}
if (update.selectionSet || update.docChanged) {
emit("selectionChange", selectedSqlFromView(update.view));
}
}),
fontSizeTheme.of(fontTheme(EditorView, fontSize.value)),
EditorView.domEventHandlers({
+1 -1
View File
@@ -6,7 +6,7 @@ export default {
newConnection: "New Connection",
newQuery: "New Query",
execute: "Execute",
executeShortcut: "Execute (Cmd+Enter)",
executeShortcut: "Execute selection/query (Cmd+Enter)",
},
sidebar: {
connections: "CONNECTIONS",
+1 -1
View File
@@ -6,7 +6,7 @@ export default {
newConnection: "新建连接",
newQuery: "新建查询",
execute: "执行",
executeShortcut: "执行 (Cmd+Enter)",
executeShortcut: "执行选中/全部 (Cmd+Enter)",
},
sidebar: {
connections: "连接",
+4
View File
@@ -0,0 +1,4 @@
export function resolveExecutableSql(fullSql: string, selectedSql: string): string {
const trimmedSelection = selectedSql.trim();
return trimmedSelection ? trimmedSelection : fullSql;
}
+12 -1
View File
@@ -55,6 +55,7 @@ export const useQueryStore = defineStore("query", () => {
if (!tab || tab.database === database) return;
tab.database = database;
tab.result = undefined;
tab.lastExecutedSql = undefined;
tab.tableMeta = undefined;
}
@@ -64,6 +65,7 @@ export const useQueryStore = defineStore("query", () => {
tab.connectionId = connectionId;
tab.database = database;
tab.result = undefined;
tab.lastExecutedSql = undefined;
tab.tableMeta = undefined;
}
@@ -76,9 +78,17 @@ export const useQueryStore = defineStore("query", () => {
const tab = tabs.value.find((t) => t.id === activeTabId.value);
if (!tab || !tab.sql.trim()) return;
await executeCurrentSql(tab.sql);
}
async function executeCurrentSql(sql: string) {
const tab = tabs.value.find((t) => t.id === activeTabId.value);
if (!tab || !sql.trim()) return;
tab.isExecuting = true;
tab.lastExecutedSql = sql;
try {
tab.result = await api.executeQuery(tab.connectionId, tab.database, tab.sql);
tab.result = await api.executeQuery(tab.connectionId, tab.database, sql);
} catch (e: any) {
tab.result = {
columns: ["Error"],
@@ -110,5 +120,6 @@ export const useQueryStore = defineStore("query", () => {
updateConnection,
setTableMeta,
executeCurrentTab,
executeCurrentSql,
};
});
+1
View File
@@ -95,6 +95,7 @@ export interface QueryTab {
connectionId: string;
database: string;
sql: string;
lastExecutedSql?: string;
result?: QueryResult;
isExecuting: boolean;
mode: "data" | "query" | "redis" | "mongo";