From a5e46fd659dee10502b968bfcc759e448c649b34 Mon Sep 17 00:00:00 2001 From: Eddy Lei <43884159+ededddy@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:18:32 +0800 Subject: [PATCH] feat(query): show database server messages in query results --- apps/desktop/src/App.vue | 2 +- .../src/components/layout/ContentArea.vue | 26 +- .../components/layout/QueryMessagesView.vue | 45 +++ .../layout/QueryResultToolbarActions.vue | 2 +- .../layout/QueryResultViewSwitcher.vue | 29 +- .../__tests__/useSqlExecution.spec.ts | 102 ++++++ .../src/composables/useSqlExecution.ts | 5 +- apps/desktop/src/i18n/locales/en.ts | 5 + apps/desktop/src/i18n/locales/es.ts | 5 + apps/desktop/src/i18n/locales/it.ts | 5 + apps/desktop/src/i18n/locales/ja.ts | 5 + apps/desktop/src/i18n/locales/ko.ts | 5 + apps/desktop/src/i18n/locales/pt-BR.ts | 5 + apps/desktop/src/i18n/locales/zh-CN.ts | 5 + apps/desktop/src/i18n/locales/zh-TW.ts | 5 + .../src/lib/query/queryResultDefaultView.ts | 12 + apps/desktop/src/types/database.ts | 11 + crates/dbx-cli/src/main.rs | 87 ++++- crates/dbx-core/src/agent_tools.rs | 61 +++- crates/dbx-core/src/connection.rs | 1 + crates/dbx-core/src/db/clickhouse_driver.rs | 2 + crates/dbx-core/src/db/cloudflare_d1/mod.rs | 1 + .../dbx-core/src/db/elasticsearch_driver.rs | 5 + crates/dbx-core/src/db/influxdb_driver.rs | 3 + crates/dbx-core/src/db/mysql.rs | 263 ++++++++++++++- crates/dbx-core/src/db/postgres.rs | 312 +++++++++++++++++- crates/dbx-core/src/db/rqlite_driver.rs | 2 + crates/dbx-core/src/db/sqlite.rs | 2 + crates/dbx-core/src/db/sqlserver.rs | 86 ++++- crates/dbx-core/src/db/turso_driver.rs | 4 + crates/dbx-core/src/db/vector_driver.rs | 2 + .../dbx-core/src/db/victoriametrics_driver.rs | 2 + crates/dbx-core/src/mongo_ops.rs | 1 + crates/dbx-core/src/query.rs | 17 + crates/dbx-core/src/schema.rs | 9 + crates/dbx-core/src/schema/kingbase.rs | 1 + crates/dbx-core/src/table_export.rs | 1 + .../src/table_structure_sql/sqlite_rebuild.rs | 1 + crates/dbx-core/src/transfer.rs | 1 + crates/dbx-core/src/types.rs | 123 ++++++- crates/dbx-mcp/src/backend.rs | 56 +++- crates/dbx-web/src/routes/query.rs | 1 + packages/app-tests/queryMessagesView.test.ts | 90 +++++ .../app-tests/queryResultDefaultView.test.ts | 42 +++ packages/app-tests/queryResultToolbar.test.ts | 2 +- 45 files changed, 1401 insertions(+), 51 deletions(-) create mode 100644 apps/desktop/src/components/layout/QueryMessagesView.vue create mode 100644 apps/desktop/src/lib/query/queryResultDefaultView.ts create mode 100644 packages/app-tests/queryMessagesView.test.ts create mode 100644 packages/app-tests/queryResultDefaultView.test.ts diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index 2979afa64..7dd12736b 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -239,7 +239,7 @@ const selectedSql = ref(""); const cursorPos = ref(0); const formatSqlRequest = ref<{ id: number; tabId: string } | null>(null); const compressSqlRequest = ref<{ id: number; tabId: string } | null>(null); -const activeOutputView = ref<"result" | "summary" | "explain" | "chart">("result"); +const activeOutputView = ref<"result" | "summary" | "explain" | "chart" | "messages">("result"); const newQueryContextSource = ref<"tab" | "sidebar">("tab"); const queryEditorDdlTarget = ref<{ connectionId: string; database: string; catalog?: string; schema?: string; tableName: string; objectType?: ObjectSourceKind } | null>(null); const queryEditorObjectSourceTarget = ref<{ diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 0e71a96d5..ccc082bdb 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -3,6 +3,7 @@ import { computed, ref, defineAsyncComponent, watch, nextTick, onMounted, onUnmo import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage"; import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog"; import { canReloadUnavailableDataTab } from "@/lib/table/tableDataRefresh"; +import { defaultViewForResult } from "@/lib/query/queryResultDefaultView"; import { isQueryExecutionErrorResult } from "@/lib/query/queryResultError"; import type { CSSProperties } from "vue"; import { useI18n } from "vue-i18n"; @@ -18,6 +19,7 @@ import QueryEditor from "@/components/editor/QueryEditor.vue"; import ColumnInfoPanel from "@/components/editor/ColumnInfoPanel.vue"; import QueryLoadingState from "@/components/common/QueryLoadingState.vue"; import QueryErrorActions from "@/components/common/QueryErrorActions.vue"; +import QueryMessagesView from "@/components/layout/QueryMessagesView.vue"; import QueryResultToolbarActions from "@/components/layout/QueryResultToolbarActions.vue"; import QueryResultViewSwitcher from "@/components/layout/QueryResultViewSwitcher.vue"; import DataGridCopyFormatControl from "@/components/grid/DataGridCopyFormatControl.vue"; @@ -138,7 +140,7 @@ const props = defineProps<{ activeTab: QueryTab; activeConnection?: ConnectionConfig; executableSql: string; - activeOutputView: "result" | "summary" | "explain" | "chart"; + activeOutputView: "result" | "summary" | "explain" | "chart" | "messages"; formatSqlRequest: { id: number; tabId: string } | null; compressSqlRequest: { id: number; tabId: string } | null; selectedSql: string; @@ -147,7 +149,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - "update:activeOutputView": [value: "result" | "summary" | "explain" | "chart"]; + "update:activeOutputView": [value: "result" | "summary" | "explain" | "chart" | "messages"]; fixWithAi: [errorMessage: string]; sendSelectionToAi: [sql: string]; execute: [sqlOverride?: SqlExecutionOverride]; @@ -427,6 +429,8 @@ const hasTabularResult = computed(() => { }); const canShowResultOutput = computed(() => hasTabularResult.value || props.activeTab.isExecuting); const canShowExplainOutput = computed(() => !!props.activeTab.explainPlan || !!props.activeTab.explainError || !!props.activeTab.explainTableResult || !!props.activeTab.explainTableError || props.activeTab.isExplaining === true); +const resultMessageCount = computed(() => props.activeTab.result?.messages?.length ?? 0); +const canShowMessagesOutput = computed(() => resultMessageCount.value > 0); const showStandaloneResultToolbar = computed(() => activeElasticsearchJsonResponse.value || props.activeOutputView !== "result" || !props.activeTab.result || !hasTabularResult.value); const standaloneResultToolbarCompact = computed(() => isDataGridToolbarCompact(standaloneResultToolbarWidth.value, standaloneResultToolbarViewportWidth.value)); let standaloneResultToolbarResizeObserver: ResizeObserver | undefined; @@ -599,7 +603,8 @@ watch( () => { if (props.activeTab.isExecuting) return; if (hasExecutionSummary.value && !hasTabularResult.value && props.activeOutputView === "result") { - emit("update:activeOutputView", "summary"); + const result = props.activeTab.result; + emit("update:activeOutputView", result ? defaultViewForResult(result) : "summary"); } }, { immediate: true }, @@ -1319,6 +1324,8 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand :can-show-result="canShowResultOutput" :can-show-summary="hasExecutionSummary" :can-show-chart="hasNumericData && !activeElasticsearchJsonResponse" + :can-show-messages="canShowMessagesOutput" + :message-count="resultMessageCount" :compact="standaloneResultToolbarCompact" @select-view="emit('update:activeOutputView', $event)" /> @@ -1414,6 +1421,8 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand + +