fix(editor): use editor snapshot for SQL execution
This commit is contained in:
parent
4dbd1c7cc8
commit
6e7035823a
|
|
@ -35,7 +35,7 @@ import { quickConnectionOpenTarget } from "@/lib/connectionOpenTarget";
|
|||
import { resolveDefaultDatabase } from "@/lib/defaultDatabase";
|
||||
import { findTreeNodeById, resolveNewQueryTarget } from "@/lib/newQueryContext";
|
||||
import { buildExecutableObjectSourceStatements, objectSourceSaveExecutionMode } from "@/lib/objectSourceEditor";
|
||||
import { resolveExecutableSql, resolveExecutableSqlWithBackend } from "@/lib/sqlExecutionTarget";
|
||||
import { resolveExecutableSql, resolveExecutableSqlWithBackend, type SqlExecutionSnapshot } from "@/lib/sqlExecutionTarget";
|
||||
import { uuid } from "@/lib/utils";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import { openQueryResultArchiveFile } from "@/lib/queryResultArchiveFile";
|
||||
|
|
@ -179,12 +179,12 @@ const executableSql = computed(() => {
|
|||
: "";
|
||||
});
|
||||
|
||||
async function resolveActiveExecutableSql() {
|
||||
async function resolveActiveExecutableSql(snapshot?: SqlExecutionSnapshot) {
|
||||
const tab = activeTab.value;
|
||||
return tab
|
||||
? await resolveExecutableSqlWithBackend(tab.sql, selectedSql.value, {
|
||||
? await resolveExecutableSqlWithBackend(snapshot?.fullSql ?? tab.sql, snapshot?.selectedSql ?? selectedSql.value, {
|
||||
mode: settingsStore.editorSettings.executeMode,
|
||||
cursorPos: cursorPos.value,
|
||||
cursorPos: snapshot?.cursorPos ?? cursorPos.value,
|
||||
databaseType: activeConnection.value?.db_type,
|
||||
})
|
||||
: "";
|
||||
|
|
@ -1230,7 +1230,7 @@ onUnmounted(() => {
|
|||
:block-dangerous-redis-commands="blockDangerousRedisCommands"
|
||||
@update:explain-mode="(m: 'explain' | 'autotrace') => (explainMode = m)"
|
||||
@update:block-dangerous-redis-commands="(v: boolean) => (blockDangerousRedisCommands = v)"
|
||||
@execute="tryExecute()"
|
||||
@execute="tryExecute($event)"
|
||||
@cancel="cancelActiveExecution()"
|
||||
@explain="tryExplain()"
|
||||
@format-sql="formatActiveSql"
|
||||
|
|
@ -1256,7 +1256,7 @@ onUnmounted(() => {
|
|||
:cursor-pos="cursorPos"
|
||||
@update:active-output-view="activeOutputView = $event"
|
||||
@fix-with-ai="fixWithAi"
|
||||
@execute="tryExecute()"
|
||||
@execute="tryExecute($event)"
|
||||
@cancel="cancelActiveExecution()"
|
||||
@explain="tryExplain()"
|
||||
@editor-update="(tabId: string, v: string) => queryStore.updateSql(tabId, v)"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { search as cmSearch } from "@codemirror/search";
|
|||
import EditorSearchPanel from "./EditorSearchPanel.vue";
|
||||
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
|
||||
import { copyToClipboard } from "@/lib/clipboard";
|
||||
import { resolveExecutableSql } from "@/lib/sqlExecutionTarget";
|
||||
import { resolveExecutableSql, type SqlExecutionSnapshot } from "@/lib/sqlExecutionTarget";
|
||||
import { formatSqlText, type SqlFormatDialect } from "@/lib/sqlFormatter";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
|
|
@ -64,7 +64,7 @@ const emit = defineEmits<{
|
|||
selectionChange: [value: string];
|
||||
cursorChange: [pos: number];
|
||||
formatError: [message: string];
|
||||
execute: [sql: string];
|
||||
execute: [snapshot: SqlExecutionSnapshot];
|
||||
save: [];
|
||||
clickTable: [tableName: string];
|
||||
viewTableData: [tableName: string];
|
||||
|
|
@ -288,7 +288,7 @@ function handleTab(view: EditorViewType): boolean {
|
|||
}
|
||||
|
||||
function executeCurrentSql() {
|
||||
if (view.value) emit("execute", executableSqlFromView(view.value));
|
||||
if (view.value) emit("execute", sqlExecutionSnapshotFromView(view.value));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -461,6 +461,14 @@ function executableSqlFromView(currentView: EditorViewType): string {
|
|||
return resolveExecutableSql(currentView.state.doc.toString(), selectedSqlFromView(currentView));
|
||||
}
|
||||
|
||||
function sqlExecutionSnapshotFromView(currentView: EditorViewType): SqlExecutionSnapshot {
|
||||
return {
|
||||
fullSql: currentView.state.doc.toString(),
|
||||
selectedSql: selectedSqlFromView(currentView),
|
||||
cursorPos: currentView.state.selection.main.head,
|
||||
};
|
||||
}
|
||||
|
||||
function identifierRangeAt(sql: string, pos: number): { from: number; to: number; text: string } | null {
|
||||
const isIdentifierChar = (ch: string | undefined) => !!ch && /[\w$.]/.test(ch);
|
||||
if (!isIdentifierChar(sql[pos]) && !isIdentifierChar(sql[pos - 1])) return null;
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import { tableMetaForDataTab } from "@/lib/tableDataTabMeta";
|
|||
import { formatShortcut } from "@/lib/shortcutRegistry";
|
||||
import { effectiveDatabaseTypeForConnection } from "@/lib/jdbcDialect";
|
||||
import { chartableColumnIndexes } from "@/lib/chartData";
|
||||
import type { SqlExecutionOverride } from "@/lib/sqlExecutionTarget";
|
||||
import { useTabScroll } from "@/composables/useTabScroll";
|
||||
import type { QueryTab, ConnectionConfig, TableInfoTab } from "@/types/database";
|
||||
import type { SqlFormatDialect } from "@/lib/sqlFormatter";
|
||||
|
|
@ -101,7 +102,7 @@ const props = defineProps<{
|
|||
const emit = defineEmits<{
|
||||
"update:activeOutputView": [value: "result" | "summary" | "explain" | "chart"];
|
||||
fixWithAi: [errorMessage: string];
|
||||
execute: [sqlOverride?: string];
|
||||
execute: [sqlOverride?: SqlExecutionOverride];
|
||||
saveSql: [];
|
||||
cancel: [];
|
||||
explain: [];
|
||||
|
|
@ -522,7 +523,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget });
|
|||
@viewport-change="emit('editorViewportChange', activeTab.id, $event)"
|
||||
@selection-state-change="emit('editorSelectionStateChange', activeTab.id, $event)"
|
||||
@format-error="emit('formatError')"
|
||||
@execute="emit('execute')"
|
||||
@execute="emit('execute', $event)"
|
||||
@save="emit('saveSql')"
|
||||
@click-table="onHandleClickTable"
|
||||
@view-table-data="onHandleViewTableData"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { useToast } from "@/composables/useToast";
|
|||
import { classifySqlActivityKind } from "@/lib/historyActivityKind";
|
||||
import { sqlMetadataRefreshTarget } from "@/lib/sqlMetadataRefresh";
|
||||
import { classifyRedisCommandSafety, firstRedisCommandToken } from "@/lib/redisCommandSafety";
|
||||
import { isSqlExecutionSnapshot, resolveExecutableSql, type SqlExecutionOverride, type SqlExecutionSnapshot } from "@/lib/sqlExecutionTarget";
|
||||
import type { ConnectionConfig, QueryTab } from "@/types/database";
|
||||
|
||||
const DANGER_RE = /^\s*(DROP|DELETE|TRUNCATE|ALTER|UPDATE|MERGE|REPLACE)\b/i;
|
||||
|
|
@ -37,7 +38,7 @@ export function useSqlExecution(deps: {
|
|||
activeTab: ComputedRef<QueryTab | undefined>;
|
||||
activeConnection: ComputedRef<ConnectionConfig | undefined>;
|
||||
executableSql: ComputedRef<string>;
|
||||
resolveExecutableSql?: () => Promise<string>;
|
||||
resolveExecutableSql?: (snapshot?: SqlExecutionSnapshot) => Promise<string>;
|
||||
activeOutputView: Ref<"result" | "summary" | "explain" | "chart">;
|
||||
blockDangerousRedisCommands?: Ref<boolean>;
|
||||
}) {
|
||||
|
|
@ -54,13 +55,16 @@ export function useSqlExecution(deps: {
|
|||
const suppressDangerConfirm = ref(false);
|
||||
const explainMode = ref<"explain" | "autotrace">("explain");
|
||||
|
||||
async function resolvedExecutableSql(): Promise<string> {
|
||||
return deps.resolveExecutableSql ? await deps.resolveExecutableSql() : deps.executableSql.value;
|
||||
async function resolvedExecutableSql(source?: SqlExecutionOverride): Promise<string> {
|
||||
if (typeof source === "string") return source;
|
||||
if (deps.resolveExecutableSql) return await deps.resolveExecutableSql(source);
|
||||
if (isSqlExecutionSnapshot(source)) return resolveExecutableSql(source.fullSql, source.selectedSql, { cursorPos: source.cursorPos });
|
||||
return deps.executableSql.value;
|
||||
}
|
||||
|
||||
async function tryExecute(sqlOverride?: string) {
|
||||
async function tryExecute(sqlOverride?: SqlExecutionOverride) {
|
||||
const tab = deps.activeTab.value;
|
||||
const sql = sqlOverride ?? (await resolvedExecutableSql());
|
||||
const sql = await resolvedExecutableSql(sqlOverride);
|
||||
if (!tab || !sql.trim()) return;
|
||||
// Redis: block dangerous commands when toggle is on (check each line for multi-line input)
|
||||
if (deps.activeConnection.value?.db_type === "redis" && deps.blockDangerousRedisCommands?.value !== false) {
|
||||
|
|
@ -135,9 +139,9 @@ export function useSqlExecution(deps: {
|
|||
return t("explain.emptySql");
|
||||
}
|
||||
|
||||
async function tryExplain(sqlOverride?: string) {
|
||||
async function tryExplain(sqlOverride?: SqlExecutionOverride) {
|
||||
const tab = deps.activeTab.value;
|
||||
const sql = sqlOverride ?? (await resolvedExecutableSql());
|
||||
const sql = await resolvedExecutableSql(sqlOverride);
|
||||
if (!tab || !sql.trim()) {
|
||||
toast(t("explain.emptySql"));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,18 @@ import type { DatabaseType } from "@/types/database";
|
|||
|
||||
export type ExecuteMode = "all" | "current";
|
||||
|
||||
export interface SqlExecutionSnapshot {
|
||||
fullSql: string;
|
||||
selectedSql: string;
|
||||
cursorPos: number;
|
||||
}
|
||||
|
||||
export type SqlExecutionOverride = string | SqlExecutionSnapshot;
|
||||
|
||||
export function isSqlExecutionSnapshot(value: SqlExecutionOverride | undefined): value is SqlExecutionSnapshot {
|
||||
return typeof value === "object" && value !== null && typeof value.fullSql === "string" && typeof value.selectedSql === "string" && typeof value.cursorPos === "number";
|
||||
}
|
||||
|
||||
export function resolveExecutableSql(fullSql: string, selectedSql: string, options?: { mode?: ExecuteMode; cursorPos?: number }): string {
|
||||
const trimmedSelection = selectedSql.trim();
|
||||
if (trimmedSelection) return trimmedSelection;
|
||||
|
|
|
|||
Loading…
Reference in New Issue