feat: add execute_multi command and update query handling for multiple results
This commit is contained in:
parent
c328b3bf49
commit
ae21053f69
|
|
@ -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>>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
15
src/App.vue
15
src/App.vue
|
|
@ -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] ?? ''))">
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ export default {
|
|||
tooltipDatabase: "Database:",
|
||||
tooltipTable: "Table:",
|
||||
tooltipCollection: "Collection:",
|
||||
resultN: "Result {n}",
|
||||
},
|
||||
grid: {
|
||||
rows: "{count} rows",
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ export default {
|
|||
tooltipDatabase: "数据库:",
|
||||
tooltipTable: "表:",
|
||||
tooltipCollection: "集合:",
|
||||
resultN: "结果 {n}",
|
||||
},
|
||||
grid: {
|
||||
rows: "{count} 行",
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue