From a1acaf209652d7162bc8f1476ac8dc6353e91abb Mon Sep 17 00:00:00 2001 From: Aasim Khan Date: Fri, 31 Mar 2023 09:18:36 -0700 Subject: [PATCH] Adding caching in OE service (#22539) --- .../objectExplorer/browser/asyncServerTree.ts | 42 +++---------------- .../browser/objectExplorerService.ts | 34 ++++++++++++++- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts index bdca7cc7f4..0a77592a97 100644 --- a/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts +++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts @@ -70,7 +70,7 @@ export class AsyncServerTree extends WorkbenchAsyncDataTree { + public override getDataNode(element: ServerTreeElement, throwError: boolean = true): IAsyncDataTreeNode | undefined { try { const node = super.getDataNode(element); return node; @@ -79,7 +79,10 @@ export class AsyncServerTree extends WorkbenchAsyncDataTree { for (let element of elements) { - const id = element.id; - const node = this.getDataNodeById(id); + const node = this.getDataNode(element, false); if (node) { await this.expand(node.element); - } else { - // If the node is not found in the nodes map, we search for the node by comparing the relative paths of the elements - if (element) { - const elementPath = this.getRelativePath(element); - for (let n of this.nodes.values()) { - if (this.getRelativePath(n.element) === elementPath) { - await this.expand(n.element); - break; - } - } - } } } } - /** - * Get the relative path of the element in the tree. For connection and group, the path is the id of the element. - * For other elements, the path is the node path of the element and the id of the connection they belong to. - */ - private getRelativePath(element: ServerTreeElement): string { - let path = ''; - if (element instanceof TreeNode) { - path = element.nodePath; - let parent = element.parent; - while (parent.parent) { - parent = parent.parent; - } - if (parent.connection) { - path = parent.connection.id + '/' + path; - } - } else if (element instanceof ConnectionProfile || element instanceof ConnectionProfileGroup) { - path = element.id; - } - return path; - } - /** * Mark the element as dirty so that it will be refreshed when it is expanded next time * @param element The element to mark as dirty diff --git a/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts b/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts index 31c4fc873d..c761662697 100644 --- a/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts +++ b/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts @@ -179,6 +179,9 @@ export class ObjectExplorerService implements IObjectExplorerService { private _connectionsWaitingForSession: Map = new Map(); + // Cache of tree nodes for each connection by session ids + private _treeNodeCache: Map> = new Map>(); + constructor( @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IAdsTelemetryService private _telemetryService: IAdsTelemetryService, @@ -233,6 +236,7 @@ export class ObjectExplorerService implements IObjectExplorerService { if (!session) { return; } + this._treeNodeCache.delete(session.sessionId); await this.closeSession(connection.providerName, session); delete this._activeObjectExplorerNodes[connectionUri]; delete this._sessions[session.sessionId!]; @@ -340,6 +344,8 @@ export class ObjectExplorerService implements IObjectExplorerService { try { if (session.success && session.rootNode) { let server = this.toTreeNode(session.rootNode, undefined); + this._treeNodeCache.set(sessionId, new Map()); + this._treeNodeCache.get(sessionId)!.set(this.getTreeNodeCacheKey(server.toNodeInfo()), server); server.connection = connection; server.session = session; this._activeObjectExplorerNodes[connection!.id] = server; @@ -730,9 +736,31 @@ export class ObjectExplorerService implements IObjectExplorerService { throw new Error('Failed to expand node - no provider name'); } const expandResult = await this.callExpandOrRefreshFromService(providerName, session, parentTree, refresh); + const sessionTreeNodeCache = this._treeNodeCache.get(session.sessionId!); if (expandResult && expandResult.nodes) { + // In case of refresh, we want to clear the cache of the descendants of the node being refreshed + if (refresh && parentTree?.children) { + const stack = [...parentTree.children]; + while (stack.length > 0) { + const currentTreeNode = stack.pop(); + if (currentTreeNode) { + sessionTreeNodeCache.delete(this.getTreeNodeCacheKey(currentTreeNode.toNodeInfo())); + if (currentTreeNode.children) { + stack.push(...currentTreeNode.children); + } + } + } + } const children = expandResult.nodes.map(node => { - return this.toTreeNode(node, parentTree); + const cacheKey = this.getTreeNodeCacheKey(node); + // In case of refresh, we want to update the existing node in the cache + if (!refresh && sessionTreeNodeCache.has(cacheKey)) { + return sessionTreeNodeCache.get(cacheKey); + } else { + const treeNode = this.toTreeNode(node, parentTree); + sessionTreeNodeCache.set(cacheKey, treeNode); + return treeNode; + } }); parentTree.children = children.filter(c => c !== undefined); return children; @@ -1004,4 +1032,8 @@ export class ObjectExplorerService implements IObjectExplorerService { public getObjectExplorerTimeout(): number { return this._configurationService.getValue(NODE_EXPANSION_CONFIG); } + + private getTreeNodeCacheKey(node: azdata.NodeInfo): string { + return node.nodePath; + } }