feat(editor): configurable default sort when opening tables
This commit is contained in:
25 files changed
+417
-23
No files matched your search
@@ -8,11 +8,13 @@ const mocks = vi.hoisted(() => ({
|
||||
activeTabId: "",
|
||||
buildTableSelectSql: vi.fn(),
|
||||
databaseType: "postgres" as string,
|
||||
tableOpenSortMode: "none" as "none" | "database" | "local",
|
||||
ensureConnected: vi.fn(),
|
||||
executeTabSql: vi.fn(),
|
||||
getColumns: vi.fn(),
|
||||
invalidateCompletionTableCache: vi.fn(),
|
||||
listIndexes: vi.fn(),
|
||||
setErrorResult: vi.fn(),
|
||||
setTableMeta: vi.fn(),
|
||||
updateSql: vi.fn(),
|
||||
}));
|
||||
@@ -75,16 +77,17 @@ vi.mock("@/stores/queryStore", () => ({
|
||||
}),
|
||||
updateSql: mocks.updateSql,
|
||||
executeTabSql: mocks.executeTabSql,
|
||||
setErrorResult: vi.fn(),
|
||||
setErrorResult: mocks.setErrorResult,
|
||||
invalidateTableStructure: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/settingsStore", () => ({
|
||||
useSettingsStore: () => ({ editorSettings: { dataTabReuseMode: "same-table" } }),
|
||||
useSettingsStore: () => ({ editorSettings: { dataTabReuseMode: "same-table", tableOpenSortMode: mocks.tableOpenSortMode, tableDatabaseSortDirection: "desc", tableLocalSortDirection: "asc" } }),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/table/tableSelectSql", () => ({
|
||||
vi.mock("@/lib/table/tableSelectSql", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/lib/table/tableSelectSql")>()),
|
||||
buildTableSelectSql: mocks.buildTableSelectSql,
|
||||
}));
|
||||
|
||||
@@ -104,6 +107,7 @@ describe("useNavigationTargets openTableTarget", () => {
|
||||
vi.clearAllMocks();
|
||||
mocks.tabs.length = 0;
|
||||
mocks.databaseType = "postgres";
|
||||
mocks.tableOpenSortMode = "none";
|
||||
mocks.ensureConnected.mockResolvedValue(undefined);
|
||||
mocks.buildTableSelectSql.mockImplementation(async ({ tableName }: { tableName: string }) => `SELECT * FROM ${tableName}`);
|
||||
mocks.executeTabSql.mockResolvedValue(undefined);
|
||||
@@ -111,6 +115,33 @@ describe("useNavigationTargets openTableTarget", () => {
|
||||
mocks.listIndexes.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
it.each(["oracle", "oceanbase-oracle", "xugu", "neo4j"])("keeps %s synthetic row identifiers out of default database ordering", async (databaseType) => {
|
||||
mocks.databaseType = databaseType;
|
||||
mocks.tableOpenSortMode = "database";
|
||||
mocks.getColumns.mockResolvedValue([{ ...column("name"), is_primary_key: false }]);
|
||||
|
||||
await useNavigationTargets(dialogs).openTableTarget({ connectionId: "connection-1", database: "app", tableName: "users", tableType: "TABLE" });
|
||||
|
||||
expect(mocks.executeTabSql).toHaveBeenCalled();
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalled();
|
||||
for (const [options] of mocks.buildTableSelectSql.mock.calls) {
|
||||
expect(options.orderBy).toBeUndefined();
|
||||
}
|
||||
expect(mocks.tabs[0]?.orderByInput).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each(["oracle", "oceanbase-oracle"])("uses the %s physical primary index for default ordering", async (databaseType) => {
|
||||
mocks.databaseType = databaseType;
|
||||
mocks.tableOpenSortMode = "database";
|
||||
mocks.getColumns.mockResolvedValue([{ ...column("id"), is_primary_key: false }]);
|
||||
mocks.listIndexes.mockResolvedValue([{ name: "users_pk", columns: ["id"], is_unique: true, is_primary: true }]);
|
||||
|
||||
await useNavigationTargets(dialogs).openTableTarget({ connectionId: "connection-1", database: "app", tableName: "users", tableType: "TABLE" });
|
||||
|
||||
expect(mocks.setErrorResult).not.toHaveBeenCalled();
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.objectContaining({ orderBy: '"id" DESC' }));
|
||||
});
|
||||
|
||||
it("loads stable row identity before the first PostgreSQL table query", async () => {
|
||||
let releaseColumns: (columns: unknown[]) => void = () => {};
|
||||
mocks.getColumns.mockReturnValueOnce(
|
||||
@@ -162,7 +193,7 @@ describe("useNavigationTargets openTableTarget", () => {
|
||||
});
|
||||
|
||||
it("keeps row identity pending when shared metadata loading fails", async () => {
|
||||
mocks.getColumns.mockRejectedValueOnce(new Error("metadata unavailable"));
|
||||
mocks.getColumns.mockRejectedValue(new Error("metadata unavailable"));
|
||||
|
||||
await useNavigationTargets(dialogs).openLineageTarget({ connectionId: "connection-1", database: "app", schema: "public", tableName: "users" });
|
||||
|
||||
@@ -170,6 +201,17 @@ describe("useNavigationTargets openTableTarget", () => {
|
||||
expect(mocks.tabs[0]?.tableMetaPending).toBe(true);
|
||||
});
|
||||
|
||||
it.each(["none", "database", "local"] as const)("opens the table when metadata preload fails in %s sort mode", async (mode) => {
|
||||
mocks.databaseType = "sqlite";
|
||||
mocks.tableOpenSortMode = mode;
|
||||
mocks.getColumns.mockRejectedValue(new Error("metadata unavailable"));
|
||||
|
||||
await useNavigationTargets(dialogs).openLineageTarget({ connectionId: "connection-1", database: "app", tableName: "users" });
|
||||
|
||||
expect(mocks.executeTabSql).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.tabs[0]?.tableMetaPending).toBe(true);
|
||||
});
|
||||
|
||||
it("uses the SQLite attached database as the table namespace when a target omits schema", async () => {
|
||||
mocks.databaseType = "sqlite";
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { QueryTab, TreeNode } from "@/types/database";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
databaseType: "oceanbase" as string,
|
||||
tableOpenSortMode: "none" as "none" | "database" | "local",
|
||||
sortTabResultLocally: vi.fn(),
|
||||
callOrder: [] as string[],
|
||||
tabs: [] as QueryTab[],
|
||||
activeTabId: null as string | null,
|
||||
@@ -90,12 +92,13 @@ vi.mock("@/stores/queryStore", () => ({
|
||||
if (tab) tab.sql = sql;
|
||||
},
|
||||
executeTabSql: mocks.executeTabSql,
|
||||
sortTabResultLocally: mocks.sortTabResultLocally,
|
||||
setErrorResult: mocks.setErrorResult,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/settingsStore", () => ({
|
||||
useSettingsStore: () => ({ editorSettings: { dataTabReuseMode: mocks.dataTabReuseMode, openDataTabsNextToActive: mocks.openDataTabsNextToActive, pageSize: 100 } }),
|
||||
useSettingsStore: () => ({ editorSettings: { tableOpenSortMode: mocks.tableOpenSortMode, tableDatabaseSortDirection: "desc", tableLocalSortDirection: "asc", dataTabReuseMode: mocks.dataTabReuseMode, openDataTabsNextToActive: mocks.openDataTabsNextToActive, pageSize: 100 } }),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/database/jdbcDialect", () => ({
|
||||
@@ -117,8 +120,12 @@ vi.mock("@/lib/common/utils", () => ({ uuid: () => "open-data-id" }));
|
||||
vi.mock("@/lib/backend/debugLog", () => ({ appendDebugLog: vi.fn(), isDebugLoggingEnabled: () => false }));
|
||||
// dataTabOpenPolicy 使用真实实现,覆盖设置开关对应的复用范围
|
||||
vi.mock("@/lib/sidebar/treeNodeContext", () => ({ hasTreeNodeDatabaseContext: () => true }));
|
||||
vi.mock("@/lib/table/tableSelectSql", () => ({ buildTableSelectSql: mocks.buildTableSelectSql }));
|
||||
vi.mock("@/lib/table/tableSelectSql", async (importOriginal) => ({ ...(await importOriginal<typeof import("@/lib/table/tableSelectSql")>()), buildTableSelectSql: mocks.buildTableSelectSql }));
|
||||
vi.mock("@/lib/table/tableEditing", () => ({
|
||||
physicalTablePrimaryKeys: (columns: Array<{ name: string; is_primary_key: boolean }>, indexes: Array<{ columns: string[]; is_primary: boolean }> = []) => {
|
||||
const columnPrimaryKeys = columns.filter((column) => column.is_primary_key).map((column) => column.name);
|
||||
return columnPrimaryKeys.length > 0 ? columnPrimaryKeys : (indexes.find((index) => index.is_primary && index.columns.length > 0)?.columns ?? []);
|
||||
},
|
||||
usesSyntheticRowIdKey: () => false,
|
||||
shouldIncludeSyntheticRowId: () => false,
|
||||
}));
|
||||
@@ -147,6 +154,7 @@ describe("useSidebarDataOpenRuntime", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.databaseType = "oceanbase";
|
||||
mocks.tableOpenSortMode = "none";
|
||||
mocks.callOrder.length = 0;
|
||||
mocks.tabs.length = 0;
|
||||
mocks.activeTabId = null;
|
||||
@@ -178,6 +186,48 @@ describe("useSidebarDataOpenRuntime", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["database", "local"] as const)("applies the %s default using its independent direction", async (mode) => {
|
||||
mocks.tableOpenSortMode = mode;
|
||||
mocks.executeTabSql.mockImplementation(async () => {
|
||||
const tab = mocks.tabs[0]!;
|
||||
tab.isExecuting = false;
|
||||
tab.executionId = undefined;
|
||||
tab.result = { columns: ["id"], rows: [[2], [1]], affected_rows: 0, execution_time_ms: 0 } as any;
|
||||
});
|
||||
await useSidebarDataOpenRuntime().openData(tableNode);
|
||||
if (mode === "database") {
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.objectContaining({ orderBy: '"id" DESC' }));
|
||||
expect(mocks.tabs[0]).toMatchObject({ resultSortColumn: "id", resultSortDirection: "desc", resultSortMode: "database" });
|
||||
} else {
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.objectContaining({ orderBy: undefined }));
|
||||
expect(mocks.sortTabResultLocally).toHaveBeenCalledWith("tab-1", "id", 0, "asc");
|
||||
}
|
||||
});
|
||||
|
||||
it("applies database sorting from a physical primary index when columns omit the key flag", async () => {
|
||||
mocks.databaseType = "oracle";
|
||||
mocks.tableOpenSortMode = "database";
|
||||
mocks.loadTableMetadata.mockResolvedValue({
|
||||
metadata: {
|
||||
schema: "public",
|
||||
tableName: "users",
|
||||
tableType: "TABLE",
|
||||
database: "app",
|
||||
columns: [{ name: "id", data_type: "bigint", is_nullable: false, column_default: null, is_primary_key: false, extra: null }],
|
||||
indexes: [{ name: "users_pk", columns: ["id"], is_unique: true, is_primary: true }],
|
||||
primaryKeys: ["id"],
|
||||
cachedAt: Date.now(),
|
||||
},
|
||||
cacheStatus: "miss",
|
||||
ageMs: 0,
|
||||
});
|
||||
|
||||
await useSidebarDataOpenRuntime().openData(tableNode);
|
||||
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.objectContaining({ orderBy: '"id" DESC' }));
|
||||
expect(mocks.tabs[0]?.tableMeta?.physicalPrimaryKeys).toEqual(["id"]);
|
||||
});
|
||||
|
||||
it("creates a new sidebar tab for the same table in always-new mode", async () => {
|
||||
mocks.dataTabReuseMode = "always-new";
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import { invalidateTableMetadataCache, loadTableMetadata } from "@/lib/metadata/
|
||||
import { canApplyDataTabMetadata, canReuseActiveMongoTab, type DataTabReuseMode } from "@/lib/sidebar/dataTabOpenPolicy";
|
||||
import { isNoSnapshotErrorResult, isQueryExecutionErrorResult } from "@/lib/query/queryResultError";
|
||||
import { buildTableSelectSql } from "@/lib/table/tableSelectSql";
|
||||
import { resolveTableDefaultSort, applyTableDefaultSortResult } from "@/lib/table/tableDefaultSort";
|
||||
import { tableDataLargeValuePreviewOptions } from "@/lib/dataGrid/dataGridLargeValues";
|
||||
import { editableRowIdentifierColumns, shouldIncludeSyntheticRowId } from "@/lib/table/tableEditing";
|
||||
import { editableRowIdentifierColumns, physicalTablePrimaryKeys, shouldIncludeSyntheticRowId } from "@/lib/table/tableEditing";
|
||||
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
import { beginDataTabNavigation, endDataTabNavigation, isCurrentDataTabNavigation } from "@/lib/tabs/dataTabNavigationGeneration";
|
||||
@@ -142,6 +143,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
if (config.db_type === "neo4j") {
|
||||
const columns = await api.getColumns(target.connectionId, target.database, querySchema, target.tableName);
|
||||
const primaryKeys = editableRowIdentifierColumns(effectiveDbType, columns, undefined, targetTableType);
|
||||
const defaultSort = resolveTableDefaultSort(settingsStore.editorSettings, effectiveDbType, physicalTablePrimaryKeys(columns), identifierQuote);
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: config.driver_profile,
|
||||
@@ -155,6 +157,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
tableType: targetTableType,
|
||||
columns: columns.map((column) => column.name),
|
||||
primaryKeys,
|
||||
orderBy: defaultSort.orderBy,
|
||||
whereInput: target.whereInput,
|
||||
limit: pageLimit,
|
||||
});
|
||||
@@ -170,24 +173,34 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
primaryKeys,
|
||||
});
|
||||
firstExecuteStarted = true;
|
||||
if (targetTab) targetTab.orderByInput = defaultSort.orderBy;
|
||||
await queryStore.executeTabSql(tabId, sql, { pagination: { limit: pageLimit, offset: 0 } });
|
||||
if (isCurrentTarget() && targetTab) applyTableDefaultSortResult(targetTab, defaultSort, queryStore.sortTabResultLocally);
|
||||
return;
|
||||
}
|
||||
const eagerMetadata =
|
||||
effectiveDbType === "mysql" || effectiveDbType === "postgres"
|
||||
? await loadTableMetadata({
|
||||
connectionId: target.connectionId,
|
||||
database: target.database,
|
||||
schema: querySchema,
|
||||
tableName: target.tableName,
|
||||
tableType: targetTableType,
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: config.driver_profile || config.db_type,
|
||||
catalog: target.catalog,
|
||||
})
|
||||
: undefined;
|
||||
let eagerMetadata: Awaited<ReturnType<typeof loadTableMetadata>> | undefined;
|
||||
if (effectiveDbType === "mysql" || effectiveDbType === "postgres" || (settingsStore.editorSettings.tableOpenSortMode ?? "none") !== "none") {
|
||||
try {
|
||||
eagerMetadata = await loadTableMetadata({
|
||||
connectionId: target.connectionId,
|
||||
database: target.database,
|
||||
schema: querySchema,
|
||||
tableName: target.tableName,
|
||||
tableType: targetTableType,
|
||||
databaseType: effectiveDbType ?? config.db_type,
|
||||
driverProfile: config.driver_profile || config.db_type,
|
||||
catalog: target.catalog,
|
||||
});
|
||||
} catch (error) {
|
||||
// Metadata is needed for optional default sorting, but it must not
|
||||
// prevent the data preview from opening. The later metadata refresh
|
||||
// keeps the tab pending for edits and can retry independently.
|
||||
console.warn("[DBX] unable to preload table metadata for default sort", error);
|
||||
}
|
||||
}
|
||||
const eagerColumns = eagerMetadata?.metadata.columns ?? [];
|
||||
const eagerPrimaryKeys = eagerMetadata?.metadata.primaryKeys ?? [];
|
||||
const defaultSort = resolveTableDefaultSort(settingsStore.editorSettings, effectiveDbType, physicalTablePrimaryKeys(eagerColumns, eagerMetadata?.metadata.indexes), identifierQuote);
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: config.driver_profile,
|
||||
@@ -200,6 +213,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
tableType: targetTableType,
|
||||
columns: eagerColumns.map((column) => column.name),
|
||||
primaryKeys: eagerPrimaryKeys,
|
||||
orderBy: defaultSort.orderBy,
|
||||
...tableDataLargeValuePreviewOptions(effectiveDbType, eagerColumns, eagerPrimaryKeys, pageLimit),
|
||||
whereInput: target.whereInput,
|
||||
limit: pageLimit,
|
||||
@@ -219,6 +233,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
// 取消计数快照:isCancelling 是瞬态的(取消失败/查询先完成会被清掉),
|
||||
// 比对计数才能跨越 executeTabSql 生命周期识别"执行期间用户请求过停止"
|
||||
const cancelCountBeforeExecute = queryStore.tabs.find((tab) => tab.id === tabId)?.cancelRequestCount ?? 0;
|
||||
if (targetTab) targetTab.orderByInput = defaultSort.orderBy;
|
||||
await queryStore.executeTabSql(tabId, sql, { pagination: { limit: pageLimit, offset: 0 } });
|
||||
if (!isCurrentTarget()) return;
|
||||
// 首次查询被停止/失败(executeTabSql 以 Error 结果表达,不抛出)时,
|
||||
@@ -228,6 +243,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
const firstResult = tabAfterFirstExecute?.result;
|
||||
const cancelRequestedDuringExecute = (tabAfterFirstExecute?.cancelRequestCount ?? 0) > cancelCountBeforeExecute;
|
||||
const firstQueryFailed = cancelRequestedDuringExecute || tabAfterFirstExecute?.isCancelling === true || (firstResult !== undefined && isQueryExecutionErrorResult(firstResult));
|
||||
if (!firstQueryFailed && targetTab) applyTableDefaultSortResult(targetTab, defaultSort, queryStore.sortTabResultLocally);
|
||||
// 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 +
|
||||
@@ -298,11 +314,13 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
primaryKeys,
|
||||
columns: columns.map((column) => column.name),
|
||||
includeRowId: true,
|
||||
orderBy: defaultSort.orderBy,
|
||||
limit: pageLimit,
|
||||
});
|
||||
if (!isCurrentTarget()) return;
|
||||
queryStore.updateSql(tabId, newSql);
|
||||
await queryStore.executeTabSql(tabId, newSql, { pagination: { limit: pageLimit, offset: 0 } });
|
||||
if (isCurrentTarget() && targetTab) applyTableDefaultSortResult(targetTab, defaultSort, queryStore.sortTabResultLocally);
|
||||
}
|
||||
} catch (reason) {
|
||||
console.error("[DBX] ERROR fetching table metadata:", reason);
|
||||
|
||||
@@ -11,7 +11,8 @@ import { canApplyDataTabMetadata, dataTabMetadataNeedsRefresh, findExistingDataT
|
||||
import type { SidebarDataOpenRequest } from "@/lib/sidebar/sidebarDataOpenCoordinator";
|
||||
import { hasTreeNodeDatabaseContext } from "@/lib/sidebar/treeNodeContext";
|
||||
import { buildTableSelectSql } from "@/lib/table/tableSelectSql";
|
||||
import { shouldIncludeSyntheticRowId } from "@/lib/table/tableEditing";
|
||||
import { resolveTableDefaultSort, applyTableDefaultSortResult } from "@/lib/table/tableDefaultSort";
|
||||
import { physicalTablePrimaryKeys, shouldIncludeSyntheticRowId } from "@/lib/table/tableEditing";
|
||||
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
import { tableDataLargeValuePreviewOptions } from "@/lib/dataGrid/dataGridLargeValues";
|
||||
import { canActivateExistingDataTableTab } from "@/lib/tabs/dataTabActivation";
|
||||
@@ -314,6 +315,7 @@ export function useSidebarDataOpenRuntime() {
|
||||
if (!config) throw new Error("Connection config not found");
|
||||
|
||||
const limit = tableOpenPageLimit(settingsStore.editorSettings.tableOpenPageSize);
|
||||
const defaultSortMode = settingsStore.editorSettings.tableOpenSortMode ?? "none";
|
||||
const shouldRefreshTableMeta = !cachedTableMeta;
|
||||
// Dameng metadata calls must remain serialized behind the table query.
|
||||
const deferTableMetaRefresh = effectiveDbType === "dameng";
|
||||
@@ -334,7 +336,9 @@ export function useSidebarDataOpenRuntime() {
|
||||
}
|
||||
|
||||
const metadataRefresh = shouldRefreshTableMeta && !deferTableMetaRefresh ? refreshTableMetaInBackground(tabId) : undefined;
|
||||
if (!cachedTableMeta && (effectiveDbType === "mysql" || effectiveDbType === "postgres")) {
|
||||
if (!cachedTableMeta && (effectiveDbType === "mysql" || effectiveDbType === "postgres" || defaultSortMode !== "none")) {
|
||||
// No query is in flight yet, so deferred drivers can safely load metadata serially.
|
||||
if (deferTableMetaRefresh) await refreshTableMetaInBackground(tabId);
|
||||
await metadataRefresh;
|
||||
}
|
||||
|
||||
@@ -347,6 +351,8 @@ export function useSidebarDataOpenRuntime() {
|
||||
const loadedTableMeta = cachedTableMeta ?? queryStore.tabs.find((item) => item.id === tabId)?.tableMeta;
|
||||
const columns = loadedTableMeta?.columns ?? [];
|
||||
const primaryKeys = loadedTableMeta?.primaryKeys ?? [];
|
||||
const defaultSort = resolveTableDefaultSort(settingsStore.editorSettings, effectiveDbType, loadedTableMeta?.physicalPrimaryKeys ?? physicalTablePrimaryKeys(columns), connectionStore.connectionIdentifierQuote?.(node.connectionId));
|
||||
const defaultOrderBy = defaultSort.orderBy;
|
||||
const includeRowId = shouldIncludeSyntheticRowId(effectiveDbType, primaryKeys, tableType);
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
@@ -364,6 +370,7 @@ export function useSidebarDataOpenRuntime() {
|
||||
limit,
|
||||
includeRowId,
|
||||
injectDefaultTimeSeriesWhere: true,
|
||||
orderBy: defaultOrderBy,
|
||||
});
|
||||
// SQL 构建是异步后端调用:期间更晚的导航(openData/openTableTarget)
|
||||
// 接管会替换 executionId,旧流程不得再覆盖 SQL/启动查询
|
||||
@@ -380,6 +387,8 @@ export function useSidebarDataOpenRuntime() {
|
||||
});
|
||||
logPhase("sql-built", { tabId, columnCount: columns.length, primaryKeyCount: primaryKeys.length });
|
||||
queryStore.updateSql(tabId, sql);
|
||||
const openedTab = queryStore.tabs.find((item) => item.id === tabId);
|
||||
if (openedTab && defaultOrderBy) openedTab.orderByInput = defaultOrderBy;
|
||||
logPhase("sql-updated", { tabId });
|
||||
|
||||
openDataLog("info", "execute:start", { traceId, tabId, elapsed: elapsed() });
|
||||
@@ -389,6 +398,10 @@ export function useSidebarDataOpenRuntime() {
|
||||
pagination: { limit, offset: 0 },
|
||||
});
|
||||
openDataLog("info", "execute:done", { traceId, tabId, elapsed: elapsed() });
|
||||
const sortedTab = queryStore.tabs.find((item) => item.id === tabId);
|
||||
if ((request?.isCurrent() ?? true) && sortedTab && sortedTab === openedTab && sortedTab.sql === sql) {
|
||||
applyTableDefaultSortResult(sortedTab, defaultSort, queryStore.sortTabResultLocally);
|
||||
}
|
||||
logPhase("execute-tab-sql", { tabId });
|
||||
if (shouldRefreshTableMeta && deferTableMetaRefresh && canApplyTableMetadata(tabId)) {
|
||||
void refreshTableMetaInBackground(tabId);
|
||||
|
||||
Reference in new issue
Block a user