feat(search): limit sidebar search to opened databases

This commit is contained in:
jefferyjob
2026-09-29 13:59:42 +08:00
committed by GitHub
parent e4ab63d9f3
commit e7883a6466
17 changed files with 169 additions and 2 deletions
@@ -819,6 +819,7 @@ const sidebarObjectDisplayHelp = ref<"grouped" | "simple" | null>(null);
const dataTabReuseModeHelp = ref<DataTabReuseMode | null>(null);
const editRoutineSourceOpenMode = ref(settingsStore.editorSettings.routineSourceOpenMode);
const editSidebarTableSearchEnabled = ref(settingsStore.editorSettings.sidebarTableSearchEnabled);
const editSidebarSearchOpenedDatabasesOnly = ref(settingsStore.editorSettings.sidebarSearchOpenedDatabasesOnly);
const editAutoSelectActiveSidebarNode = ref(settingsStore.editorSettings.autoSelectActiveSidebarNode);
const editSidebarBrowseObjectsOnDatabaseActivation = ref(settingsStore.editorSettings.sidebarBrowseObjectsOnDatabaseActivation);
const editOpenTabsRestoreMode = ref<OpenTabsRestoreMode>(settingsStore.editorSettings.openTabsRestoreMode);
@@ -1098,6 +1099,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
sidebarObjectDisplay: editSidebarObjectDisplay.value,
routineSourceOpenMode: editRoutineSourceOpenMode.value,
sidebarTableSearchEnabled: editSidebarTableSearchEnabled.value,
sidebarSearchOpenedDatabasesOnly: editSidebarSearchOpenedDatabasesOnly.value,
autoSelectActiveSidebarNode: editAutoSelectActiveSidebarNode.value,
sidebarBrowseObjectsOnDatabaseActivation: editSidebarBrowseObjectsOnDatabaseActivation.value,
openTabsRestoreMode: editOpenTabsRestoreMode.value,
@@ -1763,6 +1765,7 @@ function syncEditorSettingsDraftFromStore() {
editSidebarObjectDisplay.value = settingsStore.editorSettings.sidebarObjectDisplay;
editRoutineSourceOpenMode.value = settingsStore.editorSettings.routineSourceOpenMode;
editSidebarTableSearchEnabled.value = settingsStore.editorSettings.sidebarTableSearchEnabled;
editSidebarSearchOpenedDatabasesOnly.value = settingsStore.editorSettings.sidebarSearchOpenedDatabasesOnly;
editAutoSelectActiveSidebarNode.value = settingsStore.editorSettings.autoSelectActiveSidebarNode;
editSidebarBrowseObjectsOnDatabaseActivation.value = settingsStore.editorSettings.sidebarBrowseObjectsOnDatabaseActivation;
editOpenTabsRestoreMode.value = settingsStore.editorSettings.openTabsRestoreMode;
@@ -1900,6 +1903,7 @@ const editorSettingsDraftRefs: EditorSettingsDraftRefMap = {
sidebarObjectDisplay: editSidebarObjectDisplay,
routineSourceOpenMode: editRoutineSourceOpenMode,
sidebarTableSearchEnabled: editSidebarTableSearchEnabled,
sidebarSearchOpenedDatabasesOnly: editSidebarSearchOpenedDatabasesOnly,
autoSelectActiveSidebarNode: editAutoSelectActiveSidebarNode,
sidebarBrowseObjectsOnDatabaseActivation: editSidebarBrowseObjectsOnDatabaseActivation,
openTabsRestoreMode: editOpenTabsRestoreMode,
@@ -2384,6 +2388,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editSidebarObjectDisplay.value = DEFAULT_EDITOR_SETTINGS.sidebarObjectDisplay;
editRoutineSourceOpenMode.value = DEFAULT_EDITOR_SETTINGS.routineSourceOpenMode;
editSidebarTableSearchEnabled.value = DEFAULT_EDITOR_SETTINGS.sidebarTableSearchEnabled;
editSidebarSearchOpenedDatabasesOnly.value = DEFAULT_EDITOR_SETTINGS.sidebarSearchOpenedDatabasesOnly;
editAutoSelectActiveSidebarNode.value = DEFAULT_EDITOR_SETTINGS.autoSelectActiveSidebarNode;
editSidebarBrowseObjectsOnDatabaseActivation.value = DEFAULT_EDITOR_SETTINGS.sidebarBrowseObjectsOnDatabaseActivation;
editOpenTabsRestoreMode.value = DEFAULT_EDITOR_SETTINGS.openTabsRestoreMode;
@@ -2562,6 +2567,7 @@ function resetAllDefaults() {
editSidebarObjectDisplay.value = DEFAULT_EDITOR_SETTINGS.sidebarObjectDisplay;
editRoutineSourceOpenMode.value = DEFAULT_EDITOR_SETTINGS.routineSourceOpenMode;
editSidebarTableSearchEnabled.value = DEFAULT_EDITOR_SETTINGS.sidebarTableSearchEnabled;
editSidebarSearchOpenedDatabasesOnly.value = DEFAULT_EDITOR_SETTINGS.sidebarSearchOpenedDatabasesOnly;
editAutoSelectActiveSidebarNode.value = DEFAULT_EDITOR_SETTINGS.autoSelectActiveSidebarNode;
editSidebarBrowseObjectsOnDatabaseActivation.value = DEFAULT_EDITOR_SETTINGS.sidebarBrowseObjectsOnDatabaseActivation;
editOpenTabsRestoreMode.value = DEFAULT_EDITOR_SETTINGS.openTabsRestoreMode;
@@ -7640,6 +7646,15 @@ onUnmounted(() => {
</div>
<Switch id="sidebar-table-search-enabled" v-model="editSidebarTableSearchEnabled" />
</div>
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="flex items-center gap-2">
<Label for="sidebar-search-opened-databases-only">{{ t("settings.sidebarSearchOpenedDatabasesOnly") }}</Label>
<HelpTooltip :label="t('settings.sidebarSearchOpenedDatabasesOnly')">
{{ t("settings.sidebarSearchOpenedDatabasesOnlyDescription") }}
</HelpTooltip>
</div>
<Switch id="sidebar-search-opened-databases-only" v-model="editSidebarSearchOpenedDatabasesOnly" />
</div>
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="flex items-center gap-2">
<Label for="auto-select-active-sidebar-node">{{ t("settings.autoSelectActiveSidebarNode") }}</Label>
@@ -27,6 +27,7 @@ import {
import { createSidebarLabelMatcher } from "@/lib/sidebar/sidebarSearch";
import { collectSidebarRegexIndexScopes, resolveSidebarRemoteSearchQuery, resolveSidebarSearchDispatchMode, shouldRestoreTrackedSidebarSearchTargetsInRegexMode } from "@/lib/sidebar/sidebarRegexSearchIndex";
import { needsSidebarObjectGroupDiscovery } from "@/lib/sidebar/sidebarSearchDiscovery";
import { isSidebarSearchPrunedDatabaseNode, resolveSidebarSearchDatabaseScope } from "@/lib/sidebar/sidebarSearchDatabaseScope";
import { createSidebarSearchExpansionState } from "@/lib/sidebar/sidebarSearchExpansionState";
import { createSidebarSearchLoadingTracker } from "@/lib/sidebar/sidebarSearchLoadingTracker";
import { isCancelSearchShortcut, isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut, isViewTableDdlShortcut } from "@/lib/editor/keyboardShortcuts";
@@ -387,9 +388,16 @@ async function loadSidebarSearchTargets(query: string, preservesNodeSubtree?: (n
} while (deferredSearchQuery.value === query && store.sidebarSearchQuery === query);
}
function collectExpandedObjectSearchTargets(node: TreeNode, tasks: SidebarSearchTask[], refreshedNodeIds?: Set<string>, preservesNodeSubtree?: (node: TreeNode) => boolean, ancestorPreservesSearchSubtree = false, scheduledNodeIds?: Set<string>) {
function collectExpandedObjectSearchTargets(node: TreeNode, tasks: SidebarSearchTask[], refreshedNodeIds?: Set<string>, preservesNodeSubtree?: (node: TreeNode) => boolean, ancestorPreservesSearchSubtree = false, scheduledNodeIds?: Set<string>, databaseScope?: ReadonlySet<string> | null) {
const preservesSearchSubtree = ancestorPreservesSearchSubtree || (!!refreshedNodeIds && !!preservesNodeSubtree?.(node));
if (refreshedNodeIds && node.type === "connection" && node.connectionId) {
// 数据库级节点只有被用户真正打开(树已加载或被打开的页签引用,与侧栏
// 「打开」高亮同口径)才参与自动搜索;一个都没打开时退回全库搜索。
databaseScope = resolveSidebarSearchDatabaseScope(node, {
enabled: settingsStore.editorSettings.sidebarSearchOpenedDatabasesOnly,
isChildrenLoaded: store.isTreeNodeChildrenLoaded,
openDatabaseKeys: queryStore.openDatabaseKeys,
});
const connectionIsConnected = store.connectedIds.has(node.connectionId);
if (connectionIsConnected && (!scheduledNodeIds || !scheduledNodeIds.has(node.id))) {
const connectionId = node.connectionId;
@@ -400,6 +408,7 @@ function collectExpandedObjectSearchTargets(node: TreeNode, tasks: SidebarSearch
// 断开或连不上的连接直接跳过,后台搜索不会因此弹出凭据输入或写入整段连接错误。
if (!connectionIsConnected || node.connectionId !== store.activeConnectionId) return;
}
if (refreshedNodeIds && databaseScope && isSidebarSearchPrunedDatabaseNode(node, databaseScope)) return;
if (refreshedNodeIds && isSimpleObjectSearchParent(node)) {
if (!scheduledNodeIds || !scheduledNodeIds.has(node.id)) {
scheduledNodeIds?.add(node.id);
@@ -460,7 +469,7 @@ function collectExpandedObjectSearchTargets(node: TreeNode, tasks: SidebarSearch
}
if (node.children) {
for (const child of node.children) {
collectExpandedObjectSearchTargets(child, tasks, refreshedNodeIds, preservesNodeSubtree, preservesSearchSubtree, scheduledNodeIds);
collectExpandedObjectSearchTargets(child, tasks, refreshedNodeIds, preservesNodeSubtree, preservesSearchSubtree, scheduledNodeIds, databaseScope);
}
}
}
+2
View File
@@ -8234,6 +8234,8 @@ export default {
sidebarTablePageSizeDescription: "Maximum number of tables/objects loaded per page in the sidebar tree. Increase if you have many tables and want fewer pages.",
sidebarTableSearchEnabled: "Enable table search inside databases",
sidebarTableSearchEnabledDescription: "Show a local table search box under expanded databases, schemas, or table groups so each scope can filter tables independently.",
sidebarSearchOpenedDatabasesOnly: "Search only opened databases",
sidebarSearchOpenedDatabasesOnlyDescription: "The sidebar search loads only databases you have opened in the active connection (all of them when none is opened). Turn off to search every database of the connection.",
sidebarObjectInfoMode: "Sidebar supplementary info",
sidebarObjectInfoModeDescription:
"Choose comments, object sizes, or no supplementary text after names. Comments and sizes are mutually exclusive. Database totals currently support PostgreSQL; table sizes support MySQL, PostgreSQL, GaussDB, KingbaseES, GBase 8a, SQL Server, Oracle, Dameng, and ClickHouse.",
+2
View File
@@ -8435,6 +8435,8 @@ export default withEnglishFallback({
supportInfoDatabaseTypes: "Tipos de bases de datos",
supportInfoLocalDriverVersions: "Versiones de controladores locales",
supportInfoAiProviders: "Proveedores de IA",
sidebarSearchOpenedDatabasesOnly: "Buscar solo en bases de datos abiertas",
sidebarSearchOpenedDatabasesOnlyDescription: "La búsqueda de la barra lateral solo carga las bases de datos abiertas en la conexión actual (si no hay ninguna abierta, busca en todas). Al desactivarla, se buscará en todas las bases de datos de esa conexión.",
},
driverStore: {
jreDirRemoveFailed: "No se pudo eliminar el directorio JRE antiguo: {path} (error original: {error})",
+2
View File
@@ -8359,6 +8359,8 @@ export default withEnglishFallback({
supportInfoDatabaseTypes: "Tipi di database",
supportInfoLocalDriverVersions: "Versioni dei driver locali",
supportInfoAiProviders: "Provider AI",
sidebarSearchOpenedDatabasesOnly: "Cerca solo nei database aperti",
sidebarSearchOpenedDatabasesOnlyDescription: "La ricerca nella barra laterale carica solo i database aperti nella connessione corrente (se nessuno è aperto, cerca in tutti). Se disattivata, cerca in tutti i database della connessione.",
},
driverStore: {
jreDirRemoveFailed: "Impossibile rimuovere la vecchia directory JRE: {path} (errore originale: {error})",
+2
View File
@@ -8402,6 +8402,8 @@ export default withEnglishFallback({
supportInfoDatabaseTypes: "データベースの種類",
supportInfoLocalDriverVersions: "ローカルドライバーのバージョン",
supportInfoAiProviders: "AIプロバイダー",
sidebarSearchOpenedDatabasesOnly: "開いているデータベースのみを検索",
sidebarSearchOpenedDatabasesOnlyDescription: "サイドバー検索は、現在の接続で開いているデータベースのみを読み込みます(1つも開いていない場合はすべてを検索します)。オフにすると、その接続のすべてのデータベースを検索します。",
},
driverStore: {
jreDirRemoveFailed: "古い JRE ディレクトリを削除できませんでした: {path}(元のエラー: {error})",
+2
View File
@@ -8105,6 +8105,8 @@ export default withEnglishFallback({
supportInfoDatabaseTypes: "데이터베이스 유형",
supportInfoLocalDriverVersions: "로컬 드라이버 버전",
supportInfoAiProviders: "AI 제공자",
sidebarSearchOpenedDatabasesOnly: "열려 있는 데이터베이스만 검색",
sidebarSearchOpenedDatabasesOnlyDescription: "사이드바 검색은 현재 연결에서 열려 있는 데이터베이스만 로드합니다(열려 있는 데이터베이스가 하나도 없으면 전체를 검색). 끄면 해당 연결의 모든 데이터베이스를 검색합니다.",
},
driverStore: {
jreDirRemoveFailed: "이전 JRE 디렉터리 제거 실패: {path} (원래 오류: {error})",
+2
View File
@@ -8361,6 +8361,8 @@ export default withEnglishFallback({
supportInfoDatabaseTypes: "Tipos de banco de dados",
supportInfoLocalDriverVersions: "Versões de drivers locais",
supportInfoAiProviders: "Provedores de IA",
sidebarSearchOpenedDatabasesOnly: "Pesquisar apenas bancos de dados abertos",
sidebarSearchOpenedDatabasesOnlyDescription: "A pesquisa da barra lateral carrega apenas os bancos de dados abertos na conexão atual (se nenhum estiver aberto, pesquisa todos). Quando desativada, pesquisará todos os bancos de dados da conexão.",
},
driverStore: {
jreDirRemoveFailed: "Não foi possível remover o diretório JRE antigo: {path} (erro original: {error})",
+2
View File
@@ -9592,6 +9592,8 @@ export default withEnglishFallback({
showColumnHeaderTooltipsDescription: "Показывать подробную всплывающую подсказку при наведении курсора на имя поля в заголовке результатов; после отключения всплывающая подсказка при наведении больше не появляется.",
showResultSourceDatabase: "Имя набора результатов содержит имя базы данных",
showResultSourceDatabaseDescription: "После отключения вкладки наборов результатов будут отображать только имена таблиц; полное имя (включая имя базы данных) по-прежнему можно посмотреть во всплывающей подсказке.",
sidebarSearchOpenedDatabasesOnly: "Искать только в открытых базах данных",
sidebarSearchOpenedDatabasesOnlyDescription: "Поиск на боковой панели загружает только открытые базы данных в текущем подключении (если ни одна не открыта, поиск выполняется по всем). Если отключить, поиск будет выполняться по всем базам данных этого подключения.",
},
driverStore: {
jreDirRemoveFailed: "Не удалось удалить старый каталог JRE: {path} (исходная ошибка: {error})",
+2
View File
@@ -8200,6 +8200,8 @@ export default withEnglishFallback({
sidebarTablePageSizeDescription: "侧边栏树每页最多加载的表/对象数量。表多时可以调大减少翻页次数。",
sidebarTableSearchEnabled: "启用库下表搜索框",
sidebarTableSearchEnabledDescription: "在已展开的数据库、schema 或表分组下显示局部表搜索框,用于为不同库分别过滤表。",
sidebarSearchOpenedDatabasesOnly: "仅搜索已打开的数据库",
sidebarSearchOpenedDatabasesOnlyDescription: "侧边栏搜索只加载当前连接中已打开的数据库(若一个都没打开则搜索全部)。关闭后会搜索该连接的全部数据库。",
sidebarObjectInfoMode: "侧边栏附加信息",
sidebarObjectInfoModeDescription: "选择在名称后显示注释、对象大小或不显示。注释与大小互斥;数据库总大小目前支持 PostgreSQL,表大小支持 MySQL、PostgreSQL、GaussDB、KingbaseES、GBase 8a、SQL Server、Oracle、达梦和 ClickHouse。",
sidebarObjectInfoModeCommentInline: "注释(紧跟名称)",
+2
View File
@@ -7686,6 +7686,8 @@ export default withEnglishFallback({
supportInfoDatabaseTypes: "資料庫類型",
supportInfoLocalDriverVersions: "本機驅動程式版本",
supportInfoAiProviders: "AI 供應商",
sidebarSearchOpenedDatabasesOnly: "僅搜尋已開啟的資料庫",
sidebarSearchOpenedDatabasesOnlyDescription: "側邊欄搜尋只載入目前連線中已開啟的資料庫(若一個都沒開啟則搜尋全部)。關閉後會搜尋該連線的全部資料庫。",
},
driverStore: {
jreDirRemoveFailed: "無法刪除舊的 JRE 目錄:{path}(原始錯誤:{error})",
@@ -0,0 +1,62 @@
import { describe, expect, it } from "vitest";
import type { TreeNode } from "@/types/database";
import { isSidebarSearchPrunedDatabaseNode, resolveSidebarSearchDatabaseScope } from "@/lib/sidebar/sidebarSearchDatabaseScope";
const OPTIONS = {
enabled: true,
isChildrenLoaded: (nodeId: string) => nodeId.endsWith(":loaded"),
openDatabaseKeys: new Set(["conn-1\x00tabbed"]),
};
function databaseNode(id: string, database: string, overrides: Partial<TreeNode> = {}): TreeNode {
return { id, label: database, type: "database", connectionId: "conn-1", database, isExpanded: false, children: [], ...overrides };
}
function connectionNode(children: TreeNode[]): TreeNode {
return { id: "conn-1", label: "prod", type: "connection", connectionId: "conn-1", isExpanded: true, children };
}
describe("resolveSidebarSearchDatabaseScope", () => {
it.each([
["disabled setting keeps the search unrestricted", { enabled: false }, null],
["no database opened falls back to every database", {}, null],
] as const)("returns null when %s", (_name, overrides, expected) => {
const node = connectionNode([databaseNode("conn-1:a", "a"), databaseNode("conn-1:b", "b")]);
expect(resolveSidebarSearchDatabaseScope(node, { ...OPTIONS, ...overrides })).toEqual(expected);
});
it("keeps databases opened in the tree", () => {
const node = connectionNode([databaseNode("conn-1:a:loaded", "a"), databaseNode("conn-1:b", "b")]);
expect(resolveSidebarSearchDatabaseScope(node, OPTIONS)).toEqual(new Set(["conn-1:a:loaded"]));
});
it("keeps databases referenced by an open editor tab", () => {
const node = connectionNode([databaseNode("conn-1:tabbed", "tabbed"), databaseNode("conn-1:b", "b")]);
expect(resolveSidebarSearchDatabaseScope(node, OPTIONS)).toEqual(new Set(["conn-1:tabbed"]));
});
it("discovers nested database nodes below catalogs and skips their subtrees", () => {
const nested = databaseNode("conn-1:cat:nested:loaded", "nested");
const node = connectionNode([
{ id: "conn-1:cat", label: "cat", type: "doris-catalog", connectionId: "conn-1", catalog: "cat", isExpanded: true, children: [nested] },
{ id: "conn-1:other", label: "other", type: "doris-catalog", connectionId: "conn-1", catalog: "other", isExpanded: false, children: [databaseNode("conn-1:other:deep", "deep")] },
]);
expect(resolveSidebarSearchDatabaseScope(node, OPTIONS)).toEqual(new Set(["conn-1:cat:nested:loaded"]));
});
it("treats schema-mode trees without database nodes as unrestricted", () => {
const schemaNode: TreeNode = { id: "conn-1:public", label: "public", type: "schema", connectionId: "conn-1", database: "main", isExpanded: false, children: [] };
expect(resolveSidebarSearchDatabaseScope(connectionNode([schemaNode]), OPTIONS)).toEqual(null);
});
});
describe("isSidebarSearchPrunedDatabaseNode", () => {
it.each([
["database outside the scope", databaseNode("conn-1:b", "b"), new Set(["conn-1:a"]), true],
["database inside the scope", databaseNode("conn-1:a", "a"), new Set(["conn-1:a"]), false],
["unrestricted scope", databaseNode("conn-1:b", "b"), null, false],
["non-database container", { id: "conn-1", label: "prod", type: "connection", connectionId: "conn-1", isExpanded: true, children: [] } as TreeNode, new Set(["conn-1:a"]), false],
] as const)("returns %expected when the node is a %s", (_name, node, scope, expected) => {
expect(isSidebarSearchPrunedDatabaseNode(node, scope)).toBe(expected);
});
});
@@ -89,6 +89,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"sidebarObjectDisplay",
"routineSourceOpenMode",
"sidebarTableSearchEnabled",
"sidebarSearchOpenedDatabasesOnly",
"autoSelectActiveSidebarNode",
"sidebarBrowseObjectsOnDatabaseActivation",
"openTabsRestoreMode",
@@ -201,6 +201,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ id: "navigation-object-display", category: "navigation", titleKey: "settings.sidebarObjectDisplay", targetId: "navigation" },
{ id: "navigation-object-info", category: "navigation", titleKey: "settings.sidebarObjectInfoMode", descriptionKey: "settings.sidebarObjectInfoModeDescription", targetId: "navigation" },
{ id: "navigation-table-search", category: "navigation", titleKey: "settings.sidebarTableSearchEnabled", descriptionKey: "settings.sidebarTableSearchEnabledDescription", targetId: "navigation" },
{ id: "navigation-search-opened-databases-only", category: "navigation", titleKey: "settings.sidebarSearchOpenedDatabasesOnly", descriptionKey: "settings.sidebarSearchOpenedDatabasesOnlyDescription", targetId: "navigation" },
{ id: "navigation-active-node", category: "navigation", titleKey: "settings.autoSelectActiveSidebarNode", descriptionKey: "settings.autoSelectActiveSidebarNodeDescription", targetId: "navigation" },
{ id: "navigation-browse-objects-on-database-activation", category: "navigation", titleKey: "settings.sidebarBrowseObjectsOnDatabaseActivation", descriptionKey: "settings.sidebarBrowseObjectsOnDatabaseActivationDescription", targetId: "navigation" },
{ id: "navigation-tabs-restore", category: "navigation", titleKey: "settings.openTabsRestoreMode", descriptionKey: "settings.openTabsRestoreModeDescription", targetId: "navigation" },
@@ -122,6 +122,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readon
"sidebarObjectDisplay",
"routineSourceOpenMode",
"sidebarTableSearchEnabled",
"sidebarSearchOpenedDatabasesOnly",
"autoSelectActiveSidebarNode",
"sidebarBrowseObjectsOnDatabaseActivation",
"openTabsRestoreMode",
@@ -0,0 +1,56 @@
import type { TreeNode, TreeNodeType } from "@/types/database";
import { sidebarDatabaseOpenKey } from "@/lib/sidebar/sidebarDatabaseOpenState";
// Database-level containers whose objects the sidebar search walker may load.
// Mirrors the sidebar "database opened" indicator (sidebarDatabaseOpenState):
// a database participates in the automatic search only when the user actually
// opened it — children loaded in the tree or referenced by an open editor tab.
const searchScopeDatabaseTypes = new Set<TreeNodeType>(["database", "mongo-db", "vector-database"]);
export interface SidebarSearchDatabaseScopeOptions {
enabled: boolean;
isChildrenLoaded: (nodeId: string) => boolean;
openDatabaseKeys: ReadonlySet<string>;
}
function collectOpenedDatabaseNodeIds(node: TreeNode, opened: Set<string>, options: SidebarSearchDatabaseScopeOptions): void {
for (const child of node.children ?? []) {
if (searchScopeDatabaseTypes.has(child.type)) {
const isOpened = !!child.connectionId && child.database != null && (options.isChildrenLoaded(child.id) || options.openDatabaseKeys.has(sidebarDatabaseOpenKey(child.connectionId, child.database)));
if (isOpened) opened.add(child.id);
// Nothing below a database-level node is itself database-level, so the
// subtree never contributes another scope entry.
continue;
}
// Pass-through containers (connection groups, Doris catalogs, linked
// servers) keep their nested database nodes scannable.
collectOpenedDatabaseNodeIds(child, opened, options);
}
}
/**
* Node ids of database-level nodes the sidebar search may load for one
* connection, or null when the search must stay unrestricted.
*
* null means either the setting is off, the connection exposes no
* database-level nodes (schema-mode trees such as Oracle, Redis, etc.), or
* none of them is opened — in that case falling back to searching every
* database keeps the box useful right after connecting, before the user
* opens anything.
*/
export function resolveSidebarSearchDatabaseScope(connectionNode: TreeNode, options: SidebarSearchDatabaseScopeOptions): ReadonlySet<string> | null {
if (!options.enabled) return null;
const opened = new Set<string>();
collectOpenedDatabaseNodeIds(connectionNode, opened, options);
return opened.size > 0 ? opened : null;
}
/**
* Whether the search walker must skip a node: a database-level node outside
* the opened scope. Containers and non-database nodes are never pruned, and a
* null scope (unrestricted) prunes nothing.
*/
export function isSidebarSearchPrunedDatabaseNode(node: TreeNode, scope: ReadonlySet<string> | null): boolean {
if (!scope) return false;
return searchScopeDatabaseTypes.has(node.type) && !scope.has(node.id);
}
+4
View File
@@ -957,6 +957,7 @@ export interface EditorSettings {
sidebarTableSearchEnabled: boolean;
sidebarTableSearchLocal: boolean;
sidebarGlobalSearchLocal: boolean;
sidebarSearchOpenedDatabasesOnly: boolean;
autoSelectActiveSidebarNode: boolean;
sidebarBrowseObjectsOnDatabaseActivation: boolean;
sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: number;
@@ -1244,6 +1245,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
sidebarTableSearchEnabled: false,
sidebarTableSearchLocal: true,
sidebarGlobalSearchLocal: false,
sidebarSearchOpenedDatabasesOnly: true,
autoSelectActiveSidebarNode: false,
sidebarBrowseObjectsOnDatabaseActivation: false,
sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION,
@@ -1816,6 +1818,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
sidebarTableSearchEnabled: typeof settings.sidebarTableSearchEnabled === "boolean" ? settings.sidebarTableSearchEnabled : DEFAULT_EDITOR_SETTINGS.sidebarTableSearchEnabled,
sidebarTableSearchLocal: typeof settings.sidebarTableSearchLocal === "boolean" ? settings.sidebarTableSearchLocal : DEFAULT_EDITOR_SETTINGS.sidebarTableSearchLocal,
sidebarGlobalSearchLocal: typeof settings.sidebarGlobalSearchLocal === "boolean" ? settings.sidebarGlobalSearchLocal : DEFAULT_EDITOR_SETTINGS.sidebarGlobalSearchLocal,
sidebarSearchOpenedDatabasesOnly: typeof settings.sidebarSearchOpenedDatabasesOnly === "boolean" ? settings.sidebarSearchOpenedDatabasesOnly : DEFAULT_EDITOR_SETTINGS.sidebarSearchOpenedDatabasesOnly,
autoSelectActiveSidebarNode: settings.autoSelectActiveSidebarNode ?? DEFAULT_EDITOR_SETTINGS.autoSelectActiveSidebarNode,
sidebarBrowseObjectsOnDatabaseActivation:
typeof settings.sidebarBrowseObjectsOnDatabaseActivation === "boolean"
@@ -2661,6 +2664,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.sidebarTableSearchEnabled !== undefined) editorSettings.value.sidebarTableSearchEnabled = partial.sidebarTableSearchEnabled;
if (partial.sidebarTableSearchLocal !== undefined) editorSettings.value.sidebarTableSearchLocal = partial.sidebarTableSearchLocal;
if (partial.sidebarGlobalSearchLocal !== undefined) editorSettings.value.sidebarGlobalSearchLocal = partial.sidebarGlobalSearchLocal;
if (partial.sidebarSearchOpenedDatabasesOnly !== undefined) editorSettings.value.sidebarSearchOpenedDatabasesOnly = partial.sidebarSearchOpenedDatabasesOnly;
if (partial.autoSelectActiveSidebarNode !== undefined) editorSettings.value.autoSelectActiveSidebarNode = partial.autoSelectActiveSidebarNode;
if (partial.sidebarBrowseObjectsOnDatabaseActivation !== undefined) editorSettings.value.sidebarBrowseObjectsOnDatabaseActivation = partial.sidebarBrowseObjectsOnDatabaseActivation === true;
if (partial.openTabsRestoreMode !== undefined) editorSettings.value.openTabsRestoreMode = normalizeOpenTabsRestoreMode(partial.openTabsRestoreMode);