fix(query): preserve structured backend errors
This commit is contained in:
1 parent
2e9dcdc26b
commit
bcafc4a474
58 files changed
+4182
-554
No files matched your search
@@ -425,14 +425,14 @@ describe("useSqlExecution", () => {
|
||||
expect(addHistory).toHaveBeenCalledWith(expect.objectContaining({ success: true, error: undefined }));
|
||||
});
|
||||
|
||||
it("continues to record active non-MySQL errors as failures", async () => {
|
||||
it("continues to record explicitly marked non-MySQL errors as failures", async () => {
|
||||
const activeTab = ref<QueryTab | undefined>(queryTab("app"));
|
||||
const activeConnection = ref<ConnectionConfig | undefined>(connection("postgres"));
|
||||
const activeOutputView = ref<"result" | "summary" | "explain" | "chart">("result");
|
||||
const queryStore = useQueryStore();
|
||||
const historyStore = useHistoryStore();
|
||||
vi.spyOn(queryStore, "executeCurrentSql").mockImplementation(async () => {
|
||||
if (activeTab.value) activeTab.value.result = { columns: ["Error"], rows: [["relation does not exist"]], affected_rows: 0, execution_time_ms: 1 };
|
||||
if (activeTab.value) activeTab.value.result = { columns: ["Error"], rows: [["relation does not exist"]], affected_rows: 0, execution_time_ms: 1, execution_error: true };
|
||||
});
|
||||
const addHistory = vi.spyOn(historyStore, "add").mockResolvedValue(undefined);
|
||||
|
||||
@@ -448,6 +448,29 @@ describe("useSqlExecution", () => {
|
||||
expect(addHistory).toHaveBeenCalledWith(expect.objectContaining({ success: false, error: "relation does not exist" }));
|
||||
});
|
||||
|
||||
it("does not treat an unmarked PostgreSQL Error alias as a failure", async () => {
|
||||
const activeTab = ref<QueryTab | undefined>(queryTab("app"));
|
||||
const activeConnection = ref<ConnectionConfig | undefined>(connection("postgres"));
|
||||
const activeOutputView = ref<"result" | "summary" | "explain" | "chart">("result");
|
||||
const queryStore = useQueryStore();
|
||||
const historyStore = useHistoryStore();
|
||||
vi.spyOn(queryStore, "executeCurrentSql").mockImplementation(async () => {
|
||||
if (activeTab.value) activeTab.value.result = { columns: ["Error"], rows: [[2]], affected_rows: 0, execution_time_ms: 1 };
|
||||
});
|
||||
const addHistory = vi.spyOn(historyStore, "add").mockResolvedValue(undefined);
|
||||
|
||||
const execution = useSqlExecution({
|
||||
activeTab: computed(() => activeTab.value),
|
||||
activeConnection: computed(() => activeConnection.value),
|
||||
executableSql: computed(() => "SELECT 2 AS Error"),
|
||||
activeOutputView,
|
||||
});
|
||||
|
||||
await execution.tryExecute();
|
||||
|
||||
expect(addHistory).toHaveBeenCalledWith(expect.objectContaining({ success: true, error: undefined }));
|
||||
});
|
||||
|
||||
it("keeps the full dangerous script and new-result-tab intent through confirmation", async () => {
|
||||
const activeTab = ref<QueryTab | undefined>(queryTab("app"));
|
||||
const activeConnection = ref<ConnectionConfig | undefined>(connection("mysql"));
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as api from "@/lib/backend/api";
|
||||
import { connectionObjectTreeNodeSchema, effectiveDatabaseTypeForConnection, metadataSchemaForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { invalidateTableMetadataCache, loadTableMetadata } from "@/lib/metadata/tableMetadataCache";
|
||||
import { canApplyDataTabMetadata } from "@/lib/sidebar/dataTabOpenPolicy";
|
||||
import { isNoSnapshotErrorResult } from "@/lib/query/queryResultError";
|
||||
import { isNoSnapshotErrorResult, isQueryExecutionErrorResult } from "@/lib/query/queryResultError";
|
||||
import { buildTableSelectSql } from "@/lib/table/tableSelectSql";
|
||||
import { editableRowIdentifierColumns, usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
@@ -169,7 +169,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
const tabAfterFirstExecute = queryStore.tabs.find((tab) => tab.id === tabId);
|
||||
const firstResult = tabAfterFirstExecute?.result;
|
||||
const cancelRequestedDuringExecute = (tabAfterFirstExecute?.cancelRequestCount ?? 0) > cancelCountBeforeExecute;
|
||||
const firstQueryFailed = cancelRequestedDuringExecute || tabAfterFirstExecute?.isCancelling === true || (firstResult?.columns.length === 1 && firstResult.columns[0] === "Error");
|
||||
const firstQueryFailed = cancelRequestedDuringExecute || tabAfterFirstExecute?.isCancelling === true || (firstResult !== undefined && isQueryExecutionErrorResult(firstResult));
|
||||
// executeTabSql surfaces query failures as an "Error" result instead of throwing.
|
||||
// A snapshot-less lake table fails the data preview above but its metadata still
|
||||
// reads fine — retry with LIMIT 0 so the user sees the table structure (columns +
|
||||
|
||||
@@ -9,7 +9,7 @@ import { isSingleDatabase, usesTreeSchemaMode } from "@/lib/database/databaseCap
|
||||
import { supportsConnectionLevelSqlExecution } from "@/lib/connection/connectionLevelDatabaseBootstrap";
|
||||
import { classifySqlActivityKind } from "@/lib/history/historyActivityKind";
|
||||
import { sqlMetadataRefreshTarget } from "@/lib/sql/sqlMetadataRefresh";
|
||||
import { isQueryExecutionErrorResult, usesMysqlProtocolDatabaseType } from "@/lib/query/queryResultError";
|
||||
import { isQueryExecutionErrorResult } from "@/lib/query/queryResultError";
|
||||
import { classifyRedisCommandSafety } from "@/lib/redis/redisCommandSafety";
|
||||
import { isSqlExecutionSnapshot, resolveExecutableSql, type SqlExecutionOverride, type SqlExecutionSnapshot } from "@/lib/sql/sqlExecutionTarget";
|
||||
import { isElasticsearchRestRequestText, parseElasticsearchRestRequestTarget, splitSqlStatementRanges } from "@/lib/sql/sqlStatementRanges";
|
||||
@@ -63,10 +63,9 @@ function primarySqlOperation(sql: string): string {
|
||||
return statement?.match(/^([a-z]+)/i)?.[1]?.toUpperCase() || "SQL";
|
||||
}
|
||||
|
||||
function firstQueryExecutionError(tab: Pick<QueryTab, "result" | "results">, databaseType: DatabaseType | undefined) {
|
||||
function firstQueryExecutionError(tab: Pick<QueryTab, "result" | "results">) {
|
||||
const activeResult = tab.result;
|
||||
if (activeResult && isQueryExecutionErrorResult(activeResult)) return activeResult;
|
||||
if (!usesMysqlProtocolDatabaseType(databaseType) && activeResult?.columns.includes("Error")) return activeResult;
|
||||
|
||||
const results = tab.results?.length ? tab.results : tab.result ? [tab.result] : [];
|
||||
return results.find((result) => isQueryExecutionErrorResult(result));
|
||||
@@ -233,7 +232,7 @@ export function useSqlExecution(deps: {
|
||||
deps.activeOutputView.value = "summary";
|
||||
}
|
||||
const elapsed = Date.now() - start;
|
||||
const failure = firstQueryExecutionError(tab, executionDatabaseType);
|
||||
const failure = firstQueryExecutionError(tab);
|
||||
const success = !failure;
|
||||
historyStore.add({
|
||||
connection_id: tab.connectionId,
|
||||
|
||||
Reference in new issue
Block a user