mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Adding Execution Plan Editor to ADS (#18696)
* Pushing Execution Plan Editor * Renaming class Handling error * Awaiting for handlers to be registered * Addressing some PR comments * Fixing return type for provider * Fixing editor id and removing unnecessary overrides * Adding a namespace * adding execution plan namespace * Adding protocol comment * Fixing if logic * Fixing error message * Cleaning up code * cleanup code * Adding help comments * Fixing method call * Using path.ts to get the base file name * converting to lambda functions * Adding comment for run action * Fixing pr comments * Fixing editor label * Fixing doc comments * Adding some more comments * Fixign branding in comments
This commit is contained in:
@@ -759,6 +759,9 @@
|
|||||||
"connectionProvider": {
|
"connectionProvider": {
|
||||||
"providerId": "MSSQL",
|
"providerId": "MSSQL",
|
||||||
"displayName": "%mssql.provider.displayName%",
|
"displayName": "%mssql.provider.displayName%",
|
||||||
|
"supportedExecutionPlanFileExtensions": [
|
||||||
|
"sqlplan"
|
||||||
|
],
|
||||||
"iconPath": [
|
"iconPath": [
|
||||||
{
|
{
|
||||||
"id": "mssql:cloud",
|
"id": "mssql:cloud",
|
||||||
|
|||||||
@@ -1114,3 +1114,16 @@ export namespace DisposeTableDesignerRequest {
|
|||||||
export const type = new RequestType<azdata.designers.TableInfo, void, void, void>('tabledesigner/dispose');
|
export const type = new RequestType<azdata.designers.TableInfo, void, void, void>('tabledesigner/dispose');
|
||||||
}
|
}
|
||||||
// ------------------------------- < Table Designer > ------------------------------------
|
// ------------------------------- < Table Designer > ------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------- < Execution Plan > ------------------------------------
|
||||||
|
|
||||||
|
export interface GetExecutionPlanParams {
|
||||||
|
graphInfo: azdata.executionPlan.ExecutionPlanGraphInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace GetExecutionPlanRequest {
|
||||||
|
export const type = new RequestType<GetExecutionPlanParams, azdata.executionPlan.GetExecutionPlanResult, void, void>('queryexecutionplan/getexecutionplan');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------- < Execution Plan > ------------------------------------
|
||||||
|
|||||||
@@ -1183,3 +1183,48 @@ export class TableDesignerFeature extends SqlOpsFeature<undefined> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execution Plan Service Feature
|
||||||
|
* TODO: Move this feature to data protocol client repo once stablized
|
||||||
|
*/
|
||||||
|
export class ExecutionPlanServiceFeature extends SqlOpsFeature<undefined> {
|
||||||
|
private static readonly messagesTypes: RPCMessageType[] = [
|
||||||
|
contracts.GetExecutionPlanRequest.type,
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(client: SqlOpsDataClient) {
|
||||||
|
super(client, ExecutionPlanServiceFeature.messagesTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
public initialize(capabilities: ServerCapabilities): void {
|
||||||
|
this.register(this.messages, {
|
||||||
|
id: UUID.generateUuid(),
|
||||||
|
registerOptions: undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected registerProvider(options: undefined): Disposable {
|
||||||
|
const client = this._client;
|
||||||
|
|
||||||
|
const getExecutionPlan = (planFile: azdata.executionPlan.ExecutionPlanGraphInfo): Thenable<azdata.executionPlan.GetExecutionPlanResult> => {
|
||||||
|
const params: contracts.GetExecutionPlanParams = { graphInfo: planFile };
|
||||||
|
return client.sendRequest(contracts.GetExecutionPlanRequest.type, params).then(
|
||||||
|
r => r,
|
||||||
|
e => {
|
||||||
|
client.logFailedRequest(contracts.GetExecutionPlanRequest.type, e);
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return azdata.dataprotocol.registerExecutionPlanProvider({
|
||||||
|
providerId: client.providerId,
|
||||||
|
getExecutionPlan
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import * as path from 'path';
|
|||||||
import { getCommonLaunchArgsAndCleanupOldLogFiles, getOrDownloadServer } from './utils';
|
import { getCommonLaunchArgsAndCleanupOldLogFiles, getOrDownloadServer } from './utils';
|
||||||
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||||
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
||||||
import { TelemetryFeature, AgentServicesFeature, SerializationFeature, AccountFeature, SqlAssessmentServicesFeature, ProfilerFeature, TableDesignerFeature } from './features';
|
import { TelemetryFeature, AgentServicesFeature, SerializationFeature, AccountFeature, SqlAssessmentServicesFeature, ProfilerFeature, TableDesignerFeature, ExecutionPlanServiceFeature } from './features';
|
||||||
import { CredentialStore } from './credentialstore/credentialstore';
|
import { CredentialStore } from './credentialstore/credentialstore';
|
||||||
import { AzureResourceProvider } from './resourceProvider/resourceProvider';
|
import { AzureResourceProvider } from './resourceProvider/resourceProvider';
|
||||||
import { SchemaCompareService } from './schemaCompare/schemaCompareService';
|
import { SchemaCompareService } from './schemaCompare/schemaCompareService';
|
||||||
@@ -165,7 +165,8 @@ function getClientOptions(context: AppContext): ClientOptions {
|
|||||||
ProfilerFeature,
|
ProfilerFeature,
|
||||||
SqlMigrationService.asFeature(context),
|
SqlMigrationService.asFeature(context),
|
||||||
SqlCredentialService.asFeature(context),
|
SqlCredentialService.asFeature(context),
|
||||||
TableDesignerFeature
|
TableDesignerFeature,
|
||||||
|
ExecutionPlanServiceFeature
|
||||||
],
|
],
|
||||||
outputChannel: new CustomOutputChannel()
|
outputChannel: new CustomOutputChannel()
|
||||||
};
|
};
|
||||||
|
|||||||
26
src/sql/azdata.proposed.d.ts
vendored
26
src/sql/azdata.proposed.d.ts
vendored
@@ -601,7 +601,7 @@ declare module 'azdata' {
|
|||||||
/**
|
/**
|
||||||
* Contains execution plans returned by the database in ResultSets.
|
* Contains execution plans returned by the database in ResultSets.
|
||||||
*/
|
*/
|
||||||
executionPlans: ExecutionPlanGraph[];
|
executionPlans: executionPlan.ExecutionPlanGraph[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectMetadata {
|
export interface ObjectMetadata {
|
||||||
@@ -633,11 +633,13 @@ declare module 'azdata' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum DataProviderType {
|
export enum DataProviderType {
|
||||||
TableDesignerProvider = 'TableDesignerProvider'
|
TableDesignerProvider = 'TableDesignerProvider',
|
||||||
|
ExecutionPlanProvider = 'ExecutionPlanProvider'
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace dataprotocol {
|
export namespace dataprotocol {
|
||||||
export function registerTableDesignerProvider(provider: designers.TableDesignerProvider): vscode.Disposable;
|
export function registerTableDesignerProvider(provider: designers.TableDesignerProvider): vscode.Disposable;
|
||||||
|
export function registerExecutionPlanProvider(provider: executionPlan.ExecutionPlanProvider): vscode.Disposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace designers {
|
export namespace designers {
|
||||||
@@ -1111,6 +1113,7 @@ declare module 'azdata' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace executionPlan {
|
||||||
export interface ExecutionPlanGraph {
|
export interface ExecutionPlanGraph {
|
||||||
/**
|
/**
|
||||||
* Root of the execution plan tree
|
* Root of the execution plan tree
|
||||||
@@ -1123,7 +1126,7 @@ declare module 'azdata' {
|
|||||||
/**
|
/**
|
||||||
* String representation of graph
|
* String representation of graph
|
||||||
*/
|
*/
|
||||||
graphFile: ExecutionPlanGraphFile;
|
graphFile: ExecutionPlanGraphInfo;
|
||||||
/**
|
/**
|
||||||
* Query recommendations for optimizing performance
|
* Query recommendations for optimizing performance
|
||||||
*/
|
*/
|
||||||
@@ -1234,7 +1237,7 @@ declare module 'azdata' {
|
|||||||
queryWithDescription: string;
|
queryWithDescription: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExecutionPlanGraphFile {
|
export interface ExecutionPlanGraphInfo {
|
||||||
/**
|
/**
|
||||||
* File contents
|
* File contents
|
||||||
*/
|
*/
|
||||||
@@ -1245,6 +1248,21 @@ declare module 'azdata' {
|
|||||||
graphFileType: string;
|
graphFileType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetExecutionPlanResult extends ResultStatus {
|
||||||
|
graphs: ExecutionPlanGraph[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExecutionPlanProvider extends DataProvider {
|
||||||
|
// execution plan service methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the execution plan graph from the provider for a given plan file
|
||||||
|
* @param planFile file that contains the execution plan
|
||||||
|
*/
|
||||||
|
getExecutionPlan(planFile: ExecutionPlanGraphInfo): Thenable<GetExecutionPlanResult>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display text with an icon representing the severity
|
* Component to display text with an icon representing the severity
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export interface ConnectionProviderProperties {
|
|||||||
azureResource?: string;
|
azureResource?: string;
|
||||||
connectionOptions: azdata.ConnectionOption[];
|
connectionOptions: azdata.ConnectionOption[];
|
||||||
isQueryProvider?: boolean;
|
isQueryProvider?: boolean;
|
||||||
|
supportedExecutionPlanFileExtensions?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderFeatures {
|
export interface ProviderFeatures {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { IDataGridProviderService } from 'sql/workbench/services/dataGridProvide
|
|||||||
import { IAdsTelemetryService, ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry';
|
import { IAdsTelemetryService, ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry';
|
||||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||||
import { ITableDesignerService } from 'sql/workbench/services/tableDesigner/common/interface';
|
import { ITableDesignerService } from 'sql/workbench/services/tableDesigner/common/interface';
|
||||||
|
import { IExecutionPlanService } from 'sql/workbench/services/executionPlan/common/interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main thread class for handling data protocol management registration.
|
* Main thread class for handling data protocol management registration.
|
||||||
@@ -61,7 +62,8 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData
|
|||||||
@IAssessmentService private _assessmentService: IAssessmentService,
|
@IAssessmentService private _assessmentService: IAssessmentService,
|
||||||
@IDataGridProviderService private _dataGridProviderService: IDataGridProviderService,
|
@IDataGridProviderService private _dataGridProviderService: IDataGridProviderService,
|
||||||
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
|
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
|
||||||
@ITableDesignerService private _tableDesignerService: ITableDesignerService
|
@ITableDesignerService private _tableDesignerService: ITableDesignerService,
|
||||||
|
@IExecutionPlanService private _executionPlanService: IExecutionPlanService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
if (extHostContext) {
|
if (extHostContext) {
|
||||||
@@ -550,6 +552,12 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $registerExecutionPlanProvider(providerId: string, handle: number): void {
|
||||||
|
this._executionPlanService.registerProvider(providerId, <azdata.executionPlan.ExecutionPlanProvider>{
|
||||||
|
getExecutionPlan: (planFile: azdata.executionPlan.ExecutionPlanGraphInfo) => this._proxy.$getExecutionPlan(handle, planFile)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Connection Management handlers
|
// Connection Management handlers
|
||||||
public $onConnectionComplete(handle: number, connectionInfoSummary: azdata.ConnectionInfoSummary): void {
|
public $onConnectionComplete(handle: number, connectionInfoSummary: azdata.ConnectionInfoSummary): void {
|
||||||
this._connectionManagementService.onConnectionComplete(handle, connectionInfoSummary);
|
this._connectionManagementService.onConnectionComplete(handle, connectionInfoSummary);
|
||||||
|
|||||||
@@ -201,6 +201,12 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
|||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$registerExecutionPlanProvider(provider: azdata.executionPlan.ExecutionPlanProvider): vscode.Disposable {
|
||||||
|
let rt = this.registerProvider(provider, DataProviderType.ExecutionPlanProvider);
|
||||||
|
this._proxy.$registerExecutionPlanProvider(provider.providerId, provider.handle);
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
|
||||||
// Capabilities Discovery handlers
|
// Capabilities Discovery handlers
|
||||||
override $getServerCapabilities(handle: number, client: azdata.DataProtocolClientCapabilities): Thenable<azdata.DataProtocolServerCapabilities> {
|
override $getServerCapabilities(handle: number, client: azdata.DataProtocolClientCapabilities): Thenable<azdata.DataProtocolServerCapabilities> {
|
||||||
return this._resolveProvider<azdata.CapabilitiesProvider>(handle).getServerCapabilities(client);
|
return this._resolveProvider<azdata.CapabilitiesProvider>(handle).getServerCapabilities(client);
|
||||||
@@ -921,4 +927,10 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
|||||||
this._proxy.$openTableDesigner(providerId, tableInfo, telemetryInfo);
|
this._proxy.$openTableDesigner(providerId, tableInfo, telemetryInfo);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execution Plan
|
||||||
|
|
||||||
|
public override $getExecutionPlan(handle: number, planFile: azdata.executionPlan.ExecutionPlanGraphInfo): Thenable<azdata.executionPlan.GetExecutionPlanResult> {
|
||||||
|
return this._resolveProvider<azdata.executionPlan.ExecutionPlanProvider>(handle).getExecutionPlan(planFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -385,6 +385,10 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
|
|||||||
return extHostDataProvider.$registerTableDesignerProvider(provider);
|
return extHostDataProvider.$registerTableDesignerProvider(provider);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let registerExecutionPlanProvider = (provider: azdata.executionPlan.ExecutionPlanProvider): vscode.Disposable => {
|
||||||
|
return extHostDataProvider.$registerExecutionPlanProvider(provider);
|
||||||
|
};
|
||||||
|
|
||||||
// namespace: dataprotocol
|
// namespace: dataprotocol
|
||||||
const dataprotocol: typeof azdata.dataprotocol = {
|
const dataprotocol: typeof azdata.dataprotocol = {
|
||||||
registerBackupProvider,
|
registerBackupProvider,
|
||||||
@@ -406,6 +410,7 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
|
|||||||
registerSqlAssessmentServicesProvider,
|
registerSqlAssessmentServicesProvider,
|
||||||
registerDataGridProvider,
|
registerDataGridProvider,
|
||||||
registerTableDesignerProvider,
|
registerTableDesignerProvider,
|
||||||
|
registerExecutionPlanProvider: registerExecutionPlanProvider,
|
||||||
onDidChangeLanguageFlavor(listener: (e: azdata.DidChangeLanguageFlavorParams) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) {
|
onDidChangeLanguageFlavor(listener: (e: azdata.DidChangeLanguageFlavorParams) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) {
|
||||||
return extHostDataProvider.onDidChangeLanguageFlavor(listener, thisArgs, disposables);
|
return extHostDataProvider.onDidChangeLanguageFlavor(listener, thisArgs, disposables);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -567,6 +567,11 @@ export abstract class ExtHostDataProtocolShape {
|
|||||||
* Open a new instance of table designer.
|
* Open a new instance of table designer.
|
||||||
*/
|
*/
|
||||||
$openTableDesigner(providerId: string, tableInfo: azdata.designers.TableInfo, telemetryInfo?: ITelemetryEventProperties): void { throw ni(); }
|
$openTableDesigner(providerId: string, tableInfo: azdata.designers.TableInfo, telemetryInfo?: ITelemetryEventProperties): void { throw ni(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the generic execution plan graph for a plan file.
|
||||||
|
*/
|
||||||
|
$getExecutionPlan(handle: number, planFile: azdata.executionPlan.ExecutionPlanGraphInfo): Thenable<azdata.executionPlan.GetExecutionPlanResult> { throw ni(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -637,6 +642,7 @@ export interface MainThreadDataProtocolShape extends IDisposable {
|
|||||||
$registerSqlAssessmentServicesProvider(providerId: string, handle: number): Promise<any>;
|
$registerSqlAssessmentServicesProvider(providerId: string, handle: number): Promise<any>;
|
||||||
$registerDataGridProvider(providerId: string, title: string, handle: number): void;
|
$registerDataGridProvider(providerId: string, title: string, handle: number): void;
|
||||||
$registerTableDesignerProvider(providerId: string, handle: number): Promise<any>;
|
$registerTableDesignerProvider(providerId: string, handle: number): Promise<any>;
|
||||||
|
$registerExecutionPlanProvider(providerId: string, handle: number): void;
|
||||||
$unregisterProvider(handle: number): Promise<any>;
|
$unregisterProvider(handle: number): Promise<any>;
|
||||||
$onConnectionComplete(handle: number, connectionInfoSummary: azdata.ConnectionInfoSummary): void;
|
$onConnectionComplete(handle: number, connectionInfoSummary: azdata.ConnectionInfoSummary): void;
|
||||||
$onIntelliSenseCacheComplete(handle: number, connectionUri: string): void;
|
$onIntelliSenseCacheComplete(handle: number, connectionUri: string): void;
|
||||||
@@ -661,7 +667,6 @@ export interface MainThreadDataProtocolShape extends IDisposable {
|
|||||||
$onProfilerSessionCreated(handle: number, response: azdata.ProfilerSessionCreatedParams): void;
|
$onProfilerSessionCreated(handle: number, response: azdata.ProfilerSessionCreatedParams): void;
|
||||||
$onJobDataUpdated(handle: Number): void;
|
$onJobDataUpdated(handle: Number): void;
|
||||||
$openTableDesigner(providerId: string, tableInfo: azdata.designers.TableInfo, telemetryInfo?: ITelemetryEventProperties): void;
|
$openTableDesigner(providerId: string, tableInfo: azdata.designers.TableInfo, telemetryInfo?: ITelemetryEventProperties): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback when a session has completed initialization
|
* Callback when a session has completed initialization
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -382,7 +382,8 @@ export enum DataProviderType {
|
|||||||
IconProvider = 'IconProvider',
|
IconProvider = 'IconProvider',
|
||||||
SqlAssessmentServicesProvider = 'SqlAssessmentServicesProvider',
|
SqlAssessmentServicesProvider = 'SqlAssessmentServicesProvider',
|
||||||
DataGridProvider = 'DataGridProvider',
|
DataGridProvider = 'DataGridProvider',
|
||||||
TableDesignerProvider = 'TableDesignerProvider'
|
TableDesignerProvider = 'TableDesignerProvider',
|
||||||
|
ExecutionPlanProvider = 'ExecutionPlanProvider'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DeclarativeDataType {
|
export enum DeclarativeDataType {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import type * as azdata from 'azdata';
|
import type * as azdata from 'azdata';
|
||||||
|
|
||||||
export class ExecutionPlanState {
|
export class ExecutionPlanState {
|
||||||
graphs: azdata.ExecutionPlanGraph[] = [];
|
graphs: azdata.executionPlan.ExecutionPlanGraph[] = [];
|
||||||
clearExecutionPlanState() {
|
clearExecutionPlanState() {
|
||||||
this.graphs = [];
|
this.graphs = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,20 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
|||||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||||
|
import { IExecutionPlanService } from 'sql/workbench/services/executionPlan/common/interfaces';
|
||||||
|
import { LoadingSpinner } from 'sql/base/browser/ui/loadingSpinner/loadingSpinner';
|
||||||
|
import { InfoBox } from 'sql/base/browser/ui/infoBox/infoBox';
|
||||||
|
|
||||||
let azdataGraph = azdataGraphModule();
|
let azdataGraph = azdataGraphModule();
|
||||||
|
|
||||||
export interface InternalExecutionPlanNode extends azdata.ExecutionPlanNode {
|
export interface InternalExecutionPlanNode extends azdata.executionPlan.ExecutionPlanNode {
|
||||||
/**
|
/**
|
||||||
* Unique internal id given to graph node by ADS.
|
* Unique internal id given to graph node by ADS.
|
||||||
*/
|
*/
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InternalExecutionPlanEdge extends azdata.ExecutionPlanEdge {
|
export interface InternalExecutionPlanEdge extends azdata.executionPlan.ExecutionPlanEdge {
|
||||||
/**
|
/**
|
||||||
* Unique internal id given to graph edge by ADS.
|
* Unique internal id given to graph edge by ADS.
|
||||||
*/
|
*/
|
||||||
@@ -78,17 +81,22 @@ export class ExecutionPlanTab implements IPanelTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ExecutionPlanView implements IPanelView {
|
export class ExecutionPlanView implements IPanelView {
|
||||||
|
private _loadingSpinner: LoadingSpinner;
|
||||||
|
private _loadingErrorInfoBox: InfoBox;
|
||||||
private _eps?: ExecutionPlan[] = [];
|
private _eps?: ExecutionPlan[] = [];
|
||||||
private _graphs?: azdata.ExecutionPlanGraph[] = [];
|
private _graphs?: azdata.executionPlan.ExecutionPlanGraph[] = [];
|
||||||
private _container = DOM.$('.eps-container');
|
private _container = DOM.$('.eps-container');
|
||||||
|
|
||||||
|
private _planCache: Map<string, azdata.executionPlan.ExecutionPlanGraph[]> = new Map();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IInstantiationService private instantiationService: IInstantiationService,
|
@IInstantiationService private instantiationService: IInstantiationService,
|
||||||
|
@IExecutionPlanService private executionPlanService: IExecutionPlanService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(container: HTMLElement): void {
|
public render(parent: HTMLElement): void {
|
||||||
container.appendChild(this._container);
|
parent.appendChild(this._container);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
@@ -106,7 +114,11 @@ export class ExecutionPlanView implements IPanelView {
|
|||||||
DOM.clearNode(this._container);
|
DOM.clearNode(this._container);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addGraphs(newGraphs: azdata.ExecutionPlanGraph[] | undefined) {
|
/**
|
||||||
|
* Adds executionPlanGraph to the graph controller.
|
||||||
|
* @param newGraphs ExecutionPlanGraphs to be added.
|
||||||
|
*/
|
||||||
|
public addGraphs(newGraphs: azdata.executionPlan.ExecutionPlanGraph[] | undefined) {
|
||||||
if (newGraphs) {
|
if (newGraphs) {
|
||||||
newGraphs.forEach(g => {
|
newGraphs.forEach(g => {
|
||||||
const ep = this.instantiationService.createInstance(ExecutionPlan, this._container, this._eps.length + 1);
|
const ep = this.instantiationService.createInstance(ExecutionPlan, this._container, this._eps.length + 1);
|
||||||
@@ -118,6 +130,45 @@ export class ExecutionPlanView implements IPanelView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the graph file by converting the file to generic executionPlan graphs.
|
||||||
|
* This feature requires the right providers to be registered that can handle
|
||||||
|
* the graphFileType in the graphFile
|
||||||
|
* Please note: this method clears the existing graph in the graph control
|
||||||
|
* @param graphFile graph file to be loaded.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async loadGraphFile(graphFile: azdata.executionPlan.ExecutionPlanGraphInfo) {
|
||||||
|
this.clear();
|
||||||
|
this._loadingSpinner = new LoadingSpinner(this._container, { showText: true, fullSize: true });
|
||||||
|
this._loadingSpinner.loadingMessage = localize('loadingExecutionPlanFile', "Generating execution plans");
|
||||||
|
try {
|
||||||
|
this._loadingSpinner.loading = true;
|
||||||
|
if (this._planCache.has(graphFile.graphFileContent)) {
|
||||||
|
this.addGraphs(this._planCache.get(graphFile.graphFileContent));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const graphs = (await this.executionPlanService.getExecutionPlan({
|
||||||
|
graphFileContent: graphFile.graphFileContent,
|
||||||
|
graphFileType: graphFile.graphFileType
|
||||||
|
})).graphs;
|
||||||
|
this.addGraphs(graphs);
|
||||||
|
this._planCache.set(graphFile.graphFileContent, graphs);
|
||||||
|
}
|
||||||
|
this._loadingSpinner.loadingCompletedMessage = localize('executionPlanFileLoadingComplete', "Execution plans are generated");
|
||||||
|
} catch (e) {
|
||||||
|
this._loadingErrorInfoBox = new InfoBox(this._container, {
|
||||||
|
text: e.toString(),
|
||||||
|
style: 'error',
|
||||||
|
isClickable: false
|
||||||
|
});
|
||||||
|
this._loadingErrorInfoBox.isClickable = false;
|
||||||
|
this._loadingSpinner.loadingCompletedMessage = localize('executionPlanFileLoadingFailed', "Failed to load execution plan");
|
||||||
|
} finally {
|
||||||
|
this._loadingSpinner.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private updateRelativeCosts() {
|
private updateRelativeCosts() {
|
||||||
const sum = this._graphs.reduce((prevCost: number, cg) => {
|
const sum = this._graphs.reduce((prevCost: number, cg) => {
|
||||||
return prevCost += cg.root.subTreeCost + cg.root.cost;
|
return prevCost += cg.root.subTreeCost + cg.root.cost;
|
||||||
@@ -132,7 +183,7 @@ export class ExecutionPlanView implements IPanelView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ExecutionPlan implements ISashLayoutProvider {
|
export class ExecutionPlan implements ISashLayoutProvider {
|
||||||
private _graphModel?: azdata.ExecutionPlanGraph;
|
private _graphModel?: azdata.executionPlan.ExecutionPlanGraph;
|
||||||
|
|
||||||
private _container: HTMLElement;
|
private _container: HTMLElement;
|
||||||
|
|
||||||
@@ -327,7 +378,7 @@ export class ExecutionPlan implements ISashLayoutProvider {
|
|||||||
return diagramEdge;
|
return diagramEdge;
|
||||||
}
|
}
|
||||||
|
|
||||||
private populateProperties(props: azdata.ExecutionPlanGraphElementProperty[]) {
|
private populateProperties(props: azdata.executionPlan.ExecutionPlanGraphElementProperty[]) {
|
||||||
return props.filter(e => isString(e.displayValue) && e.showInTooltip)
|
return props.filter(e => isString(e.displayValue) && e.showInTooltip)
|
||||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||||
.map(e => {
|
.map(e => {
|
||||||
@@ -347,7 +398,7 @@ export class ExecutionPlan implements ISashLayoutProvider {
|
|||||||
|
|
||||||
private createPlanDiagram(container: HTMLElement) {
|
private createPlanDiagram(container: HTMLElement) {
|
||||||
let diagramRoot: any = new Object();
|
let diagramRoot: any = new Object();
|
||||||
let graphRoot: azdata.ExecutionPlanNode = this._graphModel.root;
|
let graphRoot: azdata.executionPlan.ExecutionPlanNode = this._graphModel.root;
|
||||||
|
|
||||||
this.populate(graphRoot, diagramRoot);
|
this.populate(graphRoot, diagramRoot);
|
||||||
this.azdataGraphDiagram = new azdataGraph.azdataQueryPlan(container, diagramRoot, executionPlanNodeIconPaths);
|
this.azdataGraphDiagram = new azdataGraph.azdataQueryPlan(container, diagramRoot, executionPlanNodeIconPaths);
|
||||||
@@ -385,7 +436,7 @@ export class ExecutionPlan implements ISashLayoutProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public set graphModel(graph: azdata.ExecutionPlanGraph | undefined) {
|
public set graphModel(graph: azdata.executionPlan.ExecutionPlanGraph | undefined) {
|
||||||
this._graphModel = graph;
|
this._graphModel = graph;
|
||||||
if (this._graphModel) {
|
if (this._graphModel) {
|
||||||
this.planHeader.graphIndex = this._graphIndex;
|
this.planHeader.graphIndex = this._graphIndex;
|
||||||
@@ -420,7 +471,7 @@ export class ExecutionPlan implements ISashLayoutProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get graphModel(): azdata.ExecutionPlanGraph | undefined {
|
public get graphModel(): azdata.executionPlan.ExecutionPlanGraph | undefined {
|
||||||
return this._graphModel;
|
return this._graphModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
|
||||||
|
import { EditorExtensions } from 'vs/workbench/common/editor';
|
||||||
|
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||||
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
|
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||||
|
import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
|
||||||
|
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IExecutionPlanService } from 'sql/workbench/services/executionPlan/common/interfaces';
|
||||||
|
import { ExecutionPlanInput } from 'sql/workbench/contrib/executionPlan/common/executionPlanInput';
|
||||||
|
import { ExecutionPlanEditor } from 'sql/workbench/contrib/executionPlan/browser/executionPlanEditor';
|
||||||
|
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
|
|
||||||
|
// Execution Plan editor registration
|
||||||
|
|
||||||
|
const executionPlanEditorDescriptor = EditorPaneDescriptor.create(
|
||||||
|
ExecutionPlanEditor,
|
||||||
|
ExecutionPlanEditor.ID,
|
||||||
|
ExecutionPlanEditor.LABEL
|
||||||
|
);
|
||||||
|
|
||||||
|
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane)
|
||||||
|
.registerEditorPane(executionPlanEditorDescriptor, [new SyncDescriptor(ExecutionPlanInput)]);
|
||||||
|
|
||||||
|
export class ExecutionPlanEditorOverrideContribution extends Disposable implements IWorkbenchContribution {
|
||||||
|
constructor(
|
||||||
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
|
@IEditorResolverService private _editorResolverService: IEditorResolverService,
|
||||||
|
@IExecutionPlanService private _executionPlanService: IExecutionPlanService,
|
||||||
|
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.registerEditorOverride();
|
||||||
|
|
||||||
|
this._capabilitiesService.onCapabilitiesRegistered(e => {
|
||||||
|
const newFileFormats = this._executionPlanService.getSupportedExecutionPlanExtensionsForProvider(e.id);
|
||||||
|
if (newFileFormats?.length > 0) {
|
||||||
|
this._editorResolverService.updateUserAssociations(this.getGlobForFileExtensions(newFileFormats), ExecutionPlanEditor.ID); // Registering new file formats when new providers are registered.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerEditorOverride(): void {
|
||||||
|
const supportedFileFormats: string[] = [];
|
||||||
|
Object.keys(this._capabilitiesService.providers).forEach(e => {
|
||||||
|
if (this._capabilitiesService.providers[e]?.connection?.supportedExecutionPlanFileExtensions) {
|
||||||
|
supportedFileFormats.push(... this._capabilitiesService.providers[e].connection.supportedExecutionPlanFileExtensions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._editorResolverService.registerEditor(
|
||||||
|
this.getGlobForFileExtensions(supportedFileFormats),
|
||||||
|
{
|
||||||
|
id: ExecutionPlanEditor.ID,
|
||||||
|
label: ExecutionPlanEditor.LABEL,
|
||||||
|
priority: RegisteredEditorPriority.builtin
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
(editorInput, group) => {
|
||||||
|
const executionPlanInput = this._instantiationService.createInstance(ExecutionPlanInput, editorInput.resource);
|
||||||
|
return { editor: executionPlanInput, options: editorInput.options, group: group };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getGlobForFileExtensions(extensions: string[]): string {
|
||||||
|
return extensions?.length === 0 ? '' : `*.{${extensions.join()}}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||||
|
.registerWorkbenchContribution(ExecutionPlanEditorOverrideContribution, LifecyclePhase.Restored);
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||||
|
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { ExecutionPlanInput } from 'sql/workbench/contrib/executionPlan/common/executionPlanInput';
|
||||||
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
|
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||||
|
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||||
|
import { ExecutionPlanView } from 'sql/workbench/contrib/executionPlan/browser/executionPlan';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
|
||||||
|
export class ExecutionPlanEditor extends EditorPane {
|
||||||
|
|
||||||
|
public static ID: string = 'workbench.editor.executionplan';
|
||||||
|
public static LABEL: string = localize('executionPlanEditor', "Query Execution Plan Editor");
|
||||||
|
|
||||||
|
private view: ExecutionPlanView;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IInstantiationService instantiationService: IInstantiationService,
|
||||||
|
@ITelemetryService telemetryService: ITelemetryService,
|
||||||
|
@IThemeService themeService: IThemeService,
|
||||||
|
@IStorageService storageService: IStorageService,
|
||||||
|
) {
|
||||||
|
super(ExecutionPlanEditor.ID, telemetryService, themeService, storageService);
|
||||||
|
this.view = this._register(instantiationService.createInstance(ExecutionPlanView));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to create the editor in the parent element.
|
||||||
|
*/
|
||||||
|
public createEditor(parent: HTMLElement): void {
|
||||||
|
//Enable scrollbars when drawing area is larger than viewport
|
||||||
|
parent.style.overflow = 'auto';
|
||||||
|
this.view.render(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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: DOM.Dimension): void {
|
||||||
|
this.view.layout(dimension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async setInput(input: ExecutionPlanInput, options: IEditorOptions, context: IEditorOpenContext): Promise<void> {
|
||||||
|
if (this.input instanceof ExecutionPlanInput && this.input.matches(input)) {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
await input.resolve();
|
||||||
|
await super.setInput(input, options, context, CancellationToken.None);
|
||||||
|
this.view.loadGraphFile({
|
||||||
|
graphFileContent: input.content,
|
||||||
|
graphFileType: input.getFileExtension().replace('.', '')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,7 +126,7 @@ export class ExecutionPlanPropertiesView {
|
|||||||
attachTableStyler(this._table, this._themeService);
|
attachTableStyler(this._table, this._themeService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public set graphElement(element: azdata.ExecutionPlanNode | azdata.ExecutionPlanEdge) {
|
public set graphElement(element: azdata.executionPlan.ExecutionPlanNode | azdata.executionPlan.ExecutionPlanEdge) {
|
||||||
this._model.graphElement = element;
|
this._model.graphElement = element;
|
||||||
this.sortPropertiesByImportance();
|
this.sortPropertiesByImportance();
|
||||||
this.renderView();
|
this.renderView();
|
||||||
@@ -186,7 +186,7 @@ export class ExecutionPlanPropertiesView {
|
|||||||
|
|
||||||
private renderView(): void {
|
private renderView(): void {
|
||||||
if (this._model.graphElement) {
|
if (this._model.graphElement) {
|
||||||
const nodeName = (<azdata.ExecutionPlanNode>this._model.graphElement).name;
|
const nodeName = (<azdata.executionPlan.ExecutionPlanNode>this._model.graphElement).name;
|
||||||
this._operationName.innerText = nodeName ? removeLineBreaks(nodeName) : localize('executionPlanPropertiesEdgeOperationName', "Edge"); //since edges do not have names like node, we set the operation name to 'Edge'
|
this._operationName.innerText = nodeName ? removeLineBreaks(nodeName) : localize('executionPlanPropertiesEdgeOperationName', "Edge"); //since edges do not have names like node, we set the operation name to 'Edge'
|
||||||
}
|
}
|
||||||
this._tableContainer.scrollTo(0, 0);
|
this._tableContainer.scrollTo(0, 0);
|
||||||
@@ -201,7 +201,7 @@ export class ExecutionPlanPropertiesView {
|
|||||||
this._table.resizeCanvas();
|
this._table.resizeCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertPropertiesToTableRows(props: azdata.ExecutionPlanGraphElementProperty[], parentIndex: number, indent: number, rows: { [key: string]: string }[] = []): { [key: string]: string }[] {
|
private convertPropertiesToTableRows(props: azdata.executionPlan.ExecutionPlanGraphElementProperty[], parentIndex: number, indent: number, rows: { [key: string]: string }[] = []): { [key: string]: string }[] {
|
||||||
if (!props) {
|
if (!props) {
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ export class ExecutionPlanPropertiesView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GraphElementPropertyViewData {
|
export interface GraphElementPropertyViewData {
|
||||||
graphElement: azdata.ExecutionPlanNode | azdata.ExecutionPlanEdge;
|
graphElement: azdata.executionPlan.ExecutionPlanNode | azdata.executionPlan.ExecutionPlanEdge;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClosePropertyViewAction extends Action {
|
export class ClosePropertyViewAction extends Action {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export class PlanHeader {
|
|||||||
private _query: string;
|
private _query: string;
|
||||||
private _queryContainer: HTMLElement; // container that holds query text
|
private _queryContainer: HTMLElement; // container that holds query text
|
||||||
|
|
||||||
private _recommendations: azdata.ExecutionPlanRecommendations[];
|
private _recommendations: azdata.executionPlan.ExecutionPlanRecommendations[];
|
||||||
private _recommendationsContainer: HTMLElement; // container that holds graph recommendations
|
private _recommendationsContainer: HTMLElement; // container that holds graph recommendations
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
@@ -61,7 +61,7 @@ export class PlanHeader {
|
|||||||
this.renderQueryText();
|
this.renderQueryText();
|
||||||
}
|
}
|
||||||
|
|
||||||
public set recommendations(recommendations: azdata.ExecutionPlanRecommendations[]) {
|
public set recommendations(recommendations: azdata.executionPlan.ExecutionPlanRecommendations[]) {
|
||||||
recommendations.forEach(r => {
|
recommendations.forEach(r => {
|
||||||
r.displayString = removeLineBreaks(r.displayString);
|
r.displayString = removeLineBreaks(r.displayString);
|
||||||
});
|
});
|
||||||
@@ -113,5 +113,5 @@ export interface PlanHeaderData {
|
|||||||
planIndex?: number;
|
planIndex?: number;
|
||||||
relativeCost?: number;
|
relativeCost?: number;
|
||||||
query?: string;
|
query?: string;
|
||||||
recommendations?: azdata.ExecutionPlanRecommendations[];
|
recommendations?: azdata.executionPlan.ExecutionPlanRecommendations[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as path from 'vs/base/common/path';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||||
|
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
|
||||||
|
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||||
|
|
||||||
|
export class ExecutionPlanInput extends EditorInput {
|
||||||
|
|
||||||
|
public static ID: string = 'workbench.editorinputs.executionplan';
|
||||||
|
public static SCHEMA: string = 'executionplan';
|
||||||
|
|
||||||
|
private _content?: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _uri: URI,
|
||||||
|
@ITextFileService private readonly _fileService: ITextFileService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
override get typeId(): string {
|
||||||
|
return ExecutionPlanInput.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override getName(): string {
|
||||||
|
return path.basename(this._uri.fsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get content(): string | undefined {
|
||||||
|
return this._content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUri(): string {
|
||||||
|
return this._uri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFileExtension(): string {
|
||||||
|
return path.extname(this._uri.fsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public supportsSplitEditor(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async resolve(refresh?: boolean): Promise<EditorModel | undefined> {
|
||||||
|
if (!this._content) {
|
||||||
|
this._content = (await this._fileService.read(this._uri, { acceptTextOnly: true })).value;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get resource(): URI | undefined {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ export class QueryPlanEditorOverrideContribution extends Disposable implements I
|
|||||||
|
|
||||||
private registerEditorOverride(): void {
|
private registerEditorOverride(): void {
|
||||||
this._editorResolverService.registerEditor(
|
this._editorResolverService.registerEditor(
|
||||||
'*.sqlplan',
|
'', //Removing sqlplan glob pattern. TODO: to be removed entirely from ADS.
|
||||||
{
|
{
|
||||||
id: QueryPlanEditor.ID,
|
id: QueryPlanEditor.ID,
|
||||||
label: QueryPlanEditor.LABEL,
|
label: QueryPlanEditor.LABEL,
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import type * as azdata from 'azdata';
|
||||||
|
import { IExecutionPlanService } from 'sql/workbench/services/executionPlan/common/interfaces';
|
||||||
|
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
|
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||||
|
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
|
|
||||||
|
interface ExecutionPlanProviderRegisteredEvent {
|
||||||
|
id: string,
|
||||||
|
provider: azdata.executionPlan.ExecutionPlanProvider
|
||||||
|
}
|
||||||
|
export class ExecutionPlanService implements IExecutionPlanService {
|
||||||
|
private _providers: { [handle: string]: azdata.executionPlan.ExecutionPlanProvider; } = Object.create(null);
|
||||||
|
private _onProviderRegister: Emitter<ExecutionPlanProviderRegisteredEvent> = new Emitter<ExecutionPlanProviderRegisteredEvent>();
|
||||||
|
private _providerRegisterEvent: Event<ExecutionPlanProviderRegisteredEvent>;
|
||||||
|
constructor(
|
||||||
|
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||||
|
@IQuickInputService private _quickInputService: IQuickInputService,
|
||||||
|
@IExtensionService private _extensionService: IExtensionService
|
||||||
|
) {
|
||||||
|
this._providerRegisterEvent = this._onProviderRegister.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the actions using the provider that supports the fileFormat provided.
|
||||||
|
* @param fileFormat fileformat of the underlying execution plan file. It is used to get the provider that support it.
|
||||||
|
* @param action executionPlanService action to be performed.
|
||||||
|
*/
|
||||||
|
private async _runAction<T>(fileFormat: string, action: (handler: azdata.executionPlan.ExecutionPlanProvider) => Thenable<T>): Promise<T> {
|
||||||
|
let providers = Object.keys(this._capabilitiesService.providers);
|
||||||
|
if (!providers) {
|
||||||
|
providers = await new Promise(resolve => {
|
||||||
|
this._capabilitiesService.onCapabilitiesRegistered(e => {
|
||||||
|
resolve(Object.keys(this._capabilitiesService.providers));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let epProviders: string[] = [];
|
||||||
|
for (let i = 0; i < providers.length; i++) {
|
||||||
|
const providerCapabilities = this._capabilitiesService.getCapabilities(providers[i]);
|
||||||
|
if (providerCapabilities.connection.supportedExecutionPlanFileExtensions?.includes(fileFormat)) {
|
||||||
|
epProviders.push(providers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedProvider: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This handles the case when multiple providers support the same execution plan extension.
|
||||||
|
* The code shows a quick pick and lets user select the provider they want to open the execution plan file with.
|
||||||
|
*/
|
||||||
|
if (epProviders.length > 1) {
|
||||||
|
const providerQuickPick = this._quickInputService.createQuickPick<IQuickPickItem>();
|
||||||
|
providerQuickPick.items = epProviders.map(p => {
|
||||||
|
return {
|
||||||
|
label: p,
|
||||||
|
ariaLabel: p
|
||||||
|
};
|
||||||
|
});
|
||||||
|
providerQuickPick.placeholder = localize('selectExecutionPlanProvider', "Select a provider to open execution plan");
|
||||||
|
|
||||||
|
selectedProvider = await new Promise((resolve) => {
|
||||||
|
providerQuickPick.onDidChangeSelection(e => {
|
||||||
|
providerQuickPick.hide();
|
||||||
|
resolve(e[0].label);
|
||||||
|
});
|
||||||
|
providerQuickPick.show();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
selectedProvider = epProviders[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!selectedProvider) {
|
||||||
|
return Promise.reject(new Error(localize('providerIdNotValidError', "Valid provider is required in order to interact with ExecutionPlanService")));
|
||||||
|
}
|
||||||
|
await this._extensionService.whenInstalledExtensionsRegistered();
|
||||||
|
let handler = this._providers[selectedProvider];
|
||||||
|
if (!handler) {
|
||||||
|
handler = await new Promise((resolve, reject) => {
|
||||||
|
this._providerRegisterEvent(e => {
|
||||||
|
if (e.id === selectedProvider) {
|
||||||
|
resolve(e.provider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
/**
|
||||||
|
* Handling a possible edge case where provider registered event
|
||||||
|
* might have been called before we await for it.
|
||||||
|
*/
|
||||||
|
resolve(this._providers[selectedProvider]);
|
||||||
|
}, 30000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (handler) {
|
||||||
|
return Promise.resolve(action(handler));
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Error(localize('noHandlerRegistered', "No valid execution plan handler is registered")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProvider(providerId: string, provider: azdata.executionPlan.ExecutionPlanProvider): void {
|
||||||
|
if (this._providers[providerId]) {
|
||||||
|
throw new Error(`A execution plan provider with id "${providerId}" is already registered`);
|
||||||
|
}
|
||||||
|
this._providers[providerId] = provider;
|
||||||
|
this._onProviderRegister.fire({
|
||||||
|
id: providerId,
|
||||||
|
provider: provider
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getExecutionPlan(planFile: azdata.executionPlan.ExecutionPlanGraphInfo): Promise<azdata.executionPlan.GetExecutionPlanResult> {
|
||||||
|
return this._runAction(planFile.graphFileType, (runner) => {
|
||||||
|
return runner.getExecutionPlan(planFile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSupportedExecutionPlanExtensionsForProvider(providerId: string): string[] | undefined {
|
||||||
|
return this._capabilitiesService.getCapabilities(providerId).connection.supportedExecutionPlanFileExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
_serviceBrand: undefined;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import type * as azdata from 'azdata';
|
||||||
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
|
||||||
|
|
||||||
|
export const SERVICE_ID = 'executionPlanService';
|
||||||
|
|
||||||
|
export const IExecutionPlanService = createDecorator<IExecutionPlanService>(SERVICE_ID);
|
||||||
|
|
||||||
|
export interface IExecutionPlanService {
|
||||||
|
_serviceBrand: undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an execution plan service provider.
|
||||||
|
*/
|
||||||
|
registerProvider(providerId: string, provider: azdata.executionPlan.ExecutionPlanProvider): void;
|
||||||
|
/**
|
||||||
|
* Gets an execution plan for the given planFile.
|
||||||
|
*/
|
||||||
|
getExecutionPlan(planFile: azdata.executionPlan.ExecutionPlanGraphInfo): Promise<azdata.executionPlan.GetExecutionPlanResult>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get execution plan file extensions supported by the provider.
|
||||||
|
*/
|
||||||
|
getSupportedExecutionPlanExtensionsForProvider(providerId: string): string[];
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
EditRevertCellResult,
|
EditRevertCellResult,
|
||||||
ExecutionPlanOptions,
|
ExecutionPlanOptions,
|
||||||
queryeditor,
|
queryeditor,
|
||||||
ExecutionPlanGraph
|
executionPlan
|
||||||
} from 'azdata';
|
} from 'azdata';
|
||||||
import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService';
|
import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService';
|
||||||
import { IRange } from 'vs/editor/common/core/range';
|
import { IRange } from 'vs/editor/common/core/range';
|
||||||
@@ -34,7 +34,7 @@ export interface IQueryPlanInfo {
|
|||||||
export interface IExecutionPlanInfo {
|
export interface IExecutionPlanInfo {
|
||||||
providerId: string;
|
providerId: string;
|
||||||
fileUri: string;
|
fileUri: string;
|
||||||
planGraphs: ExecutionPlanGraph[];
|
planGraphs: executionPlan.ExecutionPlanGraph[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQueryInfo {
|
export interface IQueryInfo {
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ export default class QueryRunner extends Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleExecutionPlanAvailable(executionPlans: azdata.ExecutionPlanGraph[] | undefined) {
|
public handleExecutionPlanAvailable(executionPlans: azdata.executionPlan.ExecutionPlanGraph[] | undefined) {
|
||||||
if (executionPlans) {
|
if (executionPlans) {
|
||||||
this._onExecutionPlanAvailable.fire({
|
this._onExecutionPlanAvailable.fire({
|
||||||
providerId: mssqlProviderName,
|
providerId: mssqlProviderName,
|
||||||
|
|||||||
@@ -213,6 +213,8 @@ import { DataGridProviderService } from 'sql/workbench/services/dataGridProvider
|
|||||||
import { IDataGridProviderService } from 'sql/workbench/services/dataGridProvider/common/dataGridProviderService';
|
import { IDataGridProviderService } from 'sql/workbench/services/dataGridProvider/common/dataGridProviderService';
|
||||||
import { ITableDesignerService } from 'sql/workbench/services/tableDesigner/common/interface';
|
import { ITableDesignerService } from 'sql/workbench/services/tableDesigner/common/interface';
|
||||||
import { TableDesignerService } from 'sql/workbench/services/tableDesigner/browser/tableDesignerService';
|
import { TableDesignerService } from 'sql/workbench/services/tableDesigner/browser/tableDesignerService';
|
||||||
|
import { IExecutionPlanService } from 'sql/workbench/services/executionPlan/common/interfaces';
|
||||||
|
import { ExecutionPlanService } from 'sql/workbench/services/executionPlan/common/executionPlanService';
|
||||||
|
|
||||||
registerSingleton(IDashboardService, DashboardService);
|
registerSingleton(IDashboardService, DashboardService);
|
||||||
registerSingleton(IDashboardViewService, DashboardViewService);
|
registerSingleton(IDashboardViewService, DashboardViewService);
|
||||||
@@ -253,7 +255,7 @@ registerSingleton(IOEShimService, OEShimService);
|
|||||||
registerSingleton(IAssessmentService, AssessmentService);
|
registerSingleton(IAssessmentService, AssessmentService);
|
||||||
registerSingleton(IDataGridProviderService, DataGridProviderService);
|
registerSingleton(IDataGridProviderService, DataGridProviderService);
|
||||||
registerSingleton(ITableDesignerService, TableDesignerService);
|
registerSingleton(ITableDesignerService, TableDesignerService);
|
||||||
|
registerSingleton(IExecutionPlanService, ExecutionPlanService);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -539,4 +541,7 @@ import 'sql/workbench/contrib/charts/browser/charts.contribution';
|
|||||||
// table designer
|
// table designer
|
||||||
import 'sql/workbench/contrib/tableDesigner/browser/tableDesigner.contribution';
|
import 'sql/workbench/contrib/tableDesigner/browser/tableDesigner.contribution';
|
||||||
|
|
||||||
|
// execution plan
|
||||||
|
import 'sql/workbench/contrib/executionPlan/browser/executionPlanContribution';
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
Reference in New Issue
Block a user