revert(data-grid): remove configurable default sorting
This commit is contained in:
1 parent
2e5b439c8a
commit
ed6192842d
24 files changed
+17
-720
No files matched your search
@@ -14,9 +14,7 @@ const mocks = vi.hoisted(() => ({
|
||||
getColumns: vi.fn(),
|
||||
listIndexes: vi.fn(),
|
||||
ensureConnected: vi.fn(),
|
||||
sortTabResultLocally: vi.fn(),
|
||||
tableOpenPageSize: 100,
|
||||
openTableDefaultSortMode: "none" as "none" | "primary-key-asc" | "primary-key-desc",
|
||||
tabs: [] as QueryTab[],
|
||||
setTableMeta: vi.fn(),
|
||||
}));
|
||||
@@ -40,7 +38,6 @@ vi.mock("@/stores/connectionStore", () => ({
|
||||
useConnectionStore: () => ({
|
||||
getConfig: mocks.getConfig,
|
||||
ensureConnected: mocks.ensureConnected,
|
||||
connectionIdentifierQuote: () => undefined,
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -49,7 +46,6 @@ vi.mock("@/stores/queryStore", () => ({
|
||||
executeTabSql: mocks.executeTabSql,
|
||||
setExecuting: mocks.setExecuting,
|
||||
updateSql: mocks.updateSql,
|
||||
sortTabResultLocally: mocks.sortTabResultLocally,
|
||||
tabs: mocks.tabs,
|
||||
setTableMeta: mocks.setTableMeta.mockImplementation((id: string, meta: NonNullable<QueryTab["tableMeta"]>) => {
|
||||
const tab = mocks.tabs.find((item) => item.id === id);
|
||||
@@ -64,14 +60,7 @@ vi.mock("@/stores/queryStore", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/settingsStore", () => ({
|
||||
useSettingsStore: () => ({
|
||||
editorSettings: {
|
||||
tableOpenPageSize: mocks.tableOpenPageSize,
|
||||
openTableDefaultSortMode: mocks.openTableDefaultSortMode,
|
||||
infiniteScroll: false,
|
||||
infiniteScrollMaxRows: 5000,
|
||||
},
|
||||
}),
|
||||
useSettingsStore: () => ({ editorSettings: { tableOpenPageSize: mocks.tableOpenPageSize } }),
|
||||
}));
|
||||
|
||||
vi.mock("@/composables/useToast", () => ({
|
||||
@@ -109,7 +98,6 @@ describe("useDataGridActions", () => {
|
||||
vi.clearAllMocks();
|
||||
mocks.tabs.length = 0;
|
||||
mocks.tableOpenPageSize = 100;
|
||||
mocks.openTableDefaultSortMode = "none";
|
||||
mocks.getConfig.mockReturnValue({ id: "postgres-1", db_type: "postgres" });
|
||||
mocks.buildTableSelectSql.mockResolvedValue("SELECT * FROM public.users LIMIT 100 OFFSET 0");
|
||||
mocks.buildSortedQuerySql.mockResolvedValue({ ok: true, sql: "SELECT sorted" });
|
||||
@@ -317,64 +305,4 @@ describe("useDataGridActions", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("rebuilds composite primary-key ordering for toolbar refresh and pagination", async () => {
|
||||
mocks.openTableDefaultSortMode = "primary-key-desc";
|
||||
const tab = tableDataTab({
|
||||
tableMeta: {
|
||||
schema: "public",
|
||||
tableName: "users",
|
||||
tableType: "TABLE",
|
||||
columns: [
|
||||
{ name: "tenant_id", data_type: "text", is_nullable: false, column_default: null, is_primary_key: true, extra: null },
|
||||
{ name: "record_id", data_type: "bigint", is_nullable: false, column_default: null, is_primary_key: true, extra: null },
|
||||
],
|
||||
primaryKeys: ["tenant_id", "record_id"],
|
||||
},
|
||||
openTableDefaultSortApplied: true,
|
||||
openTableDefaultSortOrderBy: '"tenant_id" ASC, "record_id" ASC',
|
||||
orderByInput: '"tenant_id" ASC, "record_id" ASC',
|
||||
});
|
||||
const actions = useDataGridActions(computed(() => tab));
|
||||
|
||||
await actions.onReloadData(tab.sql, "", "", tab.orderByInput, 25, 50, "refresh");
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
orderBy: '"tenant_id" DESC, "record_id" DESC',
|
||||
limit: 25,
|
||||
offset: 50,
|
||||
}),
|
||||
);
|
||||
|
||||
await actions.onPaginate(75, 25, "", tab.orderByInput);
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
orderBy: '"tenant_id" DESC, "record_id" DESC',
|
||||
limit: 25,
|
||||
offset: 75,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("sorts locally without issuing a database query", async () => {
|
||||
const tab = tableDataTab();
|
||||
const actions = useDataGridActions(computed(() => tab));
|
||||
|
||||
await actions.onSort("id", 0, "asc", "", "local");
|
||||
|
||||
expect(mocks.sortTabResultLocally).toHaveBeenCalledWith("tab-1", "id", 0, "asc");
|
||||
expect(mocks.buildTableSelectSql).not.toHaveBeenCalled();
|
||||
expect(mocks.executeTabSql).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sorts in the database by rebuilding and executing table SQL", async () => {
|
||||
const tab = tableDataTab();
|
||||
const actions = useDataGridActions(computed(() => tab));
|
||||
|
||||
await actions.onSort("id", 0, "desc", "", "database");
|
||||
|
||||
expect(mocks.sortTabResultLocally).not.toHaveBeenCalled();
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.objectContaining({ orderBy: '"id" DESC' }));
|
||||
expect(mocks.executeTabSql).toHaveBeenCalledWith("tab-1", expect.any(String), expect.objectContaining({ preserveResultDuringExecution: true }));
|
||||
});
|
||||
});
|
||||
@@ -9,11 +9,9 @@ const mocks = vi.hoisted(() => ({
|
||||
cachedMetadata: undefined as unknown,
|
||||
ensureConnected: vi.fn(),
|
||||
executeTabSql: vi.fn(),
|
||||
buildTableSelectSql: vi.fn(),
|
||||
loadTableMetadata: vi.fn(),
|
||||
setErrorResult: vi.fn(),
|
||||
cancelTabExecution: vi.fn(),
|
||||
openTableDefaultSortMode: "none" as "none" | "primary-key-asc" | "primary-key-desc",
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/connectionStore", () => ({
|
||||
@@ -71,7 +69,7 @@ vi.mock("@/stores/queryStore", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/settingsStore", () => ({
|
||||
useSettingsStore: () => ({ editorSettings: { reuseDataTab: false, tableOpenPageSize: 100, openTableDefaultSortMode: mocks.openTableDefaultSortMode } }),
|
||||
useSettingsStore: () => ({ editorSettings: { reuseDataTab: false, pageSize: 100 } }),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/database/jdbcDialect", () => ({
|
||||
@@ -94,10 +92,7 @@ vi.mock("@/lib/backend/debugLog", () => ({ appendDebugLog: vi.fn(), isDebugLoggi
|
||||
// dataTabOpenPolicy 使用真实实现:beforeEach 清空 tabs 时无候选可复用,
|
||||
// 取消窗口测试则依赖真实 findExistingDataTabCandidate 选中同表 tab
|
||||
vi.mock("@/lib/sidebar/treeNodeContext", () => ({ hasTreeNodeDatabaseContext: () => true }));
|
||||
vi.mock("@/lib/table/tableSelectSql", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/lib/table/tableSelectSql")>()),
|
||||
buildTableSelectSql: mocks.buildTableSelectSql,
|
||||
}));
|
||||
vi.mock("@/lib/table/tableSelectSql", () => ({ buildTableSelectSql: async () => "SELECT * FROM users" }));
|
||||
vi.mock("@/lib/table/tableEditing", () => ({ usesSyntheticRowIdKey: () => false }));
|
||||
vi.mock("@/lib/table/tableOpenPageLimit", () => ({ tableOpenPageLimit: () => 100 }));
|
||||
vi.mock("@/lib/tabs/dataTabActivation", () => ({ canActivateExistingDataTableTab: () => false }));
|
||||
@@ -119,9 +114,7 @@ describe("useSidebarDataOpenRuntime", () => {
|
||||
mocks.callOrder.length = 0;
|
||||
mocks.tabs.length = 0;
|
||||
mocks.cachedMetadata = undefined;
|
||||
mocks.openTableDefaultSortMode = "none";
|
||||
mocks.ensureConnected.mockResolvedValue(undefined);
|
||||
mocks.buildTableSelectSql.mockResolvedValue("SELECT * FROM users");
|
||||
mocks.executeTabSql.mockImplementation(async () => {
|
||||
mocks.callOrder.push("query");
|
||||
});
|
||||
@@ -314,43 +307,4 @@ describe("useSidebarDataOpenRuntime", () => {
|
||||
expect(mocks.tabs[0]?.tableMetaPending).toBeFalsy();
|
||||
expect(mocks.loadTableMetadata).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("loads composite primary-key metadata before the first query when default ordering is enabled", async () => {
|
||||
mocks.databaseType = "postgres";
|
||||
mocks.openTableDefaultSortMode = "primary-key-asc";
|
||||
mocks.loadTableMetadata.mockResolvedValueOnce({
|
||||
metadata: {
|
||||
schema: "public",
|
||||
tableName: "users",
|
||||
tableType: "TABLE",
|
||||
database: "app",
|
||||
columns: [
|
||||
{ name: "Tenant", data_type: "text", is_nullable: false, column_default: null, is_primary_key: true, extra: null },
|
||||
{ name: "Order", data_type: "bigint", is_nullable: false, column_default: null, is_primary_key: true, extra: null },
|
||||
],
|
||||
indexes: [],
|
||||
primaryKeys: ["Tenant", "Order"],
|
||||
cachedAt: Date.now(),
|
||||
},
|
||||
cacheStatus: "miss",
|
||||
ageMs: 0,
|
||||
});
|
||||
|
||||
await useSidebarDataOpenRuntime().openData(tableNode);
|
||||
|
||||
expect(mocks.loadTableMetadata).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
primaryKeys: ["Tenant", "Order"],
|
||||
orderBy: '"Tenant" ASC, "Order" ASC',
|
||||
}),
|
||||
);
|
||||
expect(mocks.tabs[0]).toMatchObject({
|
||||
resultSortColumn: "Tenant",
|
||||
resultSortDirection: "asc",
|
||||
resultSortMode: "database",
|
||||
orderByInput: '"Tenant" ASC, "Order" ASC',
|
||||
openTableDefaultSortApplied: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,6 @@ import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql";
|
||||
import { applyOpenTableDefaultSortState, buildOpenTableDefaultSort, clearOpenTableDefaultSortMarker, shouldApplyOpenTableDefaultSort } from "@/lib/table/tableDefaultSort";
|
||||
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
import { usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
import { tableMetaForDataTab } from "@/lib/table/tableDataTabMeta";
|
||||
@@ -72,31 +71,6 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
|
||||
});
|
||||
}
|
||||
|
||||
function openTableDefaultSortForTab(tab: QueryTab) {
|
||||
const config = connectionStore.getConfig(tab.connectionId);
|
||||
const tableMeta = tableMetaForDataTab(tab);
|
||||
const primaryKeys = tab.tableMeta ? tab.tableMeta.primaryKeys : (tableMeta?.primaryKeys ?? []);
|
||||
return buildOpenTableDefaultSort({
|
||||
mode: settingsStore.editorSettings.openTableDefaultSortMode,
|
||||
databaseType: effectiveDatabaseTypeForConnection(config),
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(tab.connectionId),
|
||||
primaryKeys,
|
||||
columns: tableMeta?.columns.map((column) => column.name) ?? [],
|
||||
});
|
||||
}
|
||||
|
||||
function resolveTableOrderBy(tab: QueryTab, orderBy?: string): string | undefined {
|
||||
if (shouldApplyOpenTableDefaultSort(tab, orderBy)) {
|
||||
const defaultSort = openTableDefaultSortForTab(tab);
|
||||
applyOpenTableDefaultSortState(tab, defaultSort);
|
||||
return defaultSort?.orderBy;
|
||||
}
|
||||
if (orderBy?.trim() !== tab.openTableDefaultSortOrderBy?.trim()) {
|
||||
clearOpenTableDefaultSortMarker(tab);
|
||||
}
|
||||
return orderBy;
|
||||
}
|
||||
|
||||
async function refreshDataTabTableMeta(tab: QueryTab, trace?: { traceId: string; elapsed: () => string }): Promise<void> {
|
||||
if (tab.mode !== "data" || !tab.connectionId || !tab.database) return;
|
||||
const tableMeta = tableMetaForDataTab(tab);
|
||||
@@ -205,8 +179,7 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
|
||||
}
|
||||
try {
|
||||
console.info("[DBX][reloadData:build-sql:start]", { traceId, elapsed: elapsed() });
|
||||
const nextOrderBy = resolveTableOrderBy(tab, orderBy);
|
||||
const nextSql = await buildTableSql(tab, { whereInput, orderBy: nextOrderBy, limit: pageLimit, offset: pageOffset });
|
||||
const nextSql = await buildTableSql(tab, { whereInput, orderBy, limit: pageLimit, offset: pageOffset });
|
||||
console.info("[DBX][reloadData:build-sql:done]", { traceId, elapsed: elapsed() });
|
||||
queryStore.updateSql(tab.id, nextSql);
|
||||
console.info("[DBX][reloadData:execute:start]", { traceId, elapsed: elapsed() });
|
||||
@@ -309,8 +282,7 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
|
||||
|
||||
if (!tableMetaForDataTab(tab)) return;
|
||||
tab.whereInput = whereInput ?? "";
|
||||
const nextOrderBy = resolveTableOrderBy(tab, orderBy);
|
||||
const sql = await buildTableSql(tab, { limit, offset, whereInput, orderBy: nextOrderBy });
|
||||
const sql = await buildTableSql(tab, { limit, offset, whereInput, orderBy });
|
||||
queryStore.updateSql(tab.id, sql);
|
||||
await queryStore.executeTabSql(tab.id, sql, {
|
||||
pagination: { offset, limit },
|
||||
@@ -327,7 +299,6 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
|
||||
tab.resultSortColumnIndex = direction ? columnIndex : undefined;
|
||||
tab.resultSortDirection = direction ?? undefined;
|
||||
tab.resultSortMode = direction ? mode : undefined;
|
||||
clearOpenTableDefaultSortMarker(tab);
|
||||
|
||||
if (mode === "local") {
|
||||
if (tab.mode === "data") {
|
||||
|
||||
@@ -11,7 +11,6 @@ import { canApplyDataTabMetadata, dataTabMetadataNeedsRefresh, findExistingDataT
|
||||
import type { SidebarDataOpenRequest } from "@/lib/sidebar/sidebarDataOpenCoordinator";
|
||||
import { hasTreeNodeDatabaseContext } from "@/lib/sidebar/treeNodeContext";
|
||||
import { buildTableSelectSql } from "@/lib/table/tableSelectSql";
|
||||
import { applyOpenTableDefaultSortState, buildOpenTableDefaultSort } from "@/lib/table/tableDefaultSort";
|
||||
import { usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
import { canActivateExistingDataTableTab } from "@/lib/tabs/dataTabActivation";
|
||||
@@ -142,8 +141,6 @@ export function useSidebarDataOpenRuntime() {
|
||||
tab.resultLocalSortOriginalMongoDocuments = undefined;
|
||||
tab.resultLocalSortOriginalMongoCopyDocuments = undefined;
|
||||
tab.resultSortedSql = undefined;
|
||||
tab.openTableDefaultSortApplied = undefined;
|
||||
tab.openTableDefaultSortOrderBy = undefined;
|
||||
tab.resultPageSql = undefined;
|
||||
tab.resultPageLimit = undefined;
|
||||
tab.resultPageOffset = undefined;
|
||||
@@ -277,48 +274,22 @@ export function useSidebarDataOpenRuntime() {
|
||||
if (!config) throw new Error("Connection config not found");
|
||||
|
||||
const limit = tableOpenPageLimit(settingsStore.editorSettings.tableOpenPageSize);
|
||||
const openTableDefaultSortMode = settingsStore.editorSettings.openTableDefaultSortMode;
|
||||
let tableMetaForQuery = cachedTableMeta;
|
||||
let shouldRefreshTableMeta = !tableMetaForQuery;
|
||||
const shouldRefreshTableMeta = !cachedTableMeta;
|
||||
// Dameng metadata calls must remain serialized behind the table query.
|
||||
const deferTableMetaRefresh = effectiveDbType === "dameng";
|
||||
let attemptedDefaultSortMetadataLoad = false;
|
||||
if (openTableDefaultSortMode !== "none" && !tableMetaForQuery && !deferTableMetaRefresh) {
|
||||
attemptedDefaultSortMetadataLoad = true;
|
||||
try {
|
||||
const loadedMetadata = await loadTableMetadata({
|
||||
connectionId: node.connectionId,
|
||||
database: node.database,
|
||||
schema: querySchema,
|
||||
tableName: node.label,
|
||||
tableType,
|
||||
databaseType: metadataDatabaseType,
|
||||
driverProfile: config.driver_profile || config.db_type,
|
||||
catalog: node.catalog,
|
||||
traceLogger: isDebugLoggingEnabled() ? (event) => openDataLog("debug", "metadata:trace", { sourceTraceId: traceId, ...event }) : undefined,
|
||||
});
|
||||
if (!isActive() || !canApplyTableMetadata(tabId)) return;
|
||||
tableMetaForQuery = tableMetadataToDataTabMeta(loadedMetadata.metadata, tableSchema);
|
||||
queryStore.setTableMeta(tabId, tableMetaForQuery);
|
||||
shouldRefreshTableMeta = false;
|
||||
logPhase("metadata-loaded-for-default-sort", { tabId, columnCount: tableMetaForQuery.columns.length, primaryKeyCount: tableMetaForQuery.primaryKeys.length });
|
||||
} catch (error) {
|
||||
openDataLog("warn", "metadata:default-sort:error", { traceId, tabId, elapsed: elapsed(), error });
|
||||
}
|
||||
}
|
||||
if (tableMetaForQuery) {
|
||||
if (cachedTableMeta) {
|
||||
openDataLog("info", "metadata:cache-hit", {
|
||||
traceId,
|
||||
tabId,
|
||||
columnCount: tableMetaForQuery.columns.length,
|
||||
primaryKeyCount: tableMetaForQuery.primaryKeys.length,
|
||||
columnCount: cachedTableMeta.columns.length,
|
||||
primaryKeyCount: cachedTableMeta.primaryKeys.length,
|
||||
source: cachedTableMetaSource,
|
||||
ageMs: Math.round(cachedTableMetaAgeMs),
|
||||
elapsed: elapsed(),
|
||||
});
|
||||
} else if (deferTableMetaRefresh) {
|
||||
logPhase("metadata-deferred", { tabId });
|
||||
} else if (!attemptedDefaultSortMetadataLoad) {
|
||||
} else {
|
||||
void refreshTableMetaInBackground(tabId);
|
||||
logPhase("metadata-started", { tabId });
|
||||
}
|
||||
@@ -329,15 +300,8 @@ export function useSidebarDataOpenRuntime() {
|
||||
return;
|
||||
}
|
||||
|
||||
const columns = tableMetaForQuery?.columns ?? [];
|
||||
const primaryKeys = tableMetaForQuery?.primaryKeys ?? [];
|
||||
const defaultSort = buildOpenTableDefaultSort({
|
||||
mode: openTableDefaultSortMode,
|
||||
databaseType: effectiveDbType,
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(node.connectionId),
|
||||
primaryKeys,
|
||||
columns: columns.map((column) => column.name),
|
||||
});
|
||||
const columns = cachedTableMeta?.columns ?? [];
|
||||
const primaryKeys = cachedTableMeta?.primaryKeys ?? [];
|
||||
const includeRowId = usesSyntheticRowIdKey(effectiveDbType, primaryKeys, tableType);
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
@@ -349,7 +313,6 @@ export function useSidebarDataOpenRuntime() {
|
||||
catalog: node.catalog,
|
||||
columns: columns.map((column) => column.name),
|
||||
primaryKeys,
|
||||
orderBy: defaultSort?.orderBy,
|
||||
limit,
|
||||
includeRowId,
|
||||
});
|
||||
@@ -376,8 +339,6 @@ export function useSidebarDataOpenRuntime() {
|
||||
skipEnsureConnected: true,
|
||||
pagination: { limit, offset: 0 },
|
||||
});
|
||||
const currentTab = queryStore.tabs.find((item) => item.id === tabId);
|
||||
if (currentTab) applyOpenTableDefaultSortState(currentTab, defaultSort);
|
||||
openDataLog("info", "execute:done", { traceId, tabId, elapsed: elapsed() });
|
||||
logPhase("execute-tab-sql", { tabId });
|
||||
if (shouldRefreshTableMeta && deferTableMetaRefresh && canApplyTableMetadata(tabId)) {
|
||||
|
||||
Reference in new issue
Block a user