fix(query): preserve original backend errors with fallback detail

This commit is contained in:
zipg authored and GitHub committed 2026-09-03 13:43:55 +08:00
1 parent 76c51b5fa6
commit 8cfd69c7c9
7 files changed
+134 -13

No files matched your search

@@ -1,5 +1,6 @@
import { createPinia, setActivePinia } from "pinia";
import { isActiveResultLoading } from "@/lib/sql/queryExecutionState";
import { BackendErrorException } from "@/lib/backend/errorUtils";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
@@ -173,6 +174,42 @@ describe("queryStore multi-statement errors", () => {
expect(tab.batchSqlExecution?.items[1]?.errorDetails).toEqual(structuredError);
expect(tab.batchSqlExecution?.items[1]?.error).not.toBe(structuredError.code);
expect(tab.batchSqlExecution?.items[1]?.error).not.toBe("[object Object]");
expect(tab.batchSqlExecution?.items[1]?.error).toContain("no such table: missing");
});
it("preserves the original message when a top-level structured error omits detail", async () => {
const structuredError = {
version: 1 as const,
code: "DBX-LEGACY-0001",
messageKey: "backendErrors.legacy",
messageParams: {},
source: "legacyBackend" as const,
operationOutcome: "unknown" as const,
};
const originalMessage = "ClickHouse error: table iceberg_backend.missing_table does not exist";
mocks.getConnectionConfig.mockReturnValue({
id: "clickhouse-1",
name: "ClickHouse",
db_type: "clickhouse",
database: "iceberg_backend",
query_timeout_secs: 30,
});
mocks.executeMulti.mockRejectedValue(
new BackendErrorException({
backendError: structuredError,
message: originalMessage,
}),
);
const { useQueryStore } = await import("@/stores/queryStore");
const store = useQueryStore();
const tabId = store.createTab("clickhouse-1", "iceberg_backend", "Query");
await store.executeTabSql(tabId, "SELECT * FROM missing_table");
const tab = store.tabs.find((item) => item.id === tabId)!;
expect(tab.result?.execution_error).toBe(true);
expect(tab.result?.rows[0]?.[0]).toContain(originalMessage);
expect(tab.batchSqlExecution?.items[0]?.error).toContain(originalMessage);
});
it("updates live per-statement progress before the batch promise resolves", async () => {
+3 -3
View File
@@ -411,7 +411,7 @@ function reconcileBatchSqlResults(tab: QueryTab, executionId: string, results: Q
item.executionTimeMs = result.execution_time_ms;
item.affectedRows = result.affected_rows;
item.errorDetails = failed ? result.error : undefined;
item.error = failed ? (result.error ? translateBackendError(i18n.global.t, result.error) : String(result.rows[0]?.[0] ?? "")) : undefined;
item.error = failed ? (result.error ? translateBackendError(i18n.global.t, result.error, result.rows[0]?.[0]) : String(result.rows[0]?.[0] ?? "")) : undefined;
}
batch.completed = batch.items.filter((item) => item.status === "success" || item.status === "error").length;
}
@@ -423,7 +423,7 @@ function failBatchSqlExecution(tab: QueryTab, executionId: string, error: unknow
if (!item) return;
item.status = cancelled ? "cancelled" : "error";
item.errorDetails = cancelled ? undefined : (normalizeBackendError(error) ?? undefined);
item.error = cancelled ? undefined : translateBackendError(i18n.global.t, error);
item.error = cancelled ? undefined : translateBackendError(i18n.global.t, error, error instanceof Error ? error.message : undefined);
batch.completed = batch.items.filter((candidate) => candidate.status === "success" || candidate.status === "error").length;
}
@@ -3856,7 +3856,7 @@ export const useQueryStore = defineStore("query", () => {
// Single funnel for every query execution failure, so backend messages DBX
// knows about are shown in the active locale rather than as raw English.
const error = normalizeBackendError(e) ?? undefined;
const message = translateBackendError(i18n.global.t, e);
const message = translateBackendError(i18n.global.t, e, e instanceof Error ? e.message : undefined);
return markQueryResultRowsRaw({
columns: ["Error"],
execution_error: true,