fix(data-tab): fall back to LIMIT 0 preview for snapshot-less lake tables (#2197)
Lake/external tables (e.g. Paimon in StarRocks) return 'There is currently no snapshot' on data reads when no snapshot exists yet, while metadata reads (DESC/SHOW CREATE) still succeed. Opening such a table surfaced the raw server error because executeTabSql stores the failure as an 'Error' result and never reached the column fetch. Detect that error result and retry the preview with LIMIT 0, which returns the table structure (columns + empty grid). Skip the synthetic-row-id re-query on this path since it is another data read that would fail the same way.
This commit is contained in:
parent
0777e4aad1
commit
112fce1050
|
|
@ -1,5 +1,6 @@
|
|||
import * as api from "@/lib/api";
|
||||
import { effectiveDatabaseTypeForConnection, metadataSchemaForConnection } from "@/lib/jdbcDialect";
|
||||
import { isNoSnapshotErrorResult } from "@/lib/queryResultError";
|
||||
import { buildTableSelectSql } from "@/lib/tableSelectSql";
|
||||
import { editableRowIdentifierColumns, usesSyntheticRowIdKey } from "@/lib/tableEditing";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
|
|
@ -92,6 +93,24 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
|||
primaryKeys: [],
|
||||
});
|
||||
await queryStore.executeTabSql(tabId, sql);
|
||||
// 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 +
|
||||
// empty grid) rather than a cryptic server error. The flag also skips the
|
||||
// synthetic-row-id re-query below, which is another data read that would fail
|
||||
// the same way on a snapshot-less table.
|
||||
const fellBackToLimitZero = isNoSnapshotErrorResult(queryStore.tabs.find((tab) => tab.id === tabId)?.result);
|
||||
if (fellBackToLimitZero) {
|
||||
const emptySql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
schema: target.schema,
|
||||
tableName: target.tableName,
|
||||
whereInput: target.whereInput,
|
||||
limit: 0,
|
||||
});
|
||||
queryStore.updateSql(tabId, emptySql);
|
||||
await queryStore.executeTabSql(tabId, emptySql);
|
||||
}
|
||||
try {
|
||||
const columns = await api.getColumns(target.connectionId, target.database, querySchema, target.tableName);
|
||||
const indexes = await api.listIndexes(target.connectionId, target.database, querySchema, target.tableName).catch(() => []);
|
||||
|
|
@ -104,7 +123,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
|||
columns,
|
||||
primaryKeys,
|
||||
});
|
||||
if (useRowId || config.db_type === "tdengine") {
|
||||
if (!fellBackToLimitZero && (useRowId || config.db_type === "tdengine")) {
|
||||
const newSql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
schema: target.schema,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { QueryResult } from "@/types/database";
|
||||
|
||||
import { isNoSnapshotErrorResult } from "@/lib/queryResultError";
|
||||
|
||||
function errorResult(message: string): QueryResult {
|
||||
return { columns: ["Error"], rows: [[message]], affected_rows: 0, execution_time_ms: 0 };
|
||||
}
|
||||
|
||||
function dataResult(columns: string[]): QueryResult {
|
||||
return { columns, rows: [], affected_rows: 0, execution_time_ms: 0 };
|
||||
}
|
||||
|
||||
describe("isNoSnapshotErrorResult", () => {
|
||||
it("matches the StarRocks Paimon no-snapshot error surfaced via executeTabSql", () => {
|
||||
const result = errorResult("Server error: `ERROR HY000 (1064): There is currently no snapshot.`");
|
||||
expect(isNoSnapshotErrorResult(result)).toBe(true);
|
||||
});
|
||||
|
||||
it("matches case-insensitively", () => {
|
||||
const result = errorResult("there IS currently NO snapshot");
|
||||
expect(isNoSnapshotErrorResult(result)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not match unrelated query errors", () => {
|
||||
expect(isNoSnapshotErrorResult(errorResult("Unknown table 'tag_test.record_tag_t'"))).toBe(false);
|
||||
expect(isNoSnapshotErrorResult(errorResult("ERROR 1142: SELECT command denied"))).toBe(false);
|
||||
});
|
||||
|
||||
it("does not match successful data results", () => {
|
||||
expect(isNoSnapshotErrorResult(dataResult(["id", "name"]))).toBe(false);
|
||||
expect(isNoSnapshotErrorResult(dataResult(["Error"]))).toBe(false); // data column literally named Error, no rows
|
||||
});
|
||||
|
||||
it("returns false for missing or empty results", () => {
|
||||
expect(isNoSnapshotErrorResult(undefined)).toBe(false);
|
||||
expect(isNoSnapshotErrorResult(null)).toBe(false);
|
||||
expect(isNoSnapshotErrorResult({ ...errorResult("There is currently no snapshot."), rows: [] })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import type { QueryResult } from "@/types/database";
|
||||
|
||||
// Lake/external tables (e.g. Paimon in StarRocks) return this error on a data
|
||||
// read when no snapshot exists yet, while metadata reads (DESC/SHOW CREATE)
|
||||
// still succeed. executeTabSql surfaces query failures as an "Error" result, so
|
||||
// callers can detect this case and fall back to a structure-only (LIMIT 0)
|
||||
// preview instead of showing a cryptic server error.
|
||||
const NO_SNAPSHOT_ERROR_PATTERN = /there is currently no snapshot/i;
|
||||
|
||||
export function isNoSnapshotErrorResult(result: QueryResult | undefined | null): boolean {
|
||||
if (!result || !result.columns.includes("Error") || result.rows.length === 0) return false;
|
||||
return NO_SNAPSHOT_ERROR_PATTERN.test(String(result.rows[0]?.[0] ?? ""));
|
||||
}
|
||||
Loading…
Reference in New Issue