feat: add execute_multi command and update query handling for multiple results

This commit is contained in:
t8y2 2026-05-04 02:33:57 +08:00
parent c328b3bf49
commit ae21053f69
8 changed files with 100 additions and 2 deletions

View File

@ -262,6 +262,60 @@ pub async fn execute_query(
result
}
#[tauri::command]
pub async fn execute_multi(
state: State<'_, Arc<AppState>>,
connection_id: String,
database: String,
sql: String,
execution_id: Option<String>,
) -> Result<Vec<db::QueryResult>, String> {
let registered_query = execution_id
.as_ref()
.filter(|id| !id.trim().is_empty())
.map(|id| state.running_queries.register(id.clone()));
let cancel_token = registered_query.as_ref().map(|query| query.token());
let statements = split_sql_statements(&sql);
if statements.len() <= 1 {
let single_sql = statements.into_iter().next().unwrap_or_default();
let result = execute_sql_statement(
&state, &connection_id, &database, &single_sql, cancel_token,
).await?;
return Ok(vec![result]);
}
let mut results = Vec::with_capacity(statements.len());
for stmt in &statements {
if is_canceled(&cancel_token) {
results.push(db::QueryResult {
columns: vec!["Error".to_string()],
rows: vec![vec![serde_json::Value::String(canceled_error())]],
affected_rows: 0,
execution_time_ms: 0,
truncated: false,
});
break;
}
match execute_sql_statement(
&state, &connection_id, &database, stmt, cancel_token.clone(),
).await {
Ok(r) => results.push(r),
Err(e) => {
results.push(db::QueryResult {
columns: vec!["Error".to_string()],
rows: vec![vec![serde_json::Value::String(e)]],
affected_rows: 0,
execution_time_ms: 0,
truncated: false,
});
}
}
}
Ok(results)
}
#[tauri::command]
pub async fn cancel_query(
state: State<'_, Arc<AppState>>,

View File

@ -63,6 +63,7 @@ pub fn run() {
commands::schema::list_triggers,
commands::schema::get_table_ddl,
commands::query::execute_query,
commands::query::execute_multi,
commands::query::cancel_query,
commands::query::execute_batch,
commands::query::execute_script,

View File

@ -1320,6 +1320,19 @@ async function setupFileDrop() {
>
{{ t('tabs.tableData') }}
</Button>
<template v-if="activeOutputView === 'result' && activeTab.results && activeTab.results.length > 1">
<span class="mx-1 h-4 w-px bg-border" />
<Button
v-for="(_, rIdx) in activeTab.results"
:key="rIdx"
size="sm"
:variant="activeTab.activeResultIndex === rIdx ? 'default' : 'ghost'"
class="h-6 px-2 text-xs"
@click="queryStore.setActiveResultIndex(activeTab.id, rIdx)"
>
{{ t('tabs.resultN', { n: rIdx + 1 }) }}
</Button>
</template>
<Button
size="sm"
:variant="activeOutputView === 'explain' ? 'secondary' : 'ghost'"
@ -1343,7 +1356,7 @@ async function setupFileDrop() {
/>
<template v-else>
<DataGrid v-if="activeTab.result" :key="activeTab.id" class="flex-1 min-h-0" :result="activeTab.result" :sql="activeTab.lastExecutedSql || activeTab.sql" :loading="activeTab.isExecuting" />
<DataGrid v-if="activeTab.result" :key="`${activeTab.id}-${activeTab.activeResultIndex ?? 0}`" class="flex-1 min-h-0" :result="activeTab.result" :sql="activeTab.lastExecutedSql || activeTab.sql" :loading="activeTab.isExecuting" />
<div v-if="activeTab.result?.columns.includes('Error')" class="flex items-center gap-2 px-3 py-1.5 border-t bg-destructive/5">
<Bot class="h-3.5 w-3.5 text-destructive" />
<button class="text-xs text-destructive hover:underline" @click="fixWithAi(String(activeTab.result?.rows?.[0]?.[0] ?? ''))">

View File

@ -94,6 +94,7 @@ export default {
tooltipDatabase: "Database:",
tooltipTable: "Table:",
tooltipCollection: "Collection:",
resultN: "Result {n}",
},
grid: {
rows: "{count} rows",

View File

@ -96,6 +96,7 @@ export default {
tooltipDatabase: "数据库:",
tooltipTable: "表:",
tooltipCollection: "集合:",
resultN: "结果 {n}",
},
grid: {
rows: "{count} 行",

View File

@ -138,6 +138,10 @@ export async function executeQuery(connectionId: string, database: string, sql:
return invoke("execute_query", { connectionId, database, sql, executionId });
}
export async function executeMulti(connectionId: string, database: string, sql: string, executionId?: string): Promise<QueryResult[]> {
return invoke("execute_multi", { connectionId, database, sql, executionId });
}
export async function cancelQuery(executionId: string): Promise<boolean> {
return invoke("cancel_query", { executionId });
}

View File

@ -174,11 +174,25 @@ export const useQueryStore = defineStore("query", () => {
tab.executionId = executionId;
tab.lastExecutedSql = sql;
try {
tab.result = await api.executeQuery(tab.connectionId, tab.database, sql, executionId);
const results = await api.executeMulti(tab.connectionId, tab.database, sql, executionId);
const current = tabs.value.find((t) => t.id === id);
if (current?.executionId === executionId) {
if (results.length > 1) {
current.results = results;
current.activeResultIndex = 0;
current.result = results[0];
} else {
current.results = undefined;
current.activeResultIndex = undefined;
current.result = results[0];
}
}
} catch (e: any) {
const current = tabs.value.find((t) => t.id === id);
if (current?.executionId === executionId) {
current.result = toErrorResult(e);
current.results = undefined;
current.activeResultIndex = undefined;
}
} finally {
const current = tabs.value.find((t) => t.id === id);
@ -277,6 +291,13 @@ export const useQueryStore = defineStore("query", () => {
}
}
function setActiveResultIndex(id: string, index: number) {
const tab = tabs.value.find((t) => t.id === id);
if (!tab?.results || index < 0 || index >= tab.results.length) return;
tab.activeResultIndex = index;
tab.result = tab.results[index];
}
function trimResultCache() {
const inactive = tabs.value.filter((t) => t.id !== activeTabId.value && t.result);
if (inactive.length > MAX_CACHED_RESULTS) {
@ -299,6 +320,7 @@ export const useQueryStore = defineStore("query", () => {
setTableMeta,
setExecuting,
setErrorResult,
setActiveResultIndex,
executeCurrentTab,
executeCurrentSql,
executeTabSql,

View File

@ -110,6 +110,8 @@ export interface QueryTab {
lastExecutedSql?: string;
pinned?: boolean;
result?: QueryResult;
results?: QueryResult[];
activeResultIndex?: number;
explainPlan?: import("@/lib/explainPlan").ParsedExplainPlan;
explainError?: string;
explainSql?: string;