fix(sidebar): keep table search pins across index rebuilds
Closes #8964
This commit is contained in:
5 files changed
+100
-33
No files matched your search
@@ -10,6 +10,7 @@ import { useToast } from "@/composables/useToast";
|
||||
import type { ObjectSourceKind, QueryTab, TableInfo, TableNameFilter, TreeNode, TreeNodeType } from "@/types/database";
|
||||
import type { ElasticsearchIndexMetadataKind } from "@/lib/backend/tauri";
|
||||
import {
|
||||
filterLocallySearchedTables,
|
||||
createSidebarSearchSubtreePreserver,
|
||||
filterSidebarSearchRootsByConnectionState,
|
||||
filterSidebarTree,
|
||||
@@ -18,15 +19,13 @@ import {
|
||||
mergeSidebarRegexIndexScopes,
|
||||
resolveSidebarFilterGuards,
|
||||
resolveSidebarObjectSearchFilter,
|
||||
reuseLiveSidebarTreeNodes,
|
||||
type SidebarRegexIndexScope,
|
||||
type SidebarRegexScopeIdentity,
|
||||
} from "@/lib/sidebar/sidebarSearchTree";
|
||||
import { createSidebarLabelMatcher, matchSidebarLabel } from "@/lib/sidebar/sidebarSearch";
|
||||
import { createSidebarLabelMatcher } from "@/lib/sidebar/sidebarSearch";
|
||||
import { collectSidebarRegexIndexScopes, resolveSidebarRemoteSearchQuery, resolveSidebarSearchDispatchMode } from "@/lib/sidebar/sidebarRegexSearchIndex";
|
||||
import { createSidebarSearchExpansionState } from "@/lib/sidebar/sidebarSearchExpansionState";
|
||||
import { createSidebarSearchLoadingTracker } from "@/lib/sidebar/sidebarSearchLoadingTracker";
|
||||
import { buildTableTreeNodes } from "@/lib/table/tableTree";
|
||||
import { isCancelSearchShortcut, isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut, isViewTableDdlShortcut } from "@/lib/editor/keyboardShortcuts";
|
||||
import { sidebarNodeSupportsDdlView } from "@/lib/sidebar/sidebarTreeDdlShortcut";
|
||||
import { objectSourceTargetForTreeNode } from "@/lib/sidebar/treeNodeClick";
|
||||
@@ -658,28 +657,6 @@ type InvalidatedTableSearchScope = SidebarRegexScopeIdentity & { parentNodeId: s
|
||||
const pendingInvalidatedTableSearchScopes = new Map<string, InvalidatedTableSearchScope>();
|
||||
const regexTableSearchScopes = shallowRef<SidebarRegexIndexScope[]>([]);
|
||||
|
||||
const localTableSearchParentTypes = new Set<TreeNodeType>(["database", "schema", "linked-server-schema", "group-tables"]);
|
||||
const localTableSearchChildTypes = new Set<TreeNodeType>(["table", "view", "materialized_view"]);
|
||||
|
||||
function filterLocallySearchedTables(nodes: TreeNode[]): TreeNode[] {
|
||||
return nodes.map((node) => {
|
||||
const children = node.children ? filterLocallySearchedTables(node.children) : undefined;
|
||||
const query = settingsStore.editorSettings.sidebarTableSearchLocal && localTableSearchParentTypes.has(node.type) ? store.sidebarTableSearchQueries[node.id]?.trim() : "";
|
||||
if (!query || !children) return children === node.children ? node : { ...node, children };
|
||||
|
||||
const indexed = localTableSearchResults.value[node.id];
|
||||
// matchSidebarLabel compares case-insensitively internally and needs the
|
||||
// ORIGINAL label (and entry name) so camelCase boundaries stay detectable.
|
||||
const matchingChildren =
|
||||
indexed === null
|
||||
? children.filter((child) => localTableSearchChildTypes.has(child.type) && !!matchSidebarLabel(child.label, query))
|
||||
: indexed
|
||||
? reuseLiveSidebarTreeNodes(buildTableTreeNodes({ nodeId: node.id, connectionId: node.connectionId || "", database: node.database || "", schema: node.schema, catalog: node.catalog, tables: indexed.filter((entry) => !!matchSidebarLabel(entry.name, query)) }), children)
|
||||
: children.filter((child) => localTableSearchChildTypes.has(child.type) && !!matchSidebarLabel(child.label, query));
|
||||
return { ...node, children: matchingChildren };
|
||||
});
|
||||
}
|
||||
|
||||
async function loadRegexTableSearchIndexes() {
|
||||
if (!regexMode.value || !deferredSearchQuery.value) return;
|
||||
const loadedScopes = await collectSidebarRegexIndexScopes(
|
||||
@@ -781,7 +758,7 @@ const filteredNodes = computed(() => {
|
||||
nodes = filterSidebarTreeToConnectedConnections(nodes, store.connectedIds);
|
||||
}
|
||||
|
||||
nodes = filterLocallySearchedTables(nodes);
|
||||
nodes = filterLocallySearchedTables(nodes, { enabled: settingsStore.editorSettings.sidebarTableSearchLocal, queries: store.sidebarTableSearchQueries, indexedResults: localTableSearchResults.value });
|
||||
nodes = filterGloballyIndexedRegexTables(nodes);
|
||||
|
||||
const q = deferredSearchQuery.value;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TableInfo, TreeNode, TreeNodeType } from "@/types/database";
|
||||
import { createSidebarLabelMatcher, type SidebarLabelMatcher, type SidebarSearchMatcherOptions } from "@/lib/sidebar/sidebarSearch";
|
||||
import { createSidebarLabelMatcher, matchSidebarLabel, type SidebarLabelMatcher, type SidebarSearchMatcherOptions } from "@/lib/sidebar/sidebarSearch";
|
||||
import { buildTableTreeNodes } from "@/lib/table/tableTree";
|
||||
|
||||
const preserveMatchedSubtreeTypes = new Set(["connection", "database", "schema", "table", "view", "mongo-db", "mongo-collection"]);
|
||||
@@ -72,6 +72,31 @@ export function reuseLiveSidebarTreeNodes(indexedNodes: TreeNode[], liveNodes: r
|
||||
return indexedNodes.map((node) => liveNodesById.get(node.id) ?? node);
|
||||
}
|
||||
|
||||
const localTableSearchParentTypes = new Set<TreeNodeType>(["database", "schema", "linked-server-schema", "group-tables"]);
|
||||
const localTableSearchChildTypes = new Set<TreeNodeType>(["table", "view", "materialized_view"]);
|
||||
|
||||
export function filterLocallySearchedTables(nodes: TreeNode[], options: { enabled: boolean; queries: Readonly<Record<string, string>>; indexedResults: Readonly<Record<string, TableInfo[] | null>> }): TreeNode[] {
|
||||
return nodes.map((node) => {
|
||||
const children = node.children ? filterLocallySearchedTables(node.children, options) : undefined;
|
||||
const query = options.enabled && localTableSearchParentTypes.has(node.type) ? options.queries[node.id]?.trim() : "";
|
||||
if (!query || !children) return children === node.children ? node : { ...node, children };
|
||||
|
||||
const indexed = options.indexedResults[node.id];
|
||||
// matchSidebarLabel compares case-insensitively internally and needs the
|
||||
// ORIGINAL label (and entry name) so camelCase boundaries stay detectable.
|
||||
const matchingChildren =
|
||||
indexed === null
|
||||
? children.filter((child) => localTableSearchChildTypes.has(child.type) && !!matchSidebarLabel(child.label, query))
|
||||
: indexed
|
||||
? reuseLiveSidebarTreeNodes(
|
||||
buildSidebarIndexedTableNodes({ parentNodeId: node.id, nodeType: node.type, connectionId: node.connectionId || "", database: node.database || "", schema: node.schema, catalog: node.catalog, entries: indexed.filter((entry) => !!matchSidebarLabel(entry.name, query)) }),
|
||||
children,
|
||||
)
|
||||
: children.filter((child) => localTableSearchChildTypes.has(child.type) && !!matchSidebarLabel(child.label, query));
|
||||
return { ...node, children: matchingChildren };
|
||||
});
|
||||
}
|
||||
|
||||
export interface SidebarRegexIndexScope {
|
||||
parentNodeId: string;
|
||||
connectionId: string;
|
||||
@@ -132,7 +157,7 @@ export function findNodePathByIdentity(nodes: readonly TreeNode[], id: string, i
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function indexedTableChildren(scope: SidebarRegexIndexScope): TreeNode[] {
|
||||
export function buildSidebarIndexedTableNodes(scope: SidebarRegexIndexScope): TreeNode[] {
|
||||
// Reuse the regular table-node builder so index hits share live-node id
|
||||
// rules, normalized object types, name sorting, catalog/schema fields, and
|
||||
// partition parent/child nesting instead of hand-rolled node literals.
|
||||
@@ -143,6 +168,10 @@ function indexedTableChildren(scope: SidebarRegexIndexScope): TreeNode[] {
|
||||
schema: scope.schema,
|
||||
catalog: scope.catalog,
|
||||
tables: scope.entries,
|
||||
// Grouped object lists include the schema in each child id; simple table
|
||||
// lists do not. Search must use the same id because persisted pin keys
|
||||
// include it, even when an index hit has not been loaded into the live tree.
|
||||
includeSchemaInId: scope.nodeType === "group-tables",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -160,7 +189,7 @@ function syntheticRegexParent(scope: SidebarRegexIndexScope): TreeNode {
|
||||
...(snapshot?.linkedCatalog ? { linkedCatalog: snapshot.linkedCatalog } : {}),
|
||||
...(snapshot?.linkedSchema ? { linkedSchema: snapshot.linkedSchema } : {}),
|
||||
isExpanded: true,
|
||||
children: indexedTableChildren(scope),
|
||||
children: buildSidebarIndexedTableNodes(scope),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export function normalizeDatabaseObjectName(name: string): string {
|
||||
return name.trim();
|
||||
}
|
||||
|
||||
export function buildTableTreeNodes({ nodeId, connectionId, database, schema, tables, catalog }: { nodeId: string; connectionId: string; database: string; schema?: string; tables: TableInfo[]; catalog?: string }): TreeNode[] {
|
||||
export function buildTableTreeNodes({ nodeId, connectionId, database, schema, tables, catalog, includeSchemaInId = false }: { nodeId: string; connectionId: string; database: string; schema?: string; tables: TableInfo[]; catalog?: string; includeSchemaInId?: boolean }): TreeNode[] {
|
||||
const entries = tables.flatMap((table) => {
|
||||
const name = normalizeDatabaseObjectName(table.name);
|
||||
if (!name) return [];
|
||||
@@ -19,7 +19,7 @@ export function buildTableTreeNodes({ nodeId, connectionId, database, schema, ta
|
||||
connectionId,
|
||||
database,
|
||||
schema: childSchema,
|
||||
includeSchemaInId: false,
|
||||
includeSchemaInId,
|
||||
name,
|
||||
objectType,
|
||||
tableType: table.table_type,
|
||||
|
||||
Reference in new issue
Block a user