Add query execution plan extensibility APIs (#4072)

* WIP 1

* WIP 2

* Fix typos

* Iterate on API a bit

* Query Tab WIP

* More dynamic query tab impl

* Fix merge breaks

* Update interfaces

* Update to single event handler for query events

* Remove query plan extension

* Add generated JS file
This commit is contained in:
Karl Burtram
2019-03-28 10:59:02 -07:00
committed by GitHub
parent ee413f3b24
commit cc2951265e
18 changed files with 688 additions and 12 deletions

View File

@@ -3576,6 +3576,31 @@ declare module 'azdata' {
* Namespace for interacting with query editor
*/
export namespace queryeditor {
export type QueryEvent =
| 'queryStart'
| 'queryStop'
| 'executionPlan';
export interface QueryEventListener {
onQueryEvent(type: QueryEvent, document: queryeditor.QueryDocument, args: any);
}
// new extensibility interfaces
export interface QueryDocument {
providerId: string;
uri: string;
// get the document's execution options
getOptions(): Map<string, string>;
// set the document's execution options
setOptions(options: Map<string, string>): void;
// tab content is build using the modelview UI builder APIs
// probably should rename DialogTab class since it is useful outside dialogs
createQueryTab(tab: window.DialogTab): void;
}
/**
* Make connection for the query editor
@@ -3588,7 +3613,14 @@ declare module 'azdata' {
* Run query if it is a query editor and it is already opened.
* @param {string} fileUri file URI for the query editor
*/
export function runQuery(fileUri: string): void;
export function runQuery(fileUri: string, options?: Map<string, string>): void;
/**
* Register a query event listener
*/
export function registerQueryEventListener(listener: queryeditor.QueryEventListener): void;
export function getQueryDocument(fileUri: string): queryeditor.QueryDocument
}
/**

View File

@@ -968,4 +968,8 @@ export class QueryEditor extends BaseEditor {
public get changeConnectionAction(): ConnectDatabaseAction {
return this._changeConnectionAction;
}
public registerQueryModelViewTab(title: string, componentId: string): void {
this._resultsEditor.registerQueryModelViewTab(title, componentId);
}
}

View File

@@ -165,4 +165,8 @@ export class QueryResultsEditor extends BaseEditor {
public showQueryPlan(xml: string) {
this.resultsView.showPlan(xml);
}
public registerQueryModelViewTab(title: string, componentId: string): void {
this.resultsView.registerQueryModelViewTab(title, componentId);
}
}

View File

@@ -13,6 +13,7 @@ import { GridPanel } from './gridPanel';
import { ChartTab } from './charting/chartTab';
import { QueryPlanTab } from 'sql/parts/queryPlan/queryPlan';
import { TopOperationsTab } from 'sql/parts/queryPlan/topOperations';
import { QueryModelViewTab } from 'sql/parts/query/modelViewTab/queryModelViewTab';
import * as nls from 'vs/nls';
import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet';
@@ -175,12 +176,13 @@ export class QueryResultsView extends Disposable {
private chartTab: ChartTab;
private qpTab: QueryPlanTab;
private topOperationsTab: TopOperationsTab;
private dynamicModelViewTabs: QueryModelViewTab[] = [];
private runnerDisposables: IDisposable[];
constructor(
container: HTMLElement,
@IInstantiationService instantiationService: IInstantiationService,
@IInstantiationService private instantiationService: IInstantiationService,
@IQueryModelService private queryModelService: IQueryModelService
) {
super();
@@ -207,6 +209,7 @@ export class QueryResultsView extends Disposable {
this.runnerDisposables.push(runner.onQueryStart(e => {
this.hideChart();
this.hidePlan();
this.hideDynamicViewModelTabs();
this.input.state.visibleTabs = new Set();
this.input.state.activeTab = this.resultsTab.identifier;
}));
@@ -225,6 +228,23 @@ export class QueryResultsView extends Disposable {
this._panelView.pushTab(this.topOperationsTab);
}
}
// restore query model view tabs
this.input.state.visibleTabs.forEach(tabId => {
if (tabId.startsWith('querymodelview;')) {
// tab id format is 'tab type;title;model view id'
let parts = tabId.split(';');
if (parts.length === 3) {
let tab = this._register(new QueryModelViewTab(parts[1], this.instantiationService));
tab.view._componentId = parts[2];
this.dynamicModelViewTabs.push(tab);
if (!this._panelView.contains(tab)) {
this._panelView.pushTab(tab);
}
}
}
});
this.runnerDisposables.push(runner.onQueryEnd(() => {
if (runner.isQueryPlan) {
runner.planXml.then(e => {
@@ -311,10 +331,35 @@ export class QueryResultsView extends Disposable {
if (this._panelView.contains(this.qpTab)) {
this._panelView.removeTab(this.qpTab.identifier);
}
if (this._panelView.contains(this.topOperationsTab)) {
this._panelView.removeTab(this.topOperationsTab.identifier);
}
}
public hideDynamicViewModelTabs() {
this.dynamicModelViewTabs.forEach(tab => {
if (this._panelView.contains(tab)) {
this._panelView.removeTab(tab.identifier);
}
});
this.dynamicModelViewTabs = [];
}
public dispose() {
dispose(this.runnerDisposables);
super.dispose();
}
public registerQueryModelViewTab(title: string, componentId: string): void {
let tab = this._register(new QueryModelViewTab(title, this.instantiationService));
tab.view._componentId = componentId;
this.dynamicModelViewTabs.push(tab);
this.input.state.visibleTabs.add('querymodelview;' + title + ';' + componentId);
if (!this._panelView.contains(tab)) {
this._panelView.pushTab(tab);
}
}
}

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.dialogModal-body {
display: flex;
flex-direction: row;
height: 100%;
width: 100%;
min-width: 500px;
min-height: 600px;
}
.modal.wide .dialogModal-body {
min-width: 800px;
}
.dialog-message-and-page-container {
display: flex;
flex-direction: column;
flex: 1 1;
overflow: hidden;
}
.dialogModal-page-container {
flex: 1 1;
overflow: hidden;
}
.dialogModal-pane {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
}
.dialogModal-hidden {
display: none;
}
.footer-button.dialogModal-hidden {
margin: 0;
}
.footer-button .validating {
background-size: 15px;
background-repeat: no-repeat;
background-position: center;
}
.vs .footer-button .validating {
background-image: url("loading.svg");
}
.vs-dark .footer-button .validating,
.hc-black .footer-button .validating {
background-image: url("loading_inverse.svg");
}
.dialogModal-wizardHeader {
padding: 10px 30px;
}
.dialogModal-wizardHeader h1 {
margin-top: 10px;
margin-bottom: 3px;
font-size: 1.5em;
font-weight: lighter;
}
.dialogContainer {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}

View File

@@ -0,0 +1,31 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g>
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,31 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g style="fill:white;">
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.wizardNavigation-container {
display: flex;
flex-direction: column;
width: 80px;
height: 100%;
}
.hc-black .wizardNavigation-container {
border-right-color: #2b56f2;
border-right-style: solid;
border-right-width: 1px;
background-color: unset;
}
.wizardNavigation-pageNumber {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-height: 100px;
}
.wizardNavigation-pageNumber a {
text-decoration: none;
}
.wizardNavigation-dot {
height: 30px;
width: 30px;
background-color: rgb(200, 200, 200);
color: white;
border-radius: 50%;
border-style: none;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
}
.hc-black .wizardNavigation-dot {
flex-grow: 1;
background-color: unset;
border-style: solid;
border-width: 1px;
border-color: white;
}
.wizardNavigation-connector {
width: 3px;
display: inline-block;
flex-grow: 1;
background-color: rgb(200, 200, 200);
}
.hc-black .wizardNavigation-connector {
display: none;
}
.wizardNavigation-connector.active,
.wizardNavigation-dot.active {
background-color: rgb(9, 109, 201);
}
.hc-black .wizardNavigation-dot.active {
border-color: #2b56f2;
background-color: unset;
border-style: solid;
}
.hc-black .wizardNavigation-dot.active:hover,
.hc-black .wizardNavigation-dot.active.currentPage:hover {
border-color: #F38518;
border-style: dashed;
}
.wizardNavigation-dot.active.currentPage {
border-style: double;
}
.hc-black .wizardNavigation-dot.active.currentPage {
border-style: solid;
border-color: #F38518;
}
.wizardNavigation-connector.invisible {
visibility: hidden;
}

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* 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 'vs/css!./media/dialogModal';
import { forwardRef, NgModule, ComponentFactoryResolver, Inject, ApplicationRef } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule, APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { WizardNavigation } from 'sql/platform/dialog/wizardNavigation.component';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component';
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { QueryModelViewTabContainer } from 'sql/parts/query/modelViewTab/queryModelViewTabContainer.component';
export const QueryModelViewTabModule = (params, selector: string, instantiationService: IInstantiationService): any => {
/* Model-backed components */
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
@NgModule({
declarations: [
Checkbox,
SelectBox,
EditableDropDown,
InputBox,
QueryModelViewTabContainer,
WizardNavigation,
ModelViewContent,
ModelComponentWrapper,
ComponentHostDirective,
...extensionComponents
],
entryComponents: [QueryModelViewTabContainer, WizardNavigation, ...extensionComponents],
imports: [
FormsModule,
CommonModule,
BrowserModule
],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
CommonServiceInterface,
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
let componentClass = QueryModelViewTabContainer;
const factoryWrapper: any = this._resolver.resolveComponentFactory<QueryModelViewTabContainer>(componentClass);
factoryWrapper.factory.selector = this.selector;
appRef.bootstrap(factoryWrapper);
}
}
return ModuleClass;
};

View 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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Dimension } from 'vs/base/browser/dom';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
import { QueryModelViewTabModule } from 'sql/parts/query/modelViewTab/queryModelViewTab.module';
export class QueryModelViewTab implements IPanelTab {
public identifier = 'QueryModelViewTab_';
public readonly view: QueryModelViewTabView;
constructor(public title: string, @IInstantiationService instantiationService: IInstantiationService) {
this.identifier += title;
this.view = instantiationService.createInstance(QueryModelViewTabView);
}
public dispose() {
dispose(this.view);
}
public clear() {
this.view.clear();
}
}
export class QueryModelViewTabView implements IPanelView {
public _componentId: string;
private _isInitialized: boolean = false;
private _selector: string;
constructor(
@IInstantiationService private _instantiationService: IInstantiationService) {
}
public render(container: HTMLElement): void {
this.bootstrapAngular(container);
}
dispose() {
}
public clear() {
}
public layout(dimension: Dimension): void {
}
/**
* Load the angular components and record for this input that we have done so
*/
private bootstrapAngular(container: HTMLElement): string {
let uniqueSelector = bootstrapAngular(this._instantiationService,
QueryModelViewTabModule,
container,
'querytab-modelview-container',
{ modelViewId: this._componentId });
return uniqueSelector;
}
}

View File

@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* 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 'vs/css!./media/dialogModal';
import { Component, ViewChild, Inject, forwardRef, ElementRef, AfterViewInit } from '@angular/core';
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { DialogPane } from 'sql/platform/dialog/dialogPane';
import { ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import { Event, Emitter } from 'vs/base/common/event';
export interface LayoutRequestParams {
modelViewId?: string;
alwaysRefresh?: boolean;
}
export interface DialogComponentParams extends IBootstrapParams {
modelViewId: string;
validityChangedCallback: (valid: boolean) => void;
onLayoutRequested: Event<LayoutRequestParams>;
dialogPane: DialogPane;
}
@Component({
selector: 'querytab-modelview-container',
providers: [],
template: `
<modelview-content [modelViewId]="modelViewId">
</modelview-content>
`
})
export class QueryModelViewTabContainer implements AfterViewInit {
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
private _dialogPane: DialogPane;
public modelViewId: string;
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
constructor(
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(IBootstrapParams) private _params: DialogComponentParams) {
this.modelViewId = this._params.modelViewId;
}
ngAfterViewInit(): void {
this._modelViewContent.onEvent(event => {
if (event.isRootComponent && event.eventType === ComponentEventType.validityChanged) {
this._params.validityChangedCallback(event.args);
}
});
let element = <HTMLElement>this._el.nativeElement;
element.style.height = '100%';
element.style.width = '100%';
}
public layout(): void {
this._modelViewContent.layout();
}
}

View File

@@ -16,7 +16,8 @@ import {
EditSubsetResult,
EditCreateRowResult,
EditRevertCellResult,
ExecutionPlanOptions
ExecutionPlanOptions,
queryeditor
} from 'azdata';
import { QueryInfo } from 'sql/platform/query/common/queryModelService';
@@ -24,6 +25,18 @@ export const SERVICE_ID = 'queryModelService';
export const IQueryModelService = createDecorator<IQueryModelService>(SERVICE_ID);
export interface IQueryPlanInfo {
providerId: string;
fileUri: string;
planXml: string;
}
export interface IQueryEvent {
type: queryeditor.QueryEvent;
uri: string;
params?: any;
}
/**
* Interface for the logic of handling running queries and grid interactions for all URIs.
*/
@@ -56,7 +69,7 @@ export interface IQueryModelService {
onRunQueryStart: Event<string>;
onRunQueryComplete: Event<string>;
onQueryEvent: Event<IQueryEvent>;
// Edit Data Functions
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void;

View File

@@ -9,7 +9,7 @@ import * as GridContentEvents from 'sql/parts/grid/common/gridContentEvents';
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
import QueryRunner, { EventType as QREvents } from 'sql/platform/query/common/queryRunner';
import { DataService } from 'sql/parts/grid/services/dataService';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { IQueryModelService, IQueryPlanInfo, IQueryEvent } from 'sql/platform/query/common/queryModel';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { QueryStatusbarItem } from 'sql/parts/query/execution/queryStatus';
import { SqlFlavorStatusbarItem } from 'sql/parts/query/common/flavorStatus';
@@ -68,11 +68,13 @@ export class QueryModelService implements IQueryModelService {
private _queryInfoMap: Map<string, QueryInfo>;
private _onRunQueryStart: Emitter<string>;
private _onRunQueryComplete: Emitter<string>;
private _onQueryEvent: Emitter<IQueryEvent>;
private _onEditSessionReady: Emitter<azdata.EditSessionReadyParams>;
// EVENTS /////////////////////////////////////////////////////////////
public get onRunQueryStart(): Event<string> { return this._onRunQueryStart.event; }
public get onRunQueryComplete(): Event<string> { return this._onRunQueryComplete.event; }
public get onQueryEvent(): Event<IQueryEvent> { return this._onQueryEvent.event; }
public get onEditSessionReady(): Event<azdata.EditSessionReadyParams> { return this._onEditSessionReady.event; }
// CONSTRUCTOR /////////////////////////////////////////////////////////
@@ -83,6 +85,7 @@ export class QueryModelService implements IQueryModelService {
this._queryInfoMap = new Map<string, QueryInfo>();
this._onRunQueryStart = new Emitter<string>();
this._onRunQueryComplete = new Emitter<string>();
this._onQueryEvent = new Emitter<IQueryEvent>();
this._onEditSessionReady = new Emitter<azdata.EditSessionReadyParams>();
// Register Statusbar items
@@ -308,13 +311,40 @@ export class QueryModelService implements IQueryModelService {
});
queryRunner.addListener(QREvents.COMPLETE, totalMilliseconds => {
this._onRunQueryComplete.fire(uri);
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStop',
uri: uri
};
this._onQueryEvent.fire(event);
// fire UI event
this._fireQueryEvent(uri, 'complete', totalMilliseconds);
});
queryRunner.addListener(QREvents.START, () => {
this._onRunQueryStart.fire(uri);
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStart',
uri: uri
};
this._onQueryEvent.fire(event);
this._fireQueryEvent(uri, 'start');
});
queryRunner.addListener(QREvents.QUERY_PLAN_AVAILABLE, (planInfo) => {
// fire extensibility API event
let event: IQueryEvent = {
type: 'executionPlan',
uri: planInfo.fileUri,
params: planInfo
};
this._onQueryEvent.fire(event);
});
info.queryRunner = queryRunner;
info.dataService = this._instantiationService.createInstance(DataService, uri);
this._queryInfoMap.set(uri, info);
@@ -422,10 +452,26 @@ export class QueryModelService implements IQueryModelService {
});
queryRunner.addListener(QREvents.COMPLETE, totalMilliseconds => {
this._onRunQueryComplete.fire(ownerUri);
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStop',
uri: ownerUri
};
this._onQueryEvent.fire(event);
// fire UI event
this._fireQueryEvent(ownerUri, 'complete', totalMilliseconds);
});
queryRunner.addListener(QREvents.START, () => {
this._onRunQueryStart.fire(ownerUri);
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStart',
uri: ownerUri
};
this._onQueryEvent.fire(event);
// fire UI event
this._fireQueryEvent(ownerUri, 'start');
});
queryRunner.addListener(QREvents.EDIT_SESSION_READY, e => {

View File

@@ -25,6 +25,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ResultSerializer } from 'sql/platform/node/resultSerializer';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IQueryPlanInfo } from 'sql/platform/query/common/queryModel';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { URI } from 'vs/base/common/uri';
@@ -41,7 +42,8 @@ export const enum EventType {
BATCH_START = 'batchStart',
BATCH_COMPLETE = 'batchComplete',
RESULT_SET = 'resultSet',
EDIT_SESSION_READY = 'editSessionReady'
EDIT_SESSION_READY = 'editSessionReady',
QUERY_PLAN_AVAILABLE = 'queryPlanAvailable'
}
export interface IEventType {
@@ -52,6 +54,7 @@ export interface IEventType {
batchComplete: azdata.BatchSummary;
resultSet: azdata.ResultSetSummary;
editSessionReady: IEditSessionReadyEvent;
queryPlanAvailable: IQueryPlanInfo;
}
export interface IGridMessage extends azdata.IResultMessage {
@@ -363,7 +366,11 @@ export default class QueryRunner extends Disposable {
// check if this result has show plan, this needs work, it won't work for any other provider
let hasShowPlan = !!result.resultSetSummary.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
if (hasShowPlan) {
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue));
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => {
if (e.resultSubset.rows) {
this._planXml.resolve(e.resultSubset.rows[0][0].displayValue);
}
});
}
}
// we will just ignore the set if we already have it
@@ -387,7 +394,20 @@ export default class QueryRunner extends Disposable {
// check if this result has show plan, this needs work, it won't work for any other provider
let hasShowPlan = !!result.resultSetSummary.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
if (hasShowPlan) {
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue));
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => {
if (e.resultSubset.rows) {
let planXmlString = e.resultSubset.rows[0][0].displayValue;
this._planXml.resolve(e.resultSubset.rows[0][0].displayValue);
// fire query plan available event if execution is completed
if (result.resultSetSummary.complete) {
this._eventEmitter.emit(EventType.QUERY_PLAN_AVAILABLE, {
providerId: 'MSSQL',
fileUri: result.ownerUri,
planXml: planXmlString
});
}
}
});
}
}
if (batchSet) {

View File

@@ -7,11 +7,35 @@
import { IMainContext } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostQueryEditorShape, SqlMainContext, MainThreadQueryEditorShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { IQueryEvent } from 'sql/platform/query/common/queryModel';
class ExtHostQueryDocument implements azdata.queryeditor.QueryDocument {
constructor(
public providerId: string,
public uri: string,
private _proxy: MainThreadQueryEditorShape) {
}
// get the document's execution options
getOptions(): Map<string, string> {
return undefined;
}
// set the document's execution optionsß
setOptions(options: Map<string, string>): void {
}
createQueryTab(tab: azdata.window.DialogTab): void {
this._proxy.$createQueryTab(this.uri, tab.title, tab.content);
}
}
export class ExtHostQueryEditor implements ExtHostQueryEditorShape {
private _proxy: MainThreadQueryEditorShape;
private _nextListenerHandle: number = 0;
private _queryListeners = new Map<number, azdata.queryeditor.QueryEventListener>();
constructor(
mainContext: IMainContext
@@ -26,4 +50,17 @@ export class ExtHostQueryEditor implements ExtHostQueryEditorShape {
public $runQuery(fileUri: string): void {
return this._proxy.$runQuery(fileUri);
}
public $registerQueryInfoListener(providerId: string, listener: azdata.queryeditor.QueryEventListener): void {
this._queryListeners[this._nextListenerHandle] = listener;
this._proxy.$registerQueryInfoListener(this._nextListenerHandle, providerId);
this._nextListenerHandle++;
}
public $onQueryEvent(handle: number, fileUri:string, event: IQueryEvent): void {
let listener: azdata.queryeditor.QueryEventListener = this._queryListeners[handle];
if (listener) {
listener.onQueryEvent(event.type, new ExtHostQueryDocument('MSSQL', fileUri, this._proxy), event.params.planXml);
}
}
}

View File

@@ -12,6 +12,7 @@ import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/q
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IQueryModelService, IQueryEvent } from 'sql/platform/query/common/queryModel';
@extHostNamedCustomer(SqlMainContext.MainThreadQueryEditor)
export class MainThreadQueryEditor implements MainThreadQueryEditorShape {
@@ -23,6 +24,7 @@ export class MainThreadQueryEditor implements MainThreadQueryEditorShape {
extHostContext: IExtHostContext,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IQueryEditorService private _queryEditorService: IQueryEditorService,
@IQueryModelService private _queryModelService: IQueryModelService,
@IEditorService private _editorService: IEditorService
) {
if (extHostContext) {
@@ -75,4 +77,24 @@ export class MainThreadQueryEditor implements MainThreadQueryEditorShape {
}
}
}
public $registerQueryInfoListener(handle: number, providerId: string): void {
this._toDispose.push(this._queryModelService.onQueryEvent(event => {
this._proxy.$onQueryEvent(handle, event.uri, event);
}));
}
public $createQueryTab(fileUri: string, title: string, componentId: string): void {
let editors = this._editorService.visibleControls.filter(resource => {
return !!resource && resource.input.getResource().toString() === fileUri;
});
let editor = editors && editors.length > 0 ? editors[0] : undefined;
if (editor) {
let queryEditor = editor as QueryEditor;
if (queryEditor) {
queryEditor.registerQueryModelViewTab(title, componentId);
}
}
}
}

View File

@@ -438,13 +438,20 @@ export function createApiFactory(
// namespace: queryeditor
const queryEditor: typeof azdata.queryeditor = {
connect(fileUri: string, connectionId: string): Thenable<void> {
return extHostQueryEditor.$connect(fileUri, connectionId);
},
runQuery(fileUri: string): void {
runQuery(fileUri: string, options?: Map<string, string>): void {
extHostQueryEditor.$runQuery(fileUri);
},
registerQueryEventListener(listener: azdata.queryeditor.QueryEventListener): void {
extHostQueryEditor.$registerQueryInfoListener('MSSQL', listener);
},
getQueryDocument(fileUri: string): azdata.queryeditor.QueryDocument {
return undefined;
}
};

View File

@@ -24,6 +24,7 @@ import {
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
import { IUndoStopOptions } from 'vs/workbench/api/common/extHost.protocol';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IQueryEvent } from 'sql/platform/query/common/queryModel';
export abstract class ExtHostAccountManagementShape {
$autoOAuthCancelled(handle: number): Thenable<void> { throw ni(); }
@@ -757,11 +758,14 @@ export interface MainThreadModelViewDialogShape extends IDisposable {
$setDirty(handle: number, isDirty: boolean): void;
}
export interface ExtHostQueryEditorShape {
$onQueryEvent(handle: number, fileUri:string, event: IQueryEvent): void;
}
export interface MainThreadQueryEditorShape extends IDisposable {
$connect(fileUri: string, connectionId: string): Thenable<void>;
$runQuery(fileUri: string): void;
$createQueryTab(fileUri: string, title: string, content: string): void;
$registerQueryInfoListener(handle: number, providerId: string): void;
}
export interface ExtHostNotebookShape {
@@ -870,4 +874,4 @@ export interface ExtHostExtensionManagementShape {
export interface MainThreadExtensionManagementShape extends IDisposable {
$install(vsixPath: string): Thenable<string>;
}
}