mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-07 01:25:38 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
295
src/sql/parts/queryPlan/planXmlParser.ts
Normal file
295
src/sql/parts/queryPlan/planXmlParser.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export class RunTimeInformation {
|
||||
runtimePerThreads: RuntimePerThread[];
|
||||
public get actualRows(): number {
|
||||
let total = 0;
|
||||
if (this.runtimePerThreads) {
|
||||
this.runtimePerThreads.forEach(element => {
|
||||
total += element.actualRow;
|
||||
});
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public get actualExecutions(): number {
|
||||
let total = 0;
|
||||
if (this.runtimePerThreads) {
|
||||
this.runtimePerThreads.forEach(element => {
|
||||
total += element.actualExecutions;
|
||||
});
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
export class RuntimePerThread {
|
||||
threadId: number;
|
||||
actualRow: number;
|
||||
actualExecutionMode: string;
|
||||
actualExecutions: number;
|
||||
}
|
||||
|
||||
export class IndexObject {
|
||||
database: string;
|
||||
schema: string;
|
||||
table: string;
|
||||
index: string;
|
||||
indexKind: string;
|
||||
|
||||
public get title() {
|
||||
let title: string = '';
|
||||
if (this.database && this.schema && this.table) {
|
||||
title = `${this.database}.${this.schema}.${this.table}.${this.index}`;
|
||||
if (this.indexKind && this.indexKind !== '') {
|
||||
title += `(${this.indexKind})`;
|
||||
}
|
||||
}
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
export class PlanNode {
|
||||
root: PlanNode;
|
||||
subtreeCost: number;
|
||||
private childrenNodes: PlanNode[];
|
||||
parent: PlanNode;
|
||||
physicalOp: string;
|
||||
logicalOp: string;
|
||||
id: number;
|
||||
estimateRows: string;
|
||||
estimateIo: string;
|
||||
estimateCpu: string;
|
||||
parallel: boolean;
|
||||
partitioned: boolean;
|
||||
estimateRewinds: string;
|
||||
estimateRebinds: string;
|
||||
runtimeInfo: RunTimeInformation;
|
||||
indexObject: IndexObject;
|
||||
|
||||
public addChildren(children: PlanNode[]): void {
|
||||
if(children) {
|
||||
children.forEach(element => {
|
||||
element.parent = this;
|
||||
});
|
||||
}
|
||||
this.childrenNodes = children;
|
||||
}
|
||||
|
||||
public get totalSubTreeCost(): number {
|
||||
let total = this.subtreeCost;
|
||||
if (total === 0) {
|
||||
this.children.forEach(element => {
|
||||
total += element.subtreeCost;
|
||||
});
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public get children(): PlanNode[] {
|
||||
return this.childrenNodes;
|
||||
}
|
||||
|
||||
public get cost(): number {
|
||||
let total = this.subtreeCost;
|
||||
if (this.children && total !== 0) {
|
||||
this.children.forEach(element => {
|
||||
total -= element.subtreeCost;
|
||||
});
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public get relativeCost(): number {
|
||||
let overallCost = this.root.totalSubTreeCost;
|
||||
return overallCost > 0 ? this.cost / overallCost : 0;
|
||||
}
|
||||
|
||||
public get estimatedOperatorCost(): number {
|
||||
return Math.round(this.relativeCost * 100);
|
||||
}
|
||||
|
||||
public get estimatedSubtreeCost(): number {
|
||||
let total = this.estimatedOperatorCost;
|
||||
if (this.children) {
|
||||
this.children.forEach(element => {
|
||||
total += element.estimatedSubtreeCost;
|
||||
});
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
if (this.physicalOp === this.logicalOp) {
|
||||
return this.physicalOp;
|
||||
} else {
|
||||
return `${this.physicalOp}(${this.logicalOp})`;
|
||||
}
|
||||
}
|
||||
|
||||
public get treeViewPrefix(): string {
|
||||
return this.parent === undefined ? '' : `${this.parent.treeViewPrefix}-----`;
|
||||
}
|
||||
|
||||
public get treeViewTitle(): string {
|
||||
return `${this.treeViewPrefix}${this.title}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class PlanXmlParser {
|
||||
parser: DOMParser = new DOMParser();
|
||||
doc: Document;
|
||||
planXml: string
|
||||
root: PlanNode;
|
||||
|
||||
constructor(planXml: string) {
|
||||
|
||||
this.doc = this.parser.parseFromString(planXml, 'application/xml');
|
||||
this.planXml = planXml;
|
||||
let queryPlanNode = this.findChildren(this.doc.childNodes[0], 'QueryPlan');
|
||||
if (queryPlanNode && queryPlanNode.length > 0) {
|
||||
this.root = new PlanNode();
|
||||
let ops = this.createPlanNodes(queryPlanNode[0], 'RelOp', this.root);
|
||||
|
||||
this.root.addChildren(ops);
|
||||
this.root.subtreeCost = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public get topOperations(): PlanNode[] {
|
||||
let operations: PlanNode[] = [];
|
||||
if (this.root && this.root.children) {
|
||||
operations = this.addOperationsToList(operations, this.root.children);
|
||||
operations.sort((a, b) => {
|
||||
if (a.estimatedOperatorCost > b.estimatedOperatorCost) {
|
||||
return -1;
|
||||
} else if (a.estimatedOperatorCost <= b.estimatedOperatorCost) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
return operations;
|
||||
}
|
||||
|
||||
public get toTreeViewList(): PlanNode[] {
|
||||
let operations: PlanNode[] = [];
|
||||
operations = this.addOperationsToList(operations, this.root.children);
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
private addOperationsToList(list: PlanNode[], nodes: PlanNode[]): PlanNode[] {
|
||||
list = list.concat(nodes);
|
||||
nodes.forEach(element => {
|
||||
list = this.addOperationsToList(list, element.children);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
private findChildren(node: Node, elementName: string, untilNode: string = undefined): Node[] {
|
||||
let nodes: Node[] = [];
|
||||
if (node === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (var index = 0; index < node.childNodes.length; index++) {
|
||||
if (node.childNodes[index].nodeName.toLocaleLowerCase() === elementName.toLocaleLowerCase()) {
|
||||
nodes = nodes.concat(node.childNodes[index]);
|
||||
}
|
||||
}
|
||||
if (nodes.length > 0) {
|
||||
return nodes;
|
||||
}
|
||||
for (var index = 0; index < node.childNodes.length; index++) {
|
||||
if (untilNode && node.childNodes[index].nodeName === untilNode) {
|
||||
continue;
|
||||
}
|
||||
let result = this.findChildren(node.childNodes[index], elementName, untilNode);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private createPlanNodes(node: Node, elementName: string, root: PlanNode): PlanNode[] {
|
||||
let nodePlans: PlanNode[] = [];
|
||||
|
||||
let children = this.findChildren(node, elementName);
|
||||
if (children) {
|
||||
for (var index = 0; index < children.length; index++) {
|
||||
let childNode = children[index];
|
||||
|
||||
let planNode = this.convertToPlanNode(childNode);
|
||||
planNode.root = root;
|
||||
planNode.addChildren(this.createPlanNodes(childNode, elementName, root));
|
||||
planNode.runtimeInfo = new RunTimeInformation();
|
||||
planNode.indexObject = new IndexObject();
|
||||
|
||||
let runtimeInfoNodes = this.findChildren(childNode, 'RunTimeCountersPerThread');
|
||||
if (runtimeInfoNodes) {
|
||||
planNode.runtimeInfo.runtimePerThreads = runtimeInfoNodes.map(x => this.convertToRuntimeInfo(x));
|
||||
}
|
||||
|
||||
let objectNodes = this.findChildren(childNode, 'Object', 'RelOp');
|
||||
if (objectNodes && objectNodes.length > 0) {
|
||||
planNode.indexObject = this.convertToObject(objectNodes[0]);
|
||||
}
|
||||
nodePlans = nodePlans.concat(planNode);
|
||||
}
|
||||
}
|
||||
|
||||
return nodePlans;
|
||||
}
|
||||
|
||||
private convertToPlanNode(node: Node): PlanNode {
|
||||
let planNode = new PlanNode();
|
||||
planNode.id = this.findAttribute(node.attributes, 'NodeId');
|
||||
planNode.logicalOp = this.findAttribute(node.attributes, 'LogicalOp');
|
||||
planNode.physicalOp = this.findAttribute(node.attributes, 'PhysicalOp');
|
||||
planNode.subtreeCost = +this.findAttribute(node.attributes, 'EstimatedTotalSubtreeCost');
|
||||
planNode.estimateRows = this.findAttribute(node.attributes, 'EstimateRows');
|
||||
planNode.estimateCpu = this.findAttribute(node.attributes, 'EstimateCPU');
|
||||
planNode.estimateIo = this.findAttribute(node.attributes, 'EstimateIO');
|
||||
planNode.estimateRebinds = this.findAttribute(node.attributes, 'EstimateRebinds');
|
||||
planNode.estimateRewinds = this.findAttribute(node.attributes, 'EstimateRewinds');
|
||||
planNode.parallel = this.findAttribute(node.attributes, 'Parallel') === '1';
|
||||
planNode.partitioned = this.findAttribute(node.attributes, 'Partitioned') === '1';
|
||||
return planNode;
|
||||
}
|
||||
|
||||
private convertToRuntimeInfo(node: Node): RuntimePerThread {
|
||||
let runtimeNode = new RuntimePerThread();
|
||||
runtimeNode.actualExecutionMode = this.findAttribute(node.attributes, 'ActualExecutionMode');
|
||||
runtimeNode.actualExecutions = +this.findAttribute(node.attributes, 'ActualExecutions');
|
||||
runtimeNode.actualRow = +this.findAttribute(node.attributes, 'ActualRows');
|
||||
runtimeNode.threadId = +this.findAttribute(node.attributes, 'Thread');
|
||||
return runtimeNode;
|
||||
}
|
||||
|
||||
private convertToObject(node: Node): IndexObject {
|
||||
let objectNode = new IndexObject();
|
||||
objectNode.database = this.findAttribute(node.attributes, 'Database');
|
||||
objectNode.index = this.findAttribute(node.attributes, 'Index');
|
||||
objectNode.indexKind = this.findAttribute(node.attributes, 'IndexKind');
|
||||
objectNode.schema = this.findAttribute(node.attributes, 'Schema');
|
||||
objectNode.table = this.findAttribute(node.attributes, 'Table');
|
||||
return objectNode;
|
||||
}
|
||||
|
||||
private findAttribute(attributes: NamedNodeMap, attName: string): any {
|
||||
for (var index = 0; index < attributes.length; index++) {
|
||||
var attribute = attributes[index];
|
||||
if (attribute.name === attName) {
|
||||
return attribute.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/sql/parts/queryPlan/queryPlan.component.ts
Normal file
69
src/sql/parts/queryPlan/queryPlan.component.ts
Normal file
@@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/parts/grid/load/css/qp';
|
||||
|
||||
import { ElementRef, Component, Inject, forwardRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import * as QP from 'html-query-plan';
|
||||
|
||||
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { QueryPlanParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import * as colors from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
export const QUERYPLAN_SELECTOR: string = 'queryplan-component';
|
||||
|
||||
@Component({
|
||||
selector: QUERYPLAN_SELECTOR,
|
||||
template: `
|
||||
<div #container class="fullsize" style="overflow: scroll">
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class QueryPlanComponent implements OnDestroy, OnInit {
|
||||
|
||||
private _planXml: string;
|
||||
private _disposables: Array<IDisposable> = [];
|
||||
@ViewChild('container', { read: ElementRef }) _container: ElementRef;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService
|
||||
) { }
|
||||
|
||||
ngOnDestroy() {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
let parameters: QueryPlanParams = this._bootstrapService.getBootstrapParams(this._el.nativeElement.tagName);
|
||||
if (parameters) {
|
||||
this.planXml = parameters.planXml;
|
||||
}
|
||||
this._disposables.push(registerThemingParticipant(this._updateTheme));
|
||||
}
|
||||
|
||||
public set planXml(val: string) {
|
||||
this._planXml = val;
|
||||
QP.showPlan(this._container.nativeElement, this._planXml, {
|
||||
jsTooltips: false
|
||||
});
|
||||
}
|
||||
|
||||
private _updateTheme(theme: ITheme, collector: ICssStyleCollector) {
|
||||
let backgroundColor = theme.getColor(colors.editorBackground);
|
||||
let foregroundColor = theme.getColor(colors.editorForeground);
|
||||
|
||||
if (backgroundColor) {
|
||||
collector.addRule(`div.qp-node, .qp-tt, .qp-root { background-color: ${backgroundColor} }`);
|
||||
}
|
||||
|
||||
if (foregroundColor) {
|
||||
collector.addRule(`.qp-root { color: ${foregroundColor} }`);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/sql/parts/queryPlan/queryPlan.module.ts
Normal file
38
src/sql/parts/queryPlan/queryPlan.module.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { NgModule, Inject, forwardRef, ApplicationRef, ComponentFactoryResolver } from '@angular/core';
|
||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { QueryPlanComponent, QUERYPLAN_SELECTOR } from 'sql/parts/queryPlan/queryPlan.component';
|
||||
|
||||
// Connection Dashboard main angular module
|
||||
@NgModule({
|
||||
declarations: [
|
||||
QueryPlanComponent
|
||||
],
|
||||
entryComponents: [QueryPlanComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
BrowserModule
|
||||
],
|
||||
providers: [{ provide: APP_BASE_HREF, useValue: '/' }]
|
||||
})
|
||||
export class QueryPlanModule {
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService
|
||||
) {
|
||||
}
|
||||
|
||||
ngDoBootstrap(appRef: ApplicationRef) {
|
||||
const factory = this._resolver.resolveComponentFactory(QueryPlanComponent);
|
||||
const uniqueSelector: string = this._bootstrapService.getUniqueSelector(QUERYPLAN_SELECTOR);
|
||||
(<any>factory).factory.selector = uniqueSelector;
|
||||
appRef.bootstrap(factory);
|
||||
}
|
||||
}
|
||||
127
src/sql/parts/queryPlan/queryPlanEditor.ts
Normal file
127
src/sql/parts/queryPlan/queryPlanEditor.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/parts/query/editor/media/queryEditor';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { QueryPlanInput } from './queryPlanInput';
|
||||
import { QueryPlanModule } from './queryPlan.module';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { IMetadataService } from 'sql/services/metadata/metadataService';
|
||||
import { IScriptingService } from 'sql/services/scripting/scriptingService';
|
||||
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
|
||||
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { QueryPlanParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import { QUERYPLAN_SELECTOR } from 'sql/parts/queryPlan/queryPlan.component';
|
||||
|
||||
declare let QP;
|
||||
|
||||
export class QueryPlanEditor extends BaseEditor {
|
||||
|
||||
public static ID: string = 'workbench.editor.queryplan';
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService,
|
||||
@IMetadataService private _metadataService: IMetadataService,
|
||||
@IScriptingService private _scriptingService: IScriptingService,
|
||||
@IQueryEditorService private _queryEditorService: IQueryEditorService,
|
||||
@IBootstrapService private _bootstrapService: IBootstrapService
|
||||
) {
|
||||
super(QueryPlanEditor.ID, telemetryService, themeService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the editor in the parent builder.
|
||||
*/
|
||||
public createEditor(parent: Builder): void {
|
||||
//Enable scrollbars when drawing area is larger than viewport
|
||||
parent.overflow('auto');
|
||||
//Set background of parent to white (same as .qp-root from src\sql\parts\grid\load\css\qp.css)
|
||||
//This is because the bottom-most tooltips can extend past the drawing area, which causes the
|
||||
//scrolling area to have gaps on the bottom and left. So if the colors aren't matched then
|
||||
//these gaps show up as different colors and look bad.
|
||||
//Another option would be to check the tooltip positions and reposition them if necessary
|
||||
//during the load - but changing the background color was the simplest and least error prone
|
||||
//(plus it's probable that we won't be using this control in the future anyways if development)
|
||||
//continues on the Query plan feature
|
||||
parent.background('#fff');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets focus on this editor. Specifically, it sets the focus on the hosted text editor.
|
||||
*/
|
||||
public focus(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal variable keeping track of the editor's size, and re-calculates the sash position.
|
||||
* To be called when the container of this editor changes size.
|
||||
*/
|
||||
public layout(dimension: Dimension): void {
|
||||
}
|
||||
|
||||
public setInput(input: QueryPlanInput, options: EditorOptions): TPromise<void> {
|
||||
if (this.input instanceof QueryPlanInput && this.input.matches(input)) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
if (!input.hasInitialized) {
|
||||
this.bootstrapAngular(input);
|
||||
}
|
||||
this.revealElementWithTagName(input.uniqueSelector, this.getContainer().getHTMLElement());
|
||||
|
||||
return super.setInput(input, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reveal the child element with the given tagName and hide all other elements.
|
||||
*/
|
||||
private revealElementWithTagName(tagName: string, parent: HTMLElement): void {
|
||||
let elementToReveal: HTMLElement;
|
||||
|
||||
for (let i = 0; i < parent.children.length; i++) {
|
||||
let child: HTMLElement = <HTMLElement>parent.children[i];
|
||||
if (child.tagName && child.tagName.toLowerCase() === tagName && !elementToReveal) {
|
||||
elementToReveal = child;
|
||||
} else {
|
||||
child.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (elementToReveal) {
|
||||
elementToReveal.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the angular components and record for this input that we have done so
|
||||
*/
|
||||
private bootstrapAngular(input: QueryPlanInput): void {
|
||||
// Get the bootstrap params and perform the bootstrap
|
||||
let params: QueryPlanParams = {
|
||||
planXml: input.planXml
|
||||
};
|
||||
|
||||
let uniqueSelector = this._bootstrapService.bootstrap(
|
||||
QueryPlanModule,
|
||||
this.getContainer().getHTMLElement(),
|
||||
QUERYPLAN_SELECTOR,
|
||||
params);
|
||||
input.setUniqueSelector(uniqueSelector);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
67
src/sql/parts/queryPlan/queryPlanInput.ts
Normal file
67
src/sql/parts/queryPlan/queryPlanInput.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
|
||||
|
||||
export class QueryPlanInput extends EditorInput {
|
||||
|
||||
public static ID: string = 'workbench.editorinputs.queryplan';
|
||||
public static SCHEMA: string = 'queryplan';
|
||||
|
||||
private _uniqueSelector: string;
|
||||
|
||||
constructor(private _xml: string, private _uri: string, private _connection: ConnectionManagementInfo) {
|
||||
super();
|
||||
}
|
||||
|
||||
public setUniqueSelector(uniqueSelector: string): void {
|
||||
this._uniqueSelector = uniqueSelector;
|
||||
}
|
||||
|
||||
public getTypeId(): string {
|
||||
return UntitledEditorInput.ID;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return 'Query Plan';
|
||||
}
|
||||
|
||||
public get planXml(): string {
|
||||
return this._xml;
|
||||
}
|
||||
|
||||
public getUri(): string {
|
||||
return this._uri;
|
||||
}
|
||||
|
||||
public supportsSplitEditor(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public getConnectionProfile(): IConnectionProfile {
|
||||
//return this._connection.connectionProfile;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public resolve(refresh?: boolean): TPromise<EditorModel> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get hasInitialized(): boolean {
|
||||
return !!this._uniqueSelector;
|
||||
}
|
||||
|
||||
public get uniqueSelector(): string {
|
||||
return this._uniqueSelector;
|
||||
}
|
||||
|
||||
public getConnectionInfo(): ConnectionManagementInfo {
|
||||
return this._connection;
|
||||
}
|
||||
}
|
||||
121
src/sql/parts/queryPlan/topOperations.component.ts
Normal file
121
src/sql/parts/queryPlan/topOperations.component.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ElementRef, Component, Inject, forwardRef, OnDestroy, Input, OnInit } from '@angular/core';
|
||||
import { Subscription, Subject } from 'rxjs/Rx';
|
||||
|
||||
import { PlanXmlParser, PlanNode } from 'sql/parts/queryPlan/planXmlParser';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { attachTableStyler } from 'sql/common/theme/styler';
|
||||
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { QueryComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import * as GridContentEvents from 'sql/parts/grid/common/gridContentEvents';
|
||||
import { DataService } from 'sql/parts/grid/services/dataService';
|
||||
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const TOP_OPERATIONS_SELECTOR: string = 'top-operations-component';
|
||||
|
||||
@Component({
|
||||
selector: TOP_OPERATIONS_SELECTOR,
|
||||
template: '',
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => TopOperationsComponent) }]
|
||||
})
|
||||
export class TopOperationsComponent extends TabChild implements OnDestroy, OnInit {
|
||||
|
||||
private _operations: Array<PlanNode> = [];
|
||||
private _table: Table<any>;
|
||||
private _dataService: DataService;
|
||||
private toDispose: Array<IDisposable> = [];
|
||||
private _columns: Array<Slick.Column<any>> = [
|
||||
{ name: localize('topOperations.operation', 'Operation'), field: 'operation' },
|
||||
{ name: localize('topOperations.object', 'Object'), field: 'object' },
|
||||
{ name: localize('topOperations.estCost', 'Est Cost'), field: 'estCost' },
|
||||
{ name: localize('topOperations.estSubtreeCost', 'Est Subtree Cost'), field: 'estSubtreeCost' },
|
||||
{ name: localize('topOperations.actualRows', 'Actual Rows'), field: 'actualRows' },
|
||||
{ name: localize('topOperations.estRows', 'Est Rows'), field: 'estRows' },
|
||||
{ name: localize('topOperations.actualExecutions', 'Actual Executions'), field: 'actualExecutions' },
|
||||
{ name: localize('topOperations.estCPUCost', 'Est CPU Cost'), field: 'estCPUCost' },
|
||||
{ name: localize('topOperations.estIOCost', 'Est IO Cost'), field: 'estIOCost' },
|
||||
{ name: localize('topOperations.parallel', 'Parallel'), field: 'parallel' },
|
||||
{ name: localize('topOperations.actualRebinds', 'Actual Rebinds'), field: 'actualRebinds' },
|
||||
{ name: localize('topOperations.estRebinds', 'Est Rebinds'), field: 'estRebinds' },
|
||||
{ name: localize('topOperations.actualRewinds', 'Actual Rewinds'), field: 'actualRewinds' },
|
||||
{ name: localize('topOperations.estRewinds', 'Est Rewinds'), field: 'estRewinds' },
|
||||
{ name: localize('topOperations.partitioned', 'Partitioned'), field: 'partitioned' }
|
||||
];
|
||||
|
||||
@Input() public queryParameters: QueryComponentParams;
|
||||
|
||||
private _disposables: Array<IDisposable> = [];
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService,
|
||||
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._dataService = this.queryParameters.dataService;
|
||||
this.subscribeWithDispose(this._dataService.gridContentObserver, (type) => {
|
||||
switch (type) {
|
||||
case GridContentEvents.ResizeContents:
|
||||
this.layout();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
public set planXml(val: string) {
|
||||
let parser: PlanXmlParser = new PlanXmlParser(val);
|
||||
this._operations = parser.topOperations;
|
||||
let data = this._operations.map(i => {
|
||||
return {
|
||||
operation: i.title,
|
||||
object: i.indexObject.title,
|
||||
estCost: i.estimatedOperatorCost,
|
||||
estSubtreeCost: i.subtreeCost,
|
||||
actualRows: i.runtimeInfo.actualRows,
|
||||
estRows: i.estimateRows,
|
||||
actualExecutions: i.runtimeInfo.actualExecutions,
|
||||
estCPUCost: i.estimateCpu,
|
||||
estIOCost: i.estimateIo,
|
||||
parallel: i.parallel,
|
||||
actualRebinds: '',
|
||||
estRebinds: i.estimateRebinds,
|
||||
actualRewinds: '',
|
||||
estRewinds: i.estimateRewinds,
|
||||
partitioned: i.partitioned
|
||||
};
|
||||
});
|
||||
if (!this._table) {
|
||||
this._table = new Table(this._el.nativeElement, data, this._columns);
|
||||
this._disposables.push(attachTableStyler(this._table, this._bootstrapService.themeService));
|
||||
}
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
if (this._table) {
|
||||
setTimeout(() => {
|
||||
this._table.resizeCanvas();
|
||||
this._table.autosizeColumns();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected subscribeWithDispose<T>(subject: Subject<T>, event: (value: any) => void): void {
|
||||
let sub: Subscription = subject.subscribe(event);
|
||||
this.toDispose.push(toDisposableSubscription(sub));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user