mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-29 01:25:37 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
117
src/sql/parts/query/execution/keyboardQueryActions.ts
Normal file
117
src/sql/parts/query/execution/keyboardQueryActions.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import nls = require('vs/nls');
|
||||
|
||||
import { Action} from 'vs/base/common/actions';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
/**
|
||||
* Locates the active editor and calls runQuery() on the editor if it is a QueryEditor.
|
||||
*/
|
||||
export class RunQueryKeyboardAction extends Action {
|
||||
|
||||
public static ID = 'runQueryKeyboardAction';
|
||||
public static LABEL = nls.localize('runQueryKeyboardAction', 'Run Query');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor && editor instanceof QueryEditor) {
|
||||
let queryEditor: QueryEditor = editor;
|
||||
queryEditor.runQuery();
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the active editor and calls runCurrentQuery() on the editor if it is a QueryEditor.
|
||||
*/
|
||||
export class RunCurrentQueryKeyboardAction extends Action {
|
||||
public static ID = 'runCurrentQueryKeyboardAction';
|
||||
public static LABEL = nls.localize('runCurrentQueryKeyboardAction', 'Run Current Query');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor && editor instanceof QueryEditor) {
|
||||
let queryEditor: QueryEditor = editor;
|
||||
queryEditor.runCurrentQuery();
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the active editor and calls cancelQuery() on the editor if it is a QueryEditor.
|
||||
*/
|
||||
export class CancelQueryKeyboardAction extends Action {
|
||||
|
||||
public static ID = 'cancelQueryKeyboardAction';
|
||||
public static LABEL = nls.localize('cancelQueryKeyboardAction', 'Cancel Query');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor && editor instanceof QueryEditor) {
|
||||
let queryEditor: QueryEditor = editor;
|
||||
queryEditor.cancelQuery();
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the IntelliSense cache
|
||||
*/
|
||||
export class RefreshIntellisenseKeyboardAction extends Action {
|
||||
public static ID = 'refreshIntellisenseKeyboardAction';
|
||||
public static LABEL = nls.localize('refreshIntellisenseKeyboardAction', 'Refresh IntelliSense Cache');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor && editor instanceof QueryEditor) {
|
||||
let queryEditor: QueryEditor = editor;
|
||||
queryEditor.rebuildIntelliSenseCache();
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
543
src/sql/parts/query/execution/queryActions.ts
Normal file
543
src/sql/parts/query/execution/queryActions.ts
Normal file
@@ -0,0 +1,543 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/dropdown';
|
||||
import { Action, IActionItem, IActionRunner } from 'vs/base/common/actions';
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
import { ISelectionData } from 'data';
|
||||
import {
|
||||
IConnectionManagementService,
|
||||
IConnectionParams,
|
||||
INewConnectionParams,
|
||||
ConnectionType,
|
||||
RunQueryOnConnectionMode
|
||||
} from 'sql/parts/connection/common/connectionManagement';
|
||||
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
|
||||
/**
|
||||
* Action class that query-based Actions will extend. This base class automatically handles activating and
|
||||
* deactivating the button when a SQL file is opened.
|
||||
*/
|
||||
export abstract class QueryTaskbarAction extends Action {
|
||||
|
||||
private _classes: string[];
|
||||
|
||||
constructor(
|
||||
protected _connectionManagementService: IConnectionManagementService,
|
||||
protected editor: QueryEditor,
|
||||
id: string,
|
||||
enabledClass: string
|
||||
) {
|
||||
super(id);
|
||||
this.enabled = true;
|
||||
this._setCssClass(enabledClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is executed when the button is clicked.
|
||||
*/
|
||||
public abstract run(): TPromise<void>;
|
||||
|
||||
protected updateCssClass(enabledClass: string): void {
|
||||
// set the class, useful on change of label or icon
|
||||
this._setCssClass(enabledClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS classes combining the parent and child classes.
|
||||
* Public for testing only.
|
||||
*/
|
||||
private _setCssClass(enabledClass: string): void {
|
||||
this._classes = [];
|
||||
|
||||
if (enabledClass) {
|
||||
this._classes.push(enabledClass);
|
||||
}
|
||||
this.class = this._classes.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI of the given editor if it is not undefined and is connected.
|
||||
* Public for testing only.
|
||||
*/
|
||||
public isConnected(editor: QueryEditor): boolean {
|
||||
if (!editor || !editor.currentQueryInput) {
|
||||
return false;
|
||||
}
|
||||
return this._connectionManagementService.isConnected(editor.currentQueryInput.uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the given editor to it's current URI.
|
||||
* Public for testing only.
|
||||
*/
|
||||
protected connectEditor(editor: QueryEditor, runQueryOnCompletion?: RunQueryOnConnectionMode, selection?: ISelectionData): void {
|
||||
let params: INewConnectionParams = {
|
||||
input: editor.currentQueryInput,
|
||||
connectionType: ConnectionType.editor,
|
||||
runQueryOnCompletion: runQueryOnCompletion ? runQueryOnCompletion : RunQueryOnConnectionMode.none,
|
||||
querySelection: selection
|
||||
};
|
||||
this._connectionManagementService.showConnectionDialog(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that runs a query in the active SQL text document.
|
||||
*/
|
||||
export class RunQueryAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledClass = 'start';
|
||||
public static ID = 'runQueryAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(connectionManagementService, editor, RunQueryAction.ID, RunQueryAction.EnabledClass);
|
||||
this.label = nls.localize('runQueryLabel', 'Run');
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
if (!this.editor.isSelectionEmpty()) {
|
||||
if (this.isConnected(this.editor)) {
|
||||
// If we are already connected, run the query
|
||||
this.runQuery(this.editor);
|
||||
} else {
|
||||
// If we are not already connected, prompt for connection and run the query if the
|
||||
// connection succeeds. "runQueryOnCompletion=true" will cause the query to run after connection
|
||||
this.connectEditor(this.editor, RunQueryOnConnectionMode.executeQuery, this.editor.getSelection());
|
||||
}
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public runCurrent(): TPromise<void> {
|
||||
if (!this.editor.isSelectionEmpty()) {
|
||||
if (this.isConnected(this.editor)) {
|
||||
// If we are already connected, run the query
|
||||
this.runQuery(this.editor, true);
|
||||
} else {
|
||||
// If we are not already connected, prompt for connection and run the query if the
|
||||
// connection succeeds. "runQueryOnCompletion=true" will cause the query to run after connection
|
||||
this.connectEditor(this.editor, RunQueryOnConnectionMode.executeCurrentQuery, this.editor.getSelection(false));
|
||||
}
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public runQuery(editor: QueryEditor, runCurrentStatement: boolean = false) {
|
||||
if (!editor) {
|
||||
editor = this.editor;
|
||||
}
|
||||
|
||||
if (this.isConnected(editor)) {
|
||||
// if the selection isn't empty then execute the selection
|
||||
// otherwise, either run the statement or the script depending on parameter
|
||||
let selection: ISelectionData = editor.getSelection(false);
|
||||
if (runCurrentStatement && selection && this.isCursorPosition(selection)) {
|
||||
editor.currentQueryInput.runQueryStatement(selection);
|
||||
} else {
|
||||
// get the selection again this time with trimming
|
||||
selection = editor.getSelection();
|
||||
editor.currentQueryInput.runQuery(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isCursorPosition(selection: ISelectionData) {
|
||||
return selection.startLine === selection.endLine
|
||||
&& selection.startColumn === selection.endColumn;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that cancels the running query in the current SQL text document.
|
||||
*/
|
||||
export class CancelQueryAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledClass = 'stop';
|
||||
public static ID = 'cancelQueryAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(connectionManagementService, editor, CancelQueryAction.ID, CancelQueryAction.EnabledClass);
|
||||
this.enabled = false;
|
||||
this.label = nls.localize('cancelQueryLabel', 'Cancel');
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
if (this.isConnected(this.editor)) {
|
||||
this._queryModelService.cancelQuery(this.editor.currentQueryInput.uri);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that runs a query in the active SQL text document.
|
||||
*/
|
||||
export class EstimatedQueryPlanAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledClass = 'estimatedQueryPlan';
|
||||
public static ID = 'estimatedQueryPlanAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(connectionManagementService, editor, EstimatedQueryPlanAction.ID, EstimatedQueryPlanAction.EnabledClass);
|
||||
this.label = nls.localize('estimatedQueryPlan', 'Explain');
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
if (!this.editor.isSelectionEmpty()) {
|
||||
if (this.isConnected(this.editor)) {
|
||||
// If we are already connected, run the query
|
||||
this.runQuery(this.editor);
|
||||
} else {
|
||||
// If we are not already connected, prompt for connection and run the query if the
|
||||
// connection succeeds. "runQueryOnCompletion=true" will cause the query to run after connection
|
||||
this.connectEditor(this.editor, RunQueryOnConnectionMode.estimatedQueryPlan, this.editor.getSelection());
|
||||
}
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public runQuery(editor: QueryEditor) {
|
||||
if (!editor) {
|
||||
editor = this.editor;
|
||||
}
|
||||
|
||||
if (this.isConnected(editor)) {
|
||||
editor.currentQueryInput.runQuery(editor.getSelection(), {
|
||||
displayEstimatedQueryPlan: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that disconnects the connection associated with the current query file.
|
||||
*/
|
||||
export class DisconnectDatabaseAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledClass = 'disconnect';
|
||||
public static ID = 'disconnectDatabaseAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(connectionManagementService, editor, DisconnectDatabaseAction.ID, DisconnectDatabaseAction.EnabledClass);
|
||||
this.label = nls.localize('disconnectDatabaseLabel', 'Disconnect');
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
// Call disconnectEditor regardless of the connection state and let the ConnectionManagementService
|
||||
// determine if we need to disconnect, cancel an in-progress conneciton, or do nothing
|
||||
this._connectionManagementService.disconnectEditor(this.editor.currentQueryInput);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that launches a connection dialogue for the current query file
|
||||
*/
|
||||
export class ConnectDatabaseAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledDefaultClass = 'connect';
|
||||
public static EnabledChangeClass = 'changeConnection';
|
||||
public static ID = 'connectDatabaseAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
isChangeConnectionAction: boolean,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
let label: string;
|
||||
let enabledClass: string;
|
||||
|
||||
if (isChangeConnectionAction) {
|
||||
enabledClass = ConnectDatabaseAction.EnabledChangeClass;
|
||||
label = nls.localize('changeConnectionDatabaseLabel', 'Change Connection');
|
||||
} else {
|
||||
enabledClass = ConnectDatabaseAction.EnabledDefaultClass;
|
||||
label = nls.localize('connectDatabaseLabel', 'Connect');
|
||||
}
|
||||
|
||||
super(connectionManagementService, editor, ConnectDatabaseAction.ID, enabledClass);
|
||||
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
this.connectEditor(this.editor);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that either launches a connection dialogue for the current query file,
|
||||
* or disconnects the active connection
|
||||
*/
|
||||
export class ToggleConnectDatabaseAction extends QueryTaskbarAction {
|
||||
|
||||
public static ConnectClass = 'connect';
|
||||
public static DisconnectClass = 'disconnect';
|
||||
public static ID = 'toggleConnectDatabaseAction';
|
||||
|
||||
private _connected: boolean;
|
||||
private _connectLabel: string;
|
||||
private _disconnectLabel: string;
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
isConnected: boolean,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
let enabledClass: string;
|
||||
|
||||
super(connectionManagementService, editor, ToggleConnectDatabaseAction.ID, enabledClass);
|
||||
|
||||
this._connectLabel = nls.localize('connectDatabaseLabel', 'Connect');
|
||||
this._disconnectLabel = nls.localize('disconnectDatabaseLabel', 'Disconnect');
|
||||
|
||||
this.connected = isConnected;
|
||||
}
|
||||
|
||||
public get connected(): boolean {
|
||||
return this._connected;
|
||||
}
|
||||
|
||||
public set connected(value: boolean) {
|
||||
// intentionally always updating, since parent class handles skipping if values
|
||||
this._connected = value;
|
||||
this.updateLabelAndIcon();
|
||||
}
|
||||
|
||||
private updateLabelAndIcon(): void {
|
||||
if (this._connected) {
|
||||
// We are connected, so show option to disconnect
|
||||
this.label = this._disconnectLabel;
|
||||
this.updateCssClass(ToggleConnectDatabaseAction.DisconnectClass);
|
||||
} else {
|
||||
this.label = this._connectLabel;
|
||||
this.updateCssClass(ToggleConnectDatabaseAction.ConnectClass);
|
||||
}
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
if (this.connected) {
|
||||
// Call disconnectEditor regardless of the connection state and let the ConnectionManagementService
|
||||
// determine if we need to disconnect, cancel an in-progress connection, or do nothing
|
||||
this._connectionManagementService.disconnectEditor(this.editor.currentQueryInput);
|
||||
} else {
|
||||
this.connectEditor(this.editor);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that is tied with ListDatabasesActionItem.
|
||||
*/
|
||||
export class ListDatabasesAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledClass = '';
|
||||
public static ID = 'listDatabaseQueryAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(connectionManagementService, editor, ListDatabasesAction.ID, undefined);
|
||||
this.enabled = false;
|
||||
this.class = ListDatabasesAction.EnabledClass;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Action item that handles the dropdown (combobox) that lists the available databases.
|
||||
* Based off StartDebugActionItem.
|
||||
*/
|
||||
export class ListDatabasesActionItem extends EventEmitter implements IActionItem {
|
||||
public static ID = 'listDatabaseQueryActionItem';
|
||||
|
||||
public actionRunner: IActionRunner;
|
||||
private _toDispose: IDisposable[];
|
||||
private _context: any;
|
||||
private _currentDatabaseName: string;
|
||||
private _isConnected: boolean;
|
||||
private $databaseListDropdown: Builder;
|
||||
private _dropdown: Dropdown;
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
constructor(
|
||||
private _editor: QueryEditor,
|
||||
private _action: ListDatabasesAction,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IMessageService private _messageService: IMessageService,
|
||||
@IContextViewService contextViewProvider: IContextViewService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
this._toDispose = [];
|
||||
this.$databaseListDropdown = $('.databaseListDropdown');
|
||||
this._dropdown = new Dropdown(this.$databaseListDropdown.getHTMLElement(), contextViewProvider, themeService, {
|
||||
strictSelection: true,
|
||||
placeholder: nls.localize("selectDatabase", "Select Database")
|
||||
});
|
||||
this._dropdown.onValueChange(s => this.databaseSelected(s));
|
||||
|
||||
// Register event handlers
|
||||
let self = this;
|
||||
this._toDispose.push(this._dropdown.onFocus(() => { self.onDropdownFocus(); }));
|
||||
this._toDispose.push(this._connectionManagementService.onConnectionChanged(params => { self.onConnectionChanged(params); }));
|
||||
}
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public render(container: HTMLElement): void {
|
||||
this.$databaseListDropdown.appendTo(container);
|
||||
}
|
||||
|
||||
public style(styles) {
|
||||
this._dropdown.style(styles);
|
||||
}
|
||||
|
||||
public setActionContext(context: any): void {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
public isEnabled(): boolean {
|
||||
return !!this._isConnected;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._dropdown.focus();
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
this._dropdown.blur();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
// EVENT HANDLERS FROM EDITOR //////////////////////////////////////////
|
||||
public onConnected(): void {
|
||||
let dbName = this.getCurrentDatabaseName();
|
||||
this.updateConnection(dbName);
|
||||
}
|
||||
|
||||
public onDisconnect(): void {
|
||||
this._isConnected = false;
|
||||
this._dropdown.enabled = false;
|
||||
this._currentDatabaseName = undefined;
|
||||
this._dropdown.value = '';
|
||||
}
|
||||
|
||||
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
||||
private databaseSelected(dbName: string): void {
|
||||
let uri = this._editor.connectedUri;
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
let profile = this._connectionManagementService.getConnectionProfile(uri);
|
||||
if (!profile) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._connectionManagementService.changeDatabase(this._editor.uri, dbName)
|
||||
.then(
|
||||
result => {
|
||||
if (!result) {
|
||||
this.resetDatabaseName();
|
||||
this._messageService.show(Severity.Error, nls.localize('changeDatabase.failed', "Failed to change database"));
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.resetDatabaseName();
|
||||
this._messageService.show(Severity.Error, nls.localize('changeDatabase.failedWithError', "Failed to change database {0}", error));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private getCurrentDatabaseName() {
|
||||
let uri = this._editor.connectedUri;
|
||||
if (uri) {
|
||||
let profile = this._connectionManagementService.getConnectionProfile(uri);
|
||||
if (profile) {
|
||||
return profile.databaseName;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private resetDatabaseName() {
|
||||
this._dropdown.value = this.getCurrentDatabaseName();
|
||||
}
|
||||
|
||||
private onConnectionChanged(connParams: IConnectionParams): void {
|
||||
if (!connParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = this._editor.connectedUri;
|
||||
if (uri !== connParams.connectionUri) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateConnection(connParams.connectionProfile.databaseName);
|
||||
}
|
||||
|
||||
private onDropdownFocus(): void {
|
||||
let self = this;
|
||||
|
||||
let uri = self._editor.connectedUri;
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._connectionManagementService.listDatabases(uri)
|
||||
.then(result => {
|
||||
if (result && result.databaseNames) {
|
||||
this._dropdown.values = result.databaseNames;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateConnection(databaseName: string) {
|
||||
this._isConnected = true;
|
||||
this._dropdown.enabled = true;
|
||||
this._currentDatabaseName = databaseName;
|
||||
this._dropdown.value = databaseName;
|
||||
}
|
||||
|
||||
// TESTING PROPERTIES //////////////////////////////////////////////////
|
||||
public get currentDatabaseName(): string {
|
||||
return this._currentDatabaseName;
|
||||
}
|
||||
|
||||
}
|
||||
71
src/sql/parts/query/execution/queryModel.ts
Normal file
71
src/sql/parts/query/execution/queryModel.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||
import { DataService } from 'sql/parts/grid/services/dataService';
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import Event from 'vs/base/common/event';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import {
|
||||
ISelectionData,
|
||||
ResultSetSubset,
|
||||
EditUpdateCellResult,
|
||||
EditSessionReadyParams,
|
||||
EditSubsetResult,
|
||||
EditCreateRowResult,
|
||||
EditRevertCellResult,
|
||||
ExecutionPlanOptions
|
||||
} from 'data';
|
||||
|
||||
export const SERVICE_ID = 'queryModelService';
|
||||
|
||||
export const IQueryModelService = createDecorator<IQueryModelService>(SERVICE_ID);
|
||||
|
||||
/**
|
||||
* Interface for the logic of handling running queries and grid interactions for all URIs.
|
||||
*/
|
||||
export interface IQueryModelService {
|
||||
_serviceBrand: any;
|
||||
|
||||
getConfig(): Promise<{ [key: string]: any }>;
|
||||
getShortcuts(): Promise<any>;
|
||||
getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Thenable<ResultSetSubset>;
|
||||
runQuery(uri: string, selection: ISelectionData, title: string, queryInput: QueryInput, runOptions?: ExecutionPlanOptions): void;
|
||||
runQueryStatement(uri: string, selection: ISelectionData, title: string, queryInput: QueryInput): void;
|
||||
cancelQuery(input: QueryRunner | string): void;
|
||||
disposeQuery(uri: string): Thenable<void>;
|
||||
isRunningQuery(uri: string): boolean;
|
||||
|
||||
getDataService(uri: string): DataService;
|
||||
refreshResultsets(uri: string): void;
|
||||
sendGridContentEvent(uri: string, eventName: string): void;
|
||||
resizeResultsets(uri: string): void;
|
||||
onAngularLoaded(uri: string): void;
|
||||
|
||||
copyResults(uri: string, selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void;
|
||||
setEditorSelection(uri: string, index: number): void;
|
||||
showWarning(uri: string, message: string): void;
|
||||
showError(uri: string, message: string): void;
|
||||
showCommitError(error: string): void;
|
||||
|
||||
onRunQueryStart: Event<string>;
|
||||
onRunQueryComplete: Event<string>;
|
||||
|
||||
|
||||
// Edit Data Functions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): void;
|
||||
disposeEdit(ownerUri: string): Thenable<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult>;
|
||||
commitEdit(ownerUri): Thenable<void>;
|
||||
createRow(ownerUri: string): Thenable<EditCreateRowResult>;
|
||||
deleteRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||
revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<EditRevertCellResult>;
|
||||
revertRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||
getEditRows(ownerUri: string, rowStart: number, numberOfRows: number): Thenable<EditSubsetResult>;
|
||||
|
||||
// Edit Data Callbacks
|
||||
onEditSessionReady: Event<EditSessionReadyParams>;
|
||||
}
|
||||
529
src/sql/parts/query/execution/queryModelService.ts
Normal file
529
src/sql/parts/query/execution/queryModelService.ts
Normal file
@@ -0,0 +1,529 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as GridContentEvents from 'sql/parts/grid/common/gridContentEvents';
|
||||
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||
import { DataService } from 'sql/parts/grid/services/dataService';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/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';
|
||||
|
||||
import {
|
||||
ISelectionData, ResultSetSubset, EditSubsetResult, ExecutionPlanOptions,
|
||||
EditUpdateCellResult, EditSessionReadyParams, EditCreateRowResult, EditRevertCellResult
|
||||
} from 'data';
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import * as platform from 'vs/platform/registry/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
interface QueryEvent {
|
||||
type: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds information about the state of a query runner
|
||||
*/
|
||||
class QueryInfo {
|
||||
public queryRunner: QueryRunner;
|
||||
public dataService: DataService;
|
||||
public queryEventQueue: QueryEvent[];
|
||||
public selection: Array<ISelectionData>;
|
||||
public queryInput: QueryInput;
|
||||
|
||||
// Notes if the angular components have obtained the DataService. If not, all messages sent
|
||||
// via the data service will be lost.
|
||||
public dataServiceReady: boolean;
|
||||
|
||||
constructor() {
|
||||
this.dataServiceReady = false;
|
||||
this.queryEventQueue = [];
|
||||
this.selection = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles running queries and grid interactions for all URIs. Interacts with each URI's results grid via a DataService instance
|
||||
*/
|
||||
export class QueryModelService implements IQueryModelService {
|
||||
_serviceBrand: any;
|
||||
|
||||
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
||||
private _queryInfoMap: Map<string, QueryInfo>;
|
||||
private _onRunQueryStart: Emitter<string>;
|
||||
private _onRunQueryComplete: Emitter<string>;
|
||||
private _onEditSessionReady: Emitter<EditSessionReadyParams>;
|
||||
|
||||
// EVENTS /////////////////////////////////////////////////////////////
|
||||
public get onRunQueryStart(): Event<string> { return this._onRunQueryStart.event; }
|
||||
public get onRunQueryComplete(): Event<string> { return this._onRunQueryComplete.event; }
|
||||
public get onEditSessionReady(): Event<EditSessionReadyParams> { return this._onEditSessionReady.event; }
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IMessageService private _messageService: IMessageService
|
||||
) {
|
||||
this._queryInfoMap = new Map<string, QueryInfo>();
|
||||
this._onRunQueryStart = new Emitter<string>();
|
||||
this._onRunQueryComplete = new Emitter<string>();
|
||||
this._onEditSessionReady = new Emitter<EditSessionReadyParams>();
|
||||
|
||||
// Register Statusbar items
|
||||
(<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor(
|
||||
QueryStatusbarItem,
|
||||
statusbar.StatusbarAlignment.RIGHT,
|
||||
100 /* High Priority */
|
||||
));
|
||||
|
||||
// (<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor(
|
||||
// SqlFlavorStatusbarItem,
|
||||
// statusbar.StatusbarAlignment.RIGHT,
|
||||
// 90 /* Should appear to the right of the SQL editor status */
|
||||
// ));
|
||||
}
|
||||
|
||||
// IQUERYMODEL /////////////////////////////////////////////////////////
|
||||
public getDataService(uri: string): DataService {
|
||||
let dataService = this._getQueryInfo(uri).dataService;
|
||||
if (!dataService) {
|
||||
throw new Error('Could not find data service for uri: ' + uri);
|
||||
}
|
||||
|
||||
return dataService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force all grids to re-render. This is needed to re-render the grids when switching
|
||||
* between different URIs.
|
||||
*/
|
||||
public refreshResultsets(uri: string): void {
|
||||
this._fireGridContentEvent(uri, GridContentEvents.RefreshContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the grid UI to fit the current screen size.
|
||||
*/
|
||||
public resizeResultsets(uri: string): void {
|
||||
this._fireGridContentEvent(uri, GridContentEvents.ResizeContents);
|
||||
}
|
||||
|
||||
public sendGridContentEvent(uri: string, eventName: string): void {
|
||||
this._fireGridContentEvent(uri, eventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called by an angular component's DataService when the component has finished loading.
|
||||
* Sends all previously enqueued query events to the DataService and signals to stop enqueuing
|
||||
* any further events. This prevents QueryEvents from getting lost if they are sent before
|
||||
* angular is listening for them.
|
||||
*/
|
||||
public onAngularLoaded(uri: string) {
|
||||
let info = this._getQueryInfo(uri);
|
||||
info.dataServiceReady = true;
|
||||
this._sendQueuedEvents(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more data rows from the current resultSets from the service layer
|
||||
*/
|
||||
public getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Thenable<ResultSetSubset> {
|
||||
return this._getQueryInfo(uri).queryRunner.getQueryRows(rowStart, numberOfRows, batchId, resultId).then(results => {
|
||||
return results.resultSubset;
|
||||
});
|
||||
}
|
||||
|
||||
public getEditRows(uri: string, rowStart: number, numberOfRows: number): Thenable<EditSubsetResult> {
|
||||
return this._queryInfoMap.get(uri).queryRunner.getEditRows(rowStart, numberOfRows).then(results => {
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
public getConfig(): Promise<{ [key: string]: any }> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getShortcuts(): Promise<any> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public copyResults(uri: string, selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
||||
this._queryInfoMap.get(uri).queryRunner.copyResults(selection, batchId, resultId, includeHeaders);
|
||||
}
|
||||
|
||||
public setEditorSelection(uri: string, index: number): void {
|
||||
let info: QueryInfo = this._queryInfoMap.get(uri);
|
||||
if (info && info.queryInput) {
|
||||
info.queryInput.updateSelection(info.selection[index]);
|
||||
}
|
||||
}
|
||||
|
||||
public showWarning(uri: string, message: string): void {
|
||||
}
|
||||
|
||||
public showError(uri: string, message: string): void {
|
||||
}
|
||||
|
||||
public showCommitError(error: string): void {
|
||||
this._messageService.show(Severity.Error, nls.localize('commitEditFailed', 'Commit row failed: ') + error);
|
||||
}
|
||||
|
||||
public isRunningQuery(uri: string): boolean {
|
||||
return !this._queryInfoMap.has(uri)
|
||||
? false
|
||||
: this._getQueryInfo(uri).queryRunner.isExecuting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a query for the given URI with the given text selection
|
||||
*/
|
||||
public runQuery(uri: string, selection: ISelectionData,
|
||||
title: string, queryInput: QueryInput, runOptions?: ExecutionPlanOptions): void {
|
||||
this.doRunQuery(uri, selection, title, queryInput, false, runOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the current SQL statement for the given URI
|
||||
*/
|
||||
public runQueryStatement(uri: string, selection: ISelectionData,
|
||||
title: string, queryInput: QueryInput): void {
|
||||
this.doRunQuery(uri, selection, title, queryInput, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Query implementation
|
||||
*/
|
||||
private doRunQuery(uri: string, selection: ISelectionData,
|
||||
title: string, queryInput: QueryInput,
|
||||
runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): void {
|
||||
// Reuse existing query runner if it exists
|
||||
let queryRunner: QueryRunner;
|
||||
let info: QueryInfo;
|
||||
|
||||
if (this._queryInfoMap.has(uri)) {
|
||||
info = this._getQueryInfo(uri);
|
||||
let existingRunner: QueryRunner = info.queryRunner;
|
||||
|
||||
// If the query is already in progress, don't attempt to send it
|
||||
if (existingRunner.isExecuting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the query is not in progress, we can reuse the query runner
|
||||
queryRunner = existingRunner;
|
||||
info.selection = [];
|
||||
} else {
|
||||
// We do not have a query runner for this editor, so create a new one
|
||||
// and map it to the results uri
|
||||
info = new QueryInfo();
|
||||
queryRunner = this.initQueryRunner(uri, title, info);
|
||||
}
|
||||
|
||||
this._getQueryInfo(uri).queryInput = queryInput;
|
||||
|
||||
if (runCurrentStatement) {
|
||||
queryRunner.runQueryStatement(selection);
|
||||
} else {
|
||||
queryRunner.runQuery(selection, runOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private initQueryRunner(uri: string, title: string, info: QueryInfo): QueryRunner {
|
||||
let queryRunner: QueryRunner;
|
||||
queryRunner = this._instantiationService.createInstance(QueryRunner, uri, title);
|
||||
queryRunner.eventEmitter.on('resultSet', (resultSet) => {
|
||||
this._fireQueryEvent(uri, 'resultSet', resultSet);
|
||||
});
|
||||
queryRunner.eventEmitter.on('batchStart', (batch) => {
|
||||
let link = undefined;
|
||||
if (batch.selection) {
|
||||
link = {
|
||||
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1)
|
||||
};
|
||||
}
|
||||
let message = {
|
||||
message: LocalizedConstants.runQueryBatchStartMessage,
|
||||
batchId: batch.id,
|
||||
isError: false,
|
||||
time: new Date().toLocaleTimeString(),
|
||||
link: link
|
||||
};
|
||||
this._fireQueryEvent(uri, 'message', message);
|
||||
info.selection.push(this._validateSelection(batch.selection));
|
||||
});
|
||||
queryRunner.eventEmitter.on('message', (message) => {
|
||||
this._fireQueryEvent(uri, 'message', message);
|
||||
});
|
||||
queryRunner.eventEmitter.on('complete', (totalMilliseconds) => {
|
||||
this._onRunQueryComplete.fire(uri);
|
||||
this._fireQueryEvent(uri, 'complete', totalMilliseconds);
|
||||
});
|
||||
queryRunner.eventEmitter.on('start', () => {
|
||||
this._onRunQueryStart.fire(uri);
|
||||
this._fireQueryEvent(uri, 'start');
|
||||
});
|
||||
|
||||
info = new QueryInfo();
|
||||
info.queryRunner = queryRunner;
|
||||
info.dataService = this._instantiationService.createInstance(DataService, uri);
|
||||
this._queryInfoMap.set(uri, info);
|
||||
return queryRunner;
|
||||
}
|
||||
|
||||
public cancelQuery(input: QueryRunner | string): void {
|
||||
let queryRunner: QueryRunner;
|
||||
|
||||
if (typeof input === 'string') {
|
||||
if (this._queryInfoMap.has(input)) {
|
||||
queryRunner = this._getQueryInfo(input).queryRunner;
|
||||
}
|
||||
} else {
|
||||
queryRunner = input;
|
||||
}
|
||||
|
||||
if (queryRunner === undefined || !queryRunner.isExecuting) {
|
||||
// TODO: Cannot cancel query as no query is running.
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch the spinner to canceling, which will be reset when the query execute sends back its completed event
|
||||
// TODO indicate on the status bar that the query is being canceled
|
||||
|
||||
// Cancel the query
|
||||
queryRunner.cancelQuery().then(success => undefined, error => {
|
||||
// On error, show error message and notify that the query is complete so that buttons and other status indicators
|
||||
// can be correct
|
||||
this._messageService.show(Severity.Error, strings.format(LocalizedConstants.msgCancelQueryFailed, error));
|
||||
this._fireQueryEvent(queryRunner.uri, 'complete', 0);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public disposeQuery(ownerUri: string): Thenable<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.dispose();
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// EDIT DATA METHODS /////////////////////////////////////////////////////
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): void {
|
||||
// Reuse existing query runner if it exists
|
||||
let queryRunner: QueryRunner;
|
||||
let info: QueryInfo;
|
||||
|
||||
if (this._queryInfoMap.has(ownerUri)) {
|
||||
info = this._getQueryInfo(ownerUri);
|
||||
let existingRunner: QueryRunner = info.queryRunner;
|
||||
|
||||
// If the initialization is already in progress
|
||||
if (existingRunner.isExecuting) {
|
||||
return;
|
||||
}
|
||||
|
||||
queryRunner = existingRunner;
|
||||
} else {
|
||||
// We do not have a query runner for this editor, so create a new one
|
||||
// and map it to the results uri
|
||||
queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri, ownerUri);
|
||||
queryRunner.eventEmitter.on('resultSet', (resultSet) => {
|
||||
this._fireQueryEvent(ownerUri, 'resultSet', resultSet);
|
||||
});
|
||||
queryRunner.eventEmitter.on('batchStart', (batch) => {
|
||||
let link = undefined;
|
||||
if (batch.selection) {
|
||||
link = {
|
||||
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1),
|
||||
uri: ''
|
||||
};
|
||||
}
|
||||
let message = {
|
||||
message: LocalizedConstants.runQueryBatchStartMessage,
|
||||
batchId: undefined,
|
||||
isError: false,
|
||||
time: new Date().toLocaleTimeString(),
|
||||
link: link
|
||||
};
|
||||
this._fireQueryEvent(ownerUri, 'message', message);
|
||||
});
|
||||
queryRunner.eventEmitter.on('message', (message) => {
|
||||
this._fireQueryEvent(ownerUri, 'message', message);
|
||||
});
|
||||
queryRunner.eventEmitter.on('complete', (totalMilliseconds) => {
|
||||
this._onRunQueryComplete.fire(ownerUri);
|
||||
this._fireQueryEvent(ownerUri, 'complete', totalMilliseconds);
|
||||
});
|
||||
queryRunner.eventEmitter.on('start', () => {
|
||||
this._onRunQueryStart.fire(ownerUri);
|
||||
this._fireQueryEvent(ownerUri, 'start');
|
||||
});
|
||||
queryRunner.eventEmitter.on('editSessionReady', (ownerUri, success, message) => {
|
||||
this._onEditSessionReady.fire({ ownerUri: ownerUri, success: success, message: message });
|
||||
this._fireQueryEvent(ownerUri, 'editSessionReady');
|
||||
});
|
||||
|
||||
info = new QueryInfo();
|
||||
info.queryRunner = queryRunner;
|
||||
info.dataService = this._instantiationService.createInstance(DataService, ownerUri);
|
||||
this._queryInfoMap.set(ownerUri, info);
|
||||
}
|
||||
|
||||
queryRunner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit);
|
||||
}
|
||||
|
||||
public cancelInitializeEdit(input: QueryRunner | string): void {
|
||||
// TODO: Implement query cancellation service
|
||||
}
|
||||
|
||||
public disposeEdit(ownerUri: string): Thenable<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.disposeEdit(ownerUri);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.updateCell(ownerUri, rowId, columnId, newValue).then((result) => result, error => {
|
||||
this._messageService.show(Severity.Error, nls.localize('updateCellFailed', 'Update cell failed: ') + error.message);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public commitEdit(ownerUri): Thenable<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.commitEdit(ownerUri).then(() => { }, error => {
|
||||
this._messageService.show(Severity.Error, nls.localize('commitEditFailed', 'Commit row failed: ') + error.message);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public createRow(ownerUri: string): Thenable<EditCreateRowResult> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.createRow(ownerUri);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public deleteRow(ownerUri: string, rowId: number): Thenable<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.deleteRow(ownerUri, rowId);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<EditRevertCellResult> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.revertCell(ownerUri, rowId, columnId);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public revertRow(ownerUri: string, rowId: number): Thenable<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.revertRow(ownerUri, rowId);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// PRIVATE METHODS //////////////////////////////////////////////////////
|
||||
|
||||
private _getQueryRunner(ownerUri): QueryRunner {
|
||||
let queryRunner: QueryRunner = undefined;
|
||||
if (this._queryInfoMap.has(ownerUri)) {
|
||||
let existingRunner = this._getQueryInfo(ownerUri).queryRunner;
|
||||
// If the query is not already executing then set it up
|
||||
if (!existingRunner.isExecuting) {
|
||||
queryRunner = this._getQueryInfo(ownerUri).queryRunner;
|
||||
}
|
||||
}
|
||||
// return undefined if not found or is already executing
|
||||
return queryRunner;
|
||||
}
|
||||
|
||||
private _fireGridContentEvent(uri: string, type: string): void {
|
||||
let info: QueryInfo = this._getQueryInfo(uri);
|
||||
|
||||
if (info && info.dataServiceReady) {
|
||||
let service: DataService = this.getDataService(uri);
|
||||
if (service) {
|
||||
// There is no need to queue up these events like there is for the query events because
|
||||
// if the DataService is not yet ready there will be no grid content to update
|
||||
service.gridContentObserver.next(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _fireQueryEvent(uri: string, type: string, data?: any) {
|
||||
let info: QueryInfo = this._getQueryInfo(uri);
|
||||
|
||||
if (info.dataServiceReady) {
|
||||
let service: DataService = this.getDataService(uri);
|
||||
service.queryEventObserver.next({
|
||||
type: type,
|
||||
data: data
|
||||
});
|
||||
} else {
|
||||
let queueItem: QueryEvent = { type: type, data: data };
|
||||
info.queryEventQueue.push(queueItem);
|
||||
}
|
||||
}
|
||||
|
||||
private _sendQueuedEvents(uri: string): void {
|
||||
let info: QueryInfo = this._getQueryInfo(uri);
|
||||
while (info.queryEventQueue.length > 0) {
|
||||
let event: QueryEvent = info.queryEventQueue.shift();
|
||||
this._fireQueryEvent(uri, event.type, event.data);
|
||||
}
|
||||
}
|
||||
|
||||
private _getQueryInfo(uri: string): QueryInfo {
|
||||
return this._queryInfoMap.get(uri);
|
||||
}
|
||||
|
||||
// TODO remove this funciton and its usages when #821 in vscode-mssql is fixed and
|
||||
// the SqlToolsService version is updated in this repo - coquagli 4/19/2017
|
||||
private _validateSelection(selection: ISelectionData): ISelectionData {
|
||||
if (!selection) {
|
||||
selection = <ISelectionData>{};
|
||||
}
|
||||
selection.endColumn = selection ? Math.max(0, selection.endColumn) : 0;
|
||||
selection.endLine = selection ? Math.max(0, selection.endLine) : 0;
|
||||
selection.startColumn = selection ? Math.max(0, selection.startColumn) : 0;
|
||||
selection.startLine = selection ? Math.max(0, selection.startLine) : 0;
|
||||
return selection;
|
||||
}
|
||||
}
|
||||
525
src/sql/parts/query/execution/queryRunner.ts
Normal file
525
src/sql/parts/query/execution/queryRunner.ts
Normal file
@@ -0,0 +1,525 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 {
|
||||
BatchSummary,
|
||||
QueryCancelResult,
|
||||
QueryExecuteBatchNotificationParams,
|
||||
QueryExecuteCompleteNotificationResult,
|
||||
QueryExecuteResultSetCompleteNotificationParams,
|
||||
QueryExecuteMessageParams,
|
||||
QueryExecuteSubsetParams, QueryExecuteSubsetResult,
|
||||
EditSubsetParams, EditSubsetResult, EditUpdateCellResult, EditCreateRowResult,
|
||||
EditRevertCellResult, ISelectionData, IResultMessage, ExecutionPlanOptions
|
||||
} from 'data';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import * as Constants from 'sql/parts/query/common/constants';
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
import * as Utils from 'sql/parts/connection/common/utils';
|
||||
import { error as consoleError } from 'sql/base/common/log';
|
||||
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import * as os from 'os';
|
||||
|
||||
/*
|
||||
* Query Runner class which handles running a query, reports the results to the content manager,
|
||||
* and handles getting more rows from the service layer and disposing when the content is closed.
|
||||
*/
|
||||
export default class QueryRunner {
|
||||
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
||||
private _batchSets: BatchSummary[] = [];
|
||||
private _isExecuting: boolean;
|
||||
private _uri: string;
|
||||
private _title: string;
|
||||
private _resultLineOffset: number;
|
||||
private _totalElapsedMilliseconds: number;
|
||||
private _hasCompleted: boolean;
|
||||
public eventEmitter: EventEmitter = new EventEmitter();
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
|
||||
constructor(private _ownerUri: string,
|
||||
private _editorTitle: string,
|
||||
@IQueryManagementService private _queryManagementService: IQueryManagementService,
|
||||
@IMessageService private _messageService: IMessageService,
|
||||
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService
|
||||
) {
|
||||
|
||||
// Store the state
|
||||
this._uri = _ownerUri;
|
||||
this._title = _editorTitle;
|
||||
this._isExecuting = false;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
this._hasCompleted = false;
|
||||
}
|
||||
|
||||
// PROPERTIES //////////////////////////////////////////////////////////
|
||||
|
||||
get uri(): string {
|
||||
return this._uri;
|
||||
}
|
||||
|
||||
set uri(uri: string) {
|
||||
this._uri = uri;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
set title(title: string) {
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
get batchSets(): BatchSummary[] {
|
||||
return this._batchSets;
|
||||
}
|
||||
|
||||
set batchSets(batchSets: BatchSummary[]) {
|
||||
this._batchSets = batchSets;
|
||||
}
|
||||
|
||||
get isExecuting(): boolean {
|
||||
return this._isExecuting;
|
||||
}
|
||||
|
||||
get hasCompleted(): boolean {
|
||||
return this._hasCompleted;
|
||||
}
|
||||
|
||||
// PUBLIC METHODS ======================================================
|
||||
|
||||
/**
|
||||
* Cancels the running query, if there is one
|
||||
*/
|
||||
public cancelQuery(): Thenable<QueryCancelResult> {
|
||||
return this._queryManagementService.cancelQuery(this._uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the query with the provided query
|
||||
* @param input Query string to execute
|
||||
*/
|
||||
public runQuery(input: string, runOptions?: ExecutionPlanOptions): Thenable<void>;
|
||||
/**
|
||||
* Runs the query by pulling the query from the document using the provided selection data
|
||||
* @param input selection data
|
||||
*/
|
||||
public runQuery(input: ISelectionData, runOptions?: ExecutionPlanOptions): Thenable<void>;
|
||||
public runQuery(input, runOptions?: ExecutionPlanOptions): Thenable<void> {
|
||||
return this.doRunQuery(input, false, runOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the current SQL statement by pulling the query from the document using the provided selection data
|
||||
* @param input selection data
|
||||
*/
|
||||
public runQueryStatement(input: ISelectionData): Thenable<void> {
|
||||
return this.doRunQuery(input, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation that runs the query with the provided query
|
||||
* @param input Query string to execute
|
||||
*/
|
||||
private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Thenable<void>;
|
||||
private doRunQuery(input: ISelectionData, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Thenable<void>;
|
||||
private doRunQuery(input, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Thenable<void> {
|
||||
let ownerUri = this._uri;
|
||||
this.batchSets = [];
|
||||
this._hasCompleted = false;
|
||||
if (typeof input === 'object' || input === undefined) {
|
||||
// Update internal state to show that we're executing the query
|
||||
this._resultLineOffset = input ? input.startLine : 0;
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
|
||||
// Send the request to execute the query
|
||||
return runCurrentStatement
|
||||
? this._queryManagementService.runQueryStatement(ownerUri, input.startLine, input.startColumn).then(this.handleSuccessRunQueryResult(), this.handleFailureRunQueryResult())
|
||||
: this._queryManagementService.runQuery(ownerUri, input, runOptions).then(this.handleSuccessRunQueryResult(), this.handleFailureRunQueryResult());
|
||||
} else if (typeof input === 'string') {
|
||||
// Update internal state to show that we're executing the query
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
|
||||
return this._queryManagementService.runQueryString(ownerUri, input).then(this.handleSuccessRunQueryResult(), this.handleFailureRunQueryResult());
|
||||
} else {
|
||||
return Promise.reject('Unknown input');
|
||||
}
|
||||
}
|
||||
|
||||
private handleSuccessRunQueryResult() {
|
||||
let self = this;
|
||||
return () => {
|
||||
// The query has started, so lets fire up the result pane
|
||||
self.eventEmitter.emit('start');
|
||||
self._queryManagementService.registerRunner(self, self._uri);
|
||||
};
|
||||
}
|
||||
|
||||
private handleFailureRunQueryResult() {
|
||||
let self = this;
|
||||
return (error: any) => {
|
||||
// Attempting to launch the query failed, show the error message
|
||||
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
self._isExecuting = false;
|
||||
|
||||
self._messageService.show(Severity.Error, nls.localize('query.ExecutionFailedError', 'Execution failed: {0}', error));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a QueryComplete from the service layer
|
||||
*/
|
||||
public handleQueryComplete(result: QueryExecuteCompleteNotificationResult): void {
|
||||
|
||||
// Store the batch sets we got back as a source of "truth"
|
||||
this._isExecuting = false;
|
||||
this._hasCompleted = true;
|
||||
this._batchSets = result.batchSummaries ? result.batchSummaries : [];
|
||||
|
||||
this._batchSets.map((batch) => {
|
||||
if (batch.selection) {
|
||||
batch.selection.startLine = batch.selection.startLine + this._resultLineOffset;
|
||||
batch.selection.endLine = batch.selection.endLine + this._resultLineOffset;
|
||||
}
|
||||
});
|
||||
|
||||
// We're done with this query so shut down any waiting mechanisms
|
||||
this.eventEmitter.emit('complete', Utils.parseNumAsTimeString(this._totalElapsedMilliseconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a BatchStart from the service layer
|
||||
*/
|
||||
public handleBatchStart(result: QueryExecuteBatchNotificationParams): void {
|
||||
let batch = result.batchSummary;
|
||||
|
||||
// Recalculate the start and end lines, relative to the result line offset
|
||||
if (batch.selection) {
|
||||
batch.selection.startLine += this._resultLineOffset;
|
||||
batch.selection.endLine += this._resultLineOffset;
|
||||
}
|
||||
|
||||
// Set the result sets as an empty array so that as result sets complete we can add to the list
|
||||
batch.resultSetSummaries = [];
|
||||
|
||||
// Store the batch
|
||||
this._batchSets[batch.id] = batch;
|
||||
this.eventEmitter.emit('batchStart', batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a BatchComplete from the service layer
|
||||
*/
|
||||
public handleBatchComplete(result: QueryExecuteBatchNotificationParams): void {
|
||||
let batch: BatchSummary = result.batchSummary;
|
||||
|
||||
// Store the batch again to get the rest of the data
|
||||
this._batchSets[batch.id] = batch;
|
||||
let executionTime = <number>(Utils.parseTimeString(batch.executionElapsed) || 0);
|
||||
this._totalElapsedMilliseconds += executionTime;
|
||||
if (executionTime > 0) {
|
||||
// send a time message in the format used for query complete
|
||||
this.sendBatchTimeMessage(batch.id, Utils.parseNumAsTimeString(executionTime));
|
||||
}
|
||||
this.eventEmitter.emit('batchComplete', batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a ResultSetComplete from the service layer
|
||||
*/
|
||||
public handleResultSetComplete(result: QueryExecuteResultSetCompleteNotificationParams): void {
|
||||
if (result && result.resultSetSummary) {
|
||||
let resultSet = result.resultSetSummary;
|
||||
let batchSet: BatchSummary;
|
||||
if (!resultSet.batchId) {
|
||||
// Missing the batchId. In this case, default to always using the first batch in the list
|
||||
// or create one in the case the DMP extension didn't obey the contract perfectly
|
||||
if (this._batchSets.length > 0) {
|
||||
batchSet = this._batchSets[0];
|
||||
} else {
|
||||
batchSet = <BatchSummary>{
|
||||
id: 0,
|
||||
selection: undefined,
|
||||
hasError: false,
|
||||
resultSetSummaries: []
|
||||
};
|
||||
this._batchSets[0] = batchSet;
|
||||
}
|
||||
} else {
|
||||
batchSet = this._batchSets[resultSet.batchId];
|
||||
}
|
||||
if (batchSet) {
|
||||
// Store the result set in the batch and emit that a result set has completed
|
||||
batchSet.resultSetSummaries[resultSet.id] = resultSet;
|
||||
this.eventEmitter.emit('resultSet', resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a Mssage from the service layer
|
||||
*/
|
||||
public handleMessage(obj: QueryExecuteMessageParams): void {
|
||||
let message = obj.message;
|
||||
message.time = new Date(message.time).toLocaleTimeString();
|
||||
|
||||
// Send the message to the results pane
|
||||
this.eventEmitter.emit('message', message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more data rows from the current resultSets from the service layer
|
||||
*/
|
||||
public getQueryRows(rowStart: number, numberOfRows: number, batchIndex: number, resultSetIndex: number): Thenable<QueryExecuteSubsetResult> {
|
||||
const self = this;
|
||||
let rowData: QueryExecuteSubsetParams = <QueryExecuteSubsetParams>{
|
||||
ownerUri: this.uri,
|
||||
resultSetIndex: resultSetIndex,
|
||||
rowsCount: numberOfRows,
|
||||
rowsStartIndex: rowStart,
|
||||
batchIndex: batchIndex
|
||||
};
|
||||
|
||||
return new Promise<QueryExecuteSubsetResult>((resolve, reject) => {
|
||||
self._queryManagementService.getQueryRows(rowData).then(result => {
|
||||
resolve(result);
|
||||
}, error => {
|
||||
self._messageService.show(Severity.Error, nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error));
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a session ready event for Edit Data
|
||||
*/
|
||||
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> {
|
||||
const self = this;
|
||||
|
||||
// Update internal state to show that we're executing the query
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
|
||||
return this._queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit).then(result => {
|
||||
// The query has started, so lets fire up the result pane
|
||||
self.eventEmitter.emit('start');
|
||||
self._queryManagementService.registerRunner(self, ownerUri);
|
||||
}, error => {
|
||||
// Attempting to launch the query failed, show the error message
|
||||
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
self._isExecuting = false;
|
||||
|
||||
self._messageService.show(Severity.Error, nls.localize('query.initEditExecutionFailed', 'Init Edit Execution failed: ') + error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a number of rows from an edit session
|
||||
* @param rowStart The index of the row to start returning (inclusive)
|
||||
* @param numberOfRows The number of rows to return
|
||||
*/
|
||||
public getEditRows(rowStart: number, numberOfRows: number): Thenable<EditSubsetResult> {
|
||||
const self = this;
|
||||
let rowData: EditSubsetParams = {
|
||||
ownerUri: this.uri,
|
||||
rowCount: numberOfRows,
|
||||
rowStartIndex: rowStart
|
||||
};
|
||||
|
||||
return new Promise<EditSubsetResult>((resolve, reject) => {
|
||||
self._queryManagementService.getEditRows(rowData).then(result => {
|
||||
if (!result.hasOwnProperty('rowCount')) {
|
||||
let error = `Nothing returned from subset query`;
|
||||
self._messageService.show(Severity.Error, error);
|
||||
reject(error);
|
||||
}
|
||||
resolve(result);
|
||||
}, error => {
|
||||
let errorMessage = nls.localize('query.moreRowsFailedError', 'Something went wrong getting more rows:');
|
||||
self._messageService.show(Severity.Error, `${errorMessage} ${error}`);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public handleEditSessionReady(ownerUri: string, success: boolean, message: string): void {
|
||||
this.eventEmitter.emit('editSessionReady', ownerUri, success, message);
|
||||
}
|
||||
|
||||
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult> {
|
||||
return this._queryManagementService.updateCell(ownerUri, rowId, columnId, newValue);
|
||||
}
|
||||
|
||||
public commitEdit(ownerUri): Thenable<void> {
|
||||
return this._queryManagementService.commitEdit(ownerUri);
|
||||
}
|
||||
|
||||
public createRow(ownerUri: string): Thenable<EditCreateRowResult> {
|
||||
return this._queryManagementService.createRow(ownerUri).then(result => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public deleteRow(ownerUri: string, rowId: number): Thenable<void> {
|
||||
return this._queryManagementService.deleteRow(ownerUri, rowId);
|
||||
}
|
||||
|
||||
public revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<EditRevertCellResult> {
|
||||
return this._queryManagementService.revertCell(ownerUri, rowId, columnId).then(result => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public revertRow(ownerUri: string, rowId: number): Thenable<void> {
|
||||
return this._queryManagementService.revertRow(ownerUri, rowId);
|
||||
}
|
||||
|
||||
public disposeEdit(ownerUri: string): Thenable<void> {
|
||||
return this._queryManagementService.disposeEdit(ownerUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the Query from the service client
|
||||
* @returns A promise that will be rejected if a problem occured
|
||||
*/
|
||||
public dispose(): Promise<void> {
|
||||
const self = this;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
self._queryManagementService.disposeQuery(self.uri).then(result => {
|
||||
resolve();
|
||||
}, error => {
|
||||
consoleError('Failed disposing query: ' + error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get totalElapsedMilliseconds(): number {
|
||||
return this._totalElapsedMilliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a copy request
|
||||
* @param selection The selection range to copy
|
||||
* @param batchId The batch id of the result to copy from
|
||||
* @param resultId The result id of the result to copy from
|
||||
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
||||
*/
|
||||
copyResults(selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
||||
const self = this;
|
||||
let copyString = '';
|
||||
|
||||
// create a mapping of the ranges to get promises
|
||||
let tasks = selection.map((range, i) => {
|
||||
return () => {
|
||||
return self.getQueryRows(range.fromRow, range.toRow - range.fromRow + 1, batchId, resultId).then((result) => {
|
||||
if (self.shouldIncludeHeaders(includeHeaders)) {
|
||||
let columnHeaders = self.getColumnHeaders(batchId, resultId, range);
|
||||
if (columnHeaders !== undefined) {
|
||||
copyString += columnHeaders.join('\t') + os.EOL;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over the rows to paste into the copy string
|
||||
for (let rowIndex: number = 0; rowIndex < result.resultSubset.rows.length; rowIndex++) {
|
||||
let row = result.resultSubset.rows[rowIndex];
|
||||
let cellObjects = row.slice(range.fromCell, (range.toCell + 1));
|
||||
// Remove newlines if requested
|
||||
let cells = self.shouldRemoveNewLines()
|
||||
? cellObjects.map(x => self.removeNewLines(x.displayValue))
|
||||
: cellObjects.map(x => x.displayValue);
|
||||
copyString += cells.join('\t');
|
||||
if (rowIndex < result.resultSubset.rows.length - 1) {
|
||||
copyString += os.EOL;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
if (tasks.length > 0) {
|
||||
let p = tasks[0]();
|
||||
for (let i = 1; i < tasks.length; i++) {
|
||||
p = p.then(tasks[i]);
|
||||
}
|
||||
p.then(() => {
|
||||
WorkbenchUtils.executeCopy(copyString);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private shouldIncludeHeaders(includeHeaders: boolean): boolean {
|
||||
if (includeHeaders !== undefined) {
|
||||
// Respect the value explicity passed into the method
|
||||
return includeHeaders;
|
||||
}
|
||||
// else get config option from vscode config
|
||||
includeHeaders = WorkbenchUtils.getSqlConfigValue<boolean>(this._workspaceConfigurationService, Constants.copyIncludeHeaders);
|
||||
return !!includeHeaders;
|
||||
}
|
||||
|
||||
private shouldRemoveNewLines(): boolean {
|
||||
// get config copyRemoveNewLine option from vscode config
|
||||
let removeNewLines: boolean = WorkbenchUtils.getSqlConfigValue<boolean>(this._workspaceConfigurationService, Constants.configCopyRemoveNewLine);
|
||||
return !!removeNewLines;
|
||||
}
|
||||
|
||||
private getColumnHeaders(batchId: number, resultId: number, range: ISlickRange): string[] {
|
||||
let headers: string[] = undefined;
|
||||
let batchSummary: BatchSummary = this.batchSets[batchId];
|
||||
if (batchSummary !== undefined) {
|
||||
let resultSetSummary = batchSummary.resultSetSummaries[resultId];
|
||||
headers = resultSetSummary.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => {
|
||||
return info.columnName;
|
||||
});
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private removeNewLines(inputString: string): string {
|
||||
// This regex removes all newlines in all OS types
|
||||
// Windows(CRLF): \r\n
|
||||
// Linux(LF)/Modern MacOS: \n
|
||||
// Old MacOs: \r
|
||||
if (!inputString) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
let outputString: string = inputString.replace(/(\r\n|\n|\r)/gm, '');
|
||||
return outputString;
|
||||
}
|
||||
|
||||
private sendBatchTimeMessage(batchId: number, executionTime: string): void {
|
||||
// get config copyRemoveNewLine option from vscode config
|
||||
let showBatchTime: boolean = WorkbenchUtils.getSqlConfigValue<boolean>(this._workspaceConfigurationService, Constants.configShowBatchTime);
|
||||
if (showBatchTime) {
|
||||
let message: IResultMessage = {
|
||||
batchId: batchId,
|
||||
message: nls.localize('elapsedBatchTime', 'Batch execution time: {0}', executionTime),
|
||||
time: undefined,
|
||||
isError: false
|
||||
};
|
||||
// Send the message to the results pane
|
||||
this.eventEmitter.emit('message', message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
118
src/sql/parts/query/execution/queryStatus.ts
Normal file
118
src/sql/parts/query/execution/queryStatus.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { $, append, show, hide } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import LocalizedConstants = require('sql/parts/query/common/localizedConstants');
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
|
||||
// Query execution status
|
||||
enum QueryExecutionStatus{
|
||||
Executing,
|
||||
Completed
|
||||
}
|
||||
|
||||
// Shows query status in the editor
|
||||
export class QueryStatusbarItem implements IStatusbarItem {
|
||||
|
||||
private _element: HTMLElement;
|
||||
private _queryElement: HTMLElement;
|
||||
private _queryStatusEditors: { [editorUri: string]: QueryExecutionStatus };
|
||||
private _toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private _editorGroupService: IEditorGroupService,
|
||||
) {
|
||||
this._queryStatusEditors = {};
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): IDisposable {
|
||||
this._element = append(container, $('.query-statusbar-group'));
|
||||
this._queryElement = append(this._element, $('div.query-statusbar-item'));
|
||||
hide(this._queryElement);
|
||||
|
||||
this._toDispose = [];
|
||||
this._toDispose.push(
|
||||
this._queryModelService.onRunQueryStart((uri:string) => this._onRunQueryStart(uri)),
|
||||
this._queryModelService.onRunQueryComplete((uri:string) => this._onRunQueryComplete(uri)),
|
||||
this._editorGroupService.onEditorsChanged(() => this._onEditorsChanged()),
|
||||
this._editorGroupService.getStacksModel().onEditorClosed(event => this._onEditorClosed(event))
|
||||
);
|
||||
|
||||
return combinedDisposable(this._toDispose);
|
||||
}
|
||||
|
||||
private _onEditorClosed(event: IEditorCloseEvent): void{
|
||||
let uri = WorkbenchUtils.getEditorUri(event.editor);
|
||||
if (uri && uri in this._queryStatusEditors) {
|
||||
// If active editor is being closed, hide the query status.
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
if (uri === currentUri) {
|
||||
hide(this._queryElement);
|
||||
}
|
||||
}
|
||||
delete this._queryStatusEditors[uri];
|
||||
}
|
||||
}
|
||||
|
||||
private _onEditorsChanged(): void{
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let uri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
|
||||
// Show active editor's query status
|
||||
if (uri && uri in this._queryStatusEditors){
|
||||
this._showStatus(uri);
|
||||
} else {
|
||||
hide(this._queryElement);
|
||||
}
|
||||
} else {
|
||||
hide(this._queryElement);
|
||||
}
|
||||
}
|
||||
|
||||
private _onRunQueryStart(uri: string): void {
|
||||
this._updateStatus(uri, QueryExecutionStatus.Executing);
|
||||
}
|
||||
|
||||
private _onRunQueryComplete(uri: string): void {
|
||||
this._updateStatus(uri, QueryExecutionStatus.Completed);
|
||||
}
|
||||
|
||||
// Update query status for the editor
|
||||
private _updateStatus(uri: string, newStatus: QueryExecutionStatus){
|
||||
if (uri) {
|
||||
this._queryStatusEditors[uri] = newStatus;
|
||||
this._showStatus(uri);
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide query status for active editor
|
||||
private _showStatus(uri: string): void{
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
if (uri === currentUri) {
|
||||
switch(this._queryStatusEditors[uri]){
|
||||
case QueryExecutionStatus.Executing:
|
||||
this._queryElement.textContent = LocalizedConstants.msgStatusRunQueryInProgress;
|
||||
show(this._queryElement);
|
||||
break;
|
||||
default:
|
||||
hide(this._queryElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user