fix(sidebar): cancel stale table search work across modes
This commit is contained in:
1 parent
e671b3da7d
commit
71136377e5
8 files changed
+763
-24
No files matched your search
@@ -126,4 +126,42 @@ describe("connectionStore Doris catalog tree", () => {
|
||||
["iceberg", "sales", "tree.queries"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses the bounded fuzzy-search budget for external catalog tables", async () => {
|
||||
const listTables = vi.fn().mockResolvedValue([{ name: "late_orders", table_type: "BASE TABLE", comment: null }]);
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
|
||||
deleteSchemaCachePrefix: vi.fn().mockResolvedValue(undefined),
|
||||
listTables,
|
||||
loadSchemaCache: vi.fn().mockResolvedValue(null),
|
||||
saveConnections: vi.fn().mockResolvedValue(undefined),
|
||||
saveSchemaCache: vi.fn().mockResolvedValue(undefined),
|
||||
saveSidebarLayout: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const { useSettingsStore } = await import("@/stores/settingsStore");
|
||||
const store = useConnectionStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
settingsStore.editorSettings.sidebarObjectDisplay = "simple";
|
||||
settingsStore.desktopSettings.sidebar_table_page_size = 500;
|
||||
const connection = starRocksConnection();
|
||||
const database = databaseNode(connection.id, "hive", "sales");
|
||||
store.connections = [connection];
|
||||
store.connectedIds.add(connection.id);
|
||||
store.treeNodes = [{ id: connection.id, label: connection.name, type: "connection", connectionId: connection.id, children: [catalogNode(connection.id, "hive", "Hive", [database])] }];
|
||||
store.setSidebarTableSearchQuery(database.id, "orders");
|
||||
|
||||
await store.loadTreeNodeChildren(database, {
|
||||
force: true,
|
||||
searchFilter: "orders",
|
||||
sidebarTableSearchParentId: database.id,
|
||||
expectedSidebarTableSearchQuery: "orders",
|
||||
});
|
||||
|
||||
expect(listTables.mock.calls[0]?.[4]).toBe(2000);
|
||||
expect(listTables.mock.calls[0]?.[5]).toBeUndefined();
|
||||
expect(listTables.mock.calls[0]?.[7]).toBe("hive");
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,11 @@ function installLocalStorage() {
|
||||
});
|
||||
}
|
||||
|
||||
// Mirrors SIDEBAR_TABLE_SEARCH_RESULT_BUDGET in connectionStore.ts (4× the
|
||||
// default sidebar_table_page_size of 500). Kept as a local constant because
|
||||
// connectionStore must stay dynamically imported for vi.doMock isolation.
|
||||
const SIDEBAR_SEARCH_RESULT_BUDGET = 2000;
|
||||
|
||||
function postgresConnection(): ConnectionConfig {
|
||||
return {
|
||||
id: "pg-1",
|
||||
@@ -3165,7 +3170,7 @@ describe("connectionStore metadata loading", () => {
|
||||
expect(tablesGroup.objectCount).toBe(5);
|
||||
expect(tablesGroup.isLoading).toBe(false);
|
||||
expect(listTables.mock.calls.map((call) => [call[3], call[4], call[5]])).toEqual([
|
||||
["0003", 2, undefined],
|
||||
["0003", SIDEBAR_SEARCH_RESULT_BUDGET, undefined],
|
||||
[undefined, 3, 0],
|
||||
[undefined, 3, 0],
|
||||
[undefined, 3, 2],
|
||||
@@ -3219,7 +3224,7 @@ describe("connectionStore metadata loading", () => {
|
||||
store.sidebarSearchQuery = "orders";
|
||||
await store.loadObjectGroupChildren(tablesGroup, { force: true });
|
||||
|
||||
expect(listTables).toHaveBeenLastCalledWith(connection.id, "basic", "basic", "orders", 200, undefined, ["TABLE"]);
|
||||
expect(listTables).toHaveBeenLastCalledWith(connection.id, "basic", "basic", "orders", SIDEBAR_SEARCH_RESULT_BUDGET, undefined, ["TABLE"]);
|
||||
expect(tablesGroup.children?.map((node) => node.label)).toEqual(["orders"]);
|
||||
|
||||
let resolveAncestorLoad!: (tables: TableInfo[]) => void;
|
||||
|
||||
@@ -198,6 +198,19 @@ function sidebarObjectGroupPageSize(): number {
|
||||
return typeof size === "number" && size > 0 ? size : 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upper bound for a single remote fuzzy table-search result set.
|
||||
*
|
||||
* Every fuzzy match travels database → IPC → store → tree rendering, so an
|
||||
* unbounded result set (e.g. a single-letter query against a large schema)
|
||||
* would push thousands of rows through the whole pipeline. The budget is 4×
|
||||
* the default page size (500) and comfortably covers the reported #6190
|
||||
* schema (801 fuzzy matches) while keeping a single IPC payload bounded.
|
||||
* Queries whose fuzzy match set exceeds the budget are truncated; narrowing
|
||||
* the query reaches later tables.
|
||||
*/
|
||||
export const SIDEBAR_TABLE_SEARCH_RESULT_BUDGET = 2000;
|
||||
|
||||
function isFlatMqConnection(config: ConnectionConfig | undefined): boolean {
|
||||
if (!config || config.db_type !== "mq") return false;
|
||||
if (config.driver_profile === "kafka" || config.driver_profile === "rocketmq" || config.driver_profile === "rabbitmq") return true;
|
||||
@@ -1855,7 +1868,14 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
catalog: options.node.catalog,
|
||||
});
|
||||
const tableNameFilter = effectiveTableNameFilterForNode(options.node, userTableNameFilter);
|
||||
const fetchLimit = searchFilter ? options.pageSize : options.pageSize + 1;
|
||||
// A search must never truncate the fuzzy result set to the first page: the
|
||||
// target table can sort beyond it (e.g. "T_Erp_Nc_SuPlan_List" for
|
||||
// "erpncs" in a large ERP schema), which silently drops it from the first
|
||||
// search even though later, narrower queries succeed. Results are bounded
|
||||
// by SIDEBAR_TABLE_SEARCH_RESULT_BUDGET so a wide fuzzy query cannot push
|
||||
// an unbounded result set through database → IPC → store → tree rendering.
|
||||
// Unfiltered loads keep the page+1 probe used for load-more detection.
|
||||
const fetchLimit = searchFilter ? SIDEBAR_TABLE_SEARCH_RESULT_BUDGET : options.pageSize + 1;
|
||||
const fetchOffset = searchFilter ? undefined : options.offset;
|
||||
const tables = await loadCachedMetadataListPage<TableInfo[]>(
|
||||
metadataListCacheScope({
|
||||
@@ -1964,7 +1984,11 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
schema: options.effectiveSchema ?? options.querySchema,
|
||||
nodeKind: "simple-tables",
|
||||
});
|
||||
const fetchLimit = searchFilter ? options.pageSize : options.pageSize + 1;
|
||||
// A search must never truncate the fuzzy result set to the first page (see
|
||||
// loadPagedTableGroupChildren); results are bounded by
|
||||
// SIDEBAR_TABLE_SEARCH_RESULT_BUDGET, and unfiltered loads keep the
|
||||
// page+1 probe.
|
||||
const fetchLimit = searchFilter ? SIDEBAR_TABLE_SEARCH_RESULT_BUDGET : options.pageSize + 1;
|
||||
const fetchOffset = searchFilter ? undefined : options.offset;
|
||||
const tables = await loadCachedMetadataListPage<TableInfo[]>(
|
||||
metadataListCacheScope({
|
||||
@@ -4674,6 +4698,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
const configForScope = getConfig(connectionId);
|
||||
const simpleObjectDisplayForScope = useSettingsStore().editorSettings.sidebarObjectDisplay === "simple";
|
||||
const objectTypesForScope = simpleObjectDisplayForScope ? supportedSidebarObjectTypes(configForScope) : undefined;
|
||||
const searchFilterForScope = activeTreeLoadSearchFilter(options);
|
||||
const pageSizeForScope = sidebarObjectGroupPageSize();
|
||||
return runTreeMetadataLoad(
|
||||
{
|
||||
kind: "schema-tables",
|
||||
@@ -4682,8 +4708,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
schema: undefined,
|
||||
nodeKind: "database",
|
||||
objectTypes: objectTypesForScope,
|
||||
searchFilter: activeTreeLoadSearchFilter(options),
|
||||
limit: simpleObjectDisplayForScope ? sidebarObjectGroupPageSize() + 1 : undefined,
|
||||
searchFilter: searchFilterForScope,
|
||||
limit: simpleObjectDisplayForScope ? (searchFilterForScope ? SIDEBAR_TABLE_SEARCH_RESULT_BUDGET : pageSizeForScope + 1) : undefined,
|
||||
offset: 0,
|
||||
sidebarDisplayMode: simpleObjectDisplayForScope ? "simple" : "grouped",
|
||||
driverProfile: metadataDriverProfile(configForScope),
|
||||
@@ -4710,8 +4736,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const pageSize = sidebarObjectGroupPageSize();
|
||||
const fetchLimit = searchFilter ? pageSize : pageSize + 1;
|
||||
const pageSize = pageSizeForScope;
|
||||
const fetchLimit = searchFilter ? SIDEBAR_TABLE_SEARCH_RESULT_BUDGET : pageSize + 1;
|
||||
const fetchOffset = searchFilter ? undefined : 0;
|
||||
const tables = await withMetadataLoadTimeout(connectionId, listTablesWithOptionalTableNameFilter(connectionId, database, "", searchFilter, fetchLimit, fetchOffset, objectTypesForScope, catalog, tableNameFilter), "tables");
|
||||
const hasMore = searchFilter ? false : tables.length > pageSize;
|
||||
|
||||
Reference in new issue
Block a user