From 1356f0bcf6b958665f8cfcd27baa42dbbd08439b Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Wed, 5 Sep 2018 12:11:33 -0700 Subject: [PATCH] Readd query plan (#2409) * fix grid links * formatting * remove commented code * adding query plan * asd * add query plan * fix title --- src/sql/parts/grid/services/sharedServices.ts | 2 +- src/sql/parts/query/editor/actions.ts | 22 ++++++ src/sql/parts/query/editor/gridPanel.ts | 5 +- .../parts/query/editor/queryResultsEditor.ts | 4 ++ .../parts/query/editor/queryResultsView.ts | 14 +++- src/sql/parts/query/execution/queryRunner.ts | 9 +++ src/sql/parts/queryPlan/queryPlan.ts | 69 +++++++++++++++++++ 7 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 src/sql/parts/queryPlan/queryPlan.ts diff --git a/src/sql/parts/grid/services/sharedServices.ts b/src/sql/parts/grid/services/sharedServices.ts index 8c3f038f12..f076038a29 100644 --- a/src/sql/parts/grid/services/sharedServices.ts +++ b/src/sql/parts/grid/services/sharedServices.ts @@ -106,4 +106,4 @@ export const textFormatter: Slick.Formatter = (row, cell, value, columnDef, return cellContainer.outerHTML; }; -*/ \ No newline at end of file +*/ diff --git a/src/sql/parts/query/editor/actions.ts b/src/sql/parts/query/editor/actions.ts index 53b1336c5e..13effdeb7a 100644 --- a/src/sql/parts/query/editor/actions.ts +++ b/src/sql/parts/query/editor/actions.ts @@ -180,3 +180,25 @@ export class ChartDataAction extends Action { } } } + +export class ShowQueryPlanAction extends Action { + public static ID = 'showQueryPlan'; + public static LABEL = localize('showQueryPlan', 'Show Query Plan'); + + constructor( + @IEditorService private editorService: IEditorService + ) { + super(ShowQueryPlanAction.ID, ShowQueryPlanAction.LABEL); + } + + public run(xml: string): TPromise { + let activeEditor = this.editorService.activeControl; + if (activeEditor instanceof QueryEditor) { + activeEditor.resultsEditor.showQueryPlan(xml); + return TPromise.as(true); + } else { + return TPromise.as(false); + } + } + +} diff --git a/src/sql/parts/query/editor/gridPanel.ts b/src/sql/parts/query/editor/gridPanel.ts index 6f58860b85..a41d92037c 100644 --- a/src/sql/parts/query/editor/gridPanel.ts +++ b/src/sql/parts/query/editor/gridPanel.ts @@ -12,7 +12,7 @@ import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scr import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin'; import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin'; import { SaveFormat } from 'sql/parts/grid/common/interfaces'; -import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, MinimizeTableAction, ChartDataAction } from 'sql/parts/query/editor/actions'; +import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, MinimizeTableAction, ChartDataAction, ShowQueryPlanAction } from 'sql/parts/query/editor/actions'; import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin'; import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin'; import { escape } from 'sql/base/common/strings'; @@ -364,6 +364,9 @@ class GridTable extends Disposable implements IView { private loadData(offset: number, count: number): Thenable { return this.runner.getQueryRows(offset, count, this.resultSet.batchId, this.resultSet.id).then(response => { + if (this.runner.isQueryPlan) { + this.instantiationService.createInstance(ShowQueryPlanAction).run(response.resultSubset.rows[0][0].displayValue); + } return response.resultSubset.rows.map(r => { let dataWithSchema = {}; // skip the first column since its a number column diff --git a/src/sql/parts/query/editor/queryResultsEditor.ts b/src/sql/parts/query/editor/queryResultsEditor.ts index 2f1d6ff1cf..cd08394335 100644 --- a/src/sql/parts/query/editor/queryResultsEditor.ts +++ b/src/sql/parts/query/editor/queryResultsEditor.ts @@ -152,6 +152,10 @@ export class QueryResultsEditor extends BaseEditor { this.resultsView.chartData(dataId); } + public showQueryPlan(xml: string) { + this.resultsView.showPlan(xml); + } + public dispose(): void { super.dispose(); } diff --git a/src/sql/parts/query/editor/queryResultsView.ts b/src/sql/parts/query/editor/queryResultsView.ts index 4c18f263a0..f8223e29ea 100644 --- a/src/sql/parts/query/editor/queryResultsView.ts +++ b/src/sql/parts/query/editor/queryResultsView.ts @@ -10,13 +10,14 @@ import { IQueryModelService } from '../execution/queryModel'; import QueryRunner from 'sql/parts/query/execution/queryRunner'; import { MessagePanel } from './messagePanel'; import { GridPanel } from './gridPanel'; +import { ChartTab } from './charting/chartTab'; +import { QueryPlanTab } from 'sql/parts/queryPlan/queryPlan'; import * as nls from 'vs/nls'; import * as UUID from 'vs/base/common/uuid'; import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as DOM from 'vs/base/browser/dom'; -import { ChartTab } from './charting/chartTab'; class ResultsView implements IPanelView { private panelViewlet: PanelViewlet; @@ -79,6 +80,7 @@ export class QueryResultsView { private _input: QueryResultsInput; private resultsTab: ResultsTab; private chartTab: ChartTab; + private qpTab: QueryPlanTab; constructor( container: HTMLElement, @@ -88,6 +90,7 @@ export class QueryResultsView { this.resultsTab = new ResultsTab(instantiationService); this.chartTab = new ChartTab(instantiationService); this._panelView = new TabbedPanel(container, { showHeaderWhenSingleView: false }); + this.qpTab = new QueryPlanTab(); } public style() { @@ -119,4 +122,13 @@ export class QueryResultsView { this._panelView.showTab(this.chartTab.identifier); this.chartTab.chart(dataId); } + + public showPlan(xml: string) { + if (!this._panelView.contains(this.qpTab)) { + this._panelView.pushTab(this.qpTab); + } + + this._panelView.showTab(this.qpTab.identifier); + this.qpTab.view.showPlan(xml); + } } diff --git a/src/sql/parts/query/execution/queryRunner.ts b/src/sql/parts/query/execution/queryRunner.ts index 8b4e284cba..6d701c066a 100644 --- a/src/sql/parts/query/execution/queryRunner.ts +++ b/src/sql/parts/query/execution/queryRunner.ts @@ -63,6 +63,9 @@ export default class QueryRunner { private _hasCompleted: boolean = false; private _batchSets: sqlops.BatchSummary[] = []; private _eventEmitter = new EventEmitter(); + private _isQueryPlan: boolean; + + public get isQueryPlan(): boolean { return this._isQueryPlan; } private _onMessage = new Emitter(); public readonly onMessage = debounceEvent(echo(this._onMessage.event), (l, e) => { @@ -171,6 +174,12 @@ export default class QueryRunner { this._totalElapsedMilliseconds = 0; // TODO issue #228 add statusview callbacks here + if (runOptions && (runOptions.displayActualQueryPlan || runOptions.displayEstimatedQueryPlan)) { + this._isQueryPlan = true; + } else { + this._isQueryPlan = false; + } + // Send the request to execute the query return runCurrentStatement ? this._queryManagementService.runQueryStatement(ownerUri, input.startLine, input.startColumn).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e)) diff --git a/src/sql/parts/queryPlan/queryPlan.ts b/src/sql/parts/queryPlan/queryPlan.ts new file mode 100644 index 0000000000..eb15477765 --- /dev/null +++ b/src/sql/parts/queryPlan/queryPlan.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as QP from 'html-query-plan'; + +import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel'; + +import { Dimension } from 'vs/base/browser/dom'; +import { localize } from 'vs/nls'; +import * as UUID from 'vs/base/common/uuid'; +import { Builder } from 'vs/base/browser/builder'; + +export class QueryPlanTab implements IPanelTab { + public readonly title = localize('queryPlanTitle', 'Query Plan'); + public readonly identifier = UUID.generateUuid(); + public readonly view: QueryPlanView; + + constructor() { + this.view = new QueryPlanView(); + } +} + +export class QueryPlanView implements IPanelView { + private qp: QueryPlan; + private xml: string; + private container = document.createElement('div'); + + public render(container: HTMLElement): void { + if (!this.qp) { + this.qp = new QueryPlan(this.container); + if (this.xml) { + this.qp.xml = this.xml; + } + } + container.appendChild(this.container); + } + + public layout(dimension: Dimension): void { + } + + public showPlan(xml: string) { + if (this.qp) { + this.qp.xml = xml; + } else { + this.xml = xml; + } + } +} + +export class QueryPlan { + private _xml: string; + constructor(private container: HTMLElement) { + } + + public set xml(xml: string) { + this._xml = xml; + new Builder(this.container).empty(); + QP.showPlan(this.container, this._xml, { + jsTooltips: false + }); + } + + public get xml(): string { + return this._xml; + } +}