Refresh master with initial release/0.24 snapshot (#332)

* Initial port of release/0.24 source code

* Fix additional headers

* Fix a typo in launch.json
This commit is contained in:
Karl Burtram
2017-12-15 15:38:57 -08:00
committed by GitHub
parent 271b3a0b82
commit 6ad0df0e3e
7118 changed files with 107999 additions and 56466 deletions

View File

@@ -7,4 +7,6 @@ export const copyIncludeHeaders = 'copyIncludeHeaders';
export const configSaveAsCsv = 'saveAsCsv';
export const configCopyRemoveNewLine = 'copyRemoveNewLine';
export const configShowBatchTime = 'showBatchTime';
export const querySection = 'query';
export const shortcutStart = 'shortcut';

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!sql/parts/query/common/media/flavorStatus';
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';

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { localize } from 'vs/nls';
// localizable strings
@@ -12,19 +12,10 @@ export const runQueryBatchStartLine = localize('runQueryBatchStartLine', 'Line {
export const msgCancelQueryFailed = localize('msgCancelQueryFailed', 'Canceling the query failed: {0}');
export const filepathPrompt = localize('filepathPrompt', 'File path');
export const filepathPlaceholder = localize('filepathPlaceholder', 'File name');
export const filepathMessage = localize('filepathMessage', 'File name');
export const overwritePrompt = localize('overwritePrompt', 'A file with this name already exists. Do you want to replace the existing file?');
export const overwritePlaceholder = localize('overwritePlaceholder', 'A file with this name already exists');
export const msgSaveResultInProgress = localize('msgSaveResultInProgress', 'A save request is already executing. Please wait for its completion.');
export const msgCannotOpenContent = localize('msgCannotOpenContent', 'Error occurred opening content in editor.');
export const msgSaveStarted = localize('msgSaveStarted', 'Started saving results to ');
export const msgSaveFailed = localize('msgSaveFailed', 'Failed to save results. ');
export const msgSaveSucceeded = localize('msgSaveSucceeded', 'Successfully saved results to ');
export const msgStatusRunQueryInProgress = localize('msgStatusRunQueryInProgress', 'Executing query...');
// /** Results Pane Labels */
@@ -34,9 +25,8 @@ export const saveCSVLabel = localize('saveCSVLabel', 'Save as CSV');
export const saveJSONLabel = localize('saveJSONLabel', 'Save as JSON');
export const saveExcelLabel = localize('saveExcelLabel', 'Save as Excel');
export const viewChartLabel = localize('viewChartLabel', 'View as Chart');
export const resultPaneLabel = localize('resultPaneLabel', 'Results');
export const selectAll = localize('selectAll', 'Select all');
export const copyLabel = localize('copyLabel', 'Copy');
export const executeQueryLabel = localize('executeQueryLabel', 'Executing query ');
/** Messages Pane Labels */

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .query-statusbar-group > .editor-status-selection {
padding: 0 5px 0 5px;
}

View File

@@ -6,15 +6,15 @@
'use strict';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
import { QueryResultsEditor } from 'sql/parts/query/editor/queryResultsEditor';
@@ -23,7 +23,10 @@ import * as queryContext from 'sql/parts/query/common/queryContext';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor';
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
import { RunQueryKeyboardAction, RunCurrentQueryKeyboardAction, CancelQueryKeyboardAction, RefreshIntellisenseKeyboardAction } from 'sql/parts/query/execution/keyboardQueryActions';
import {
RunQueryKeyboardAction, RunCurrentQueryKeyboardAction, CancelQueryKeyboardAction, RefreshIntellisenseKeyboardAction, ToggleQueryResultsKeyboardAction,
RunQueryShortcutAction, RunCurrentQueryWithActualPlanKeyboardAction
} from 'sql/parts/query/execution/keyboardQueryActions';
import * as gridActions from 'sql/parts/grid/views/gridActions';
import * as gridCommands from 'sql/parts/grid/views/gridCommands';
import { QueryPlanEditor } from 'sql/parts/queryPlan/queryPlanEditor';
@@ -38,10 +41,9 @@ export const ResultsMessagesFocusCondition = ContextKeyExpr.and(ContextKeyExpr.h
// Editor
const queryResultsEditorDescriptor = new EditorDescriptor(
QueryResultsEditor,
QueryResultsEditor.ID,
'QueryResults',
'sql/parts/query/editor/queryResultsEditor',
'QueryResultsEditor'
'QueryResults'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
@@ -49,10 +51,9 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors)
// Editor
const queryEditorDescriptor = new EditorDescriptor(
QueryEditor,
QueryEditor.ID,
'Query',
'sql/parts/query/editor/queryEditor',
'QueryEditor'
'Query'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
@@ -60,22 +61,20 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors)
// Query Plan editor registration
const createLoginEditorDescriptor = new EditorDescriptor(
const queryPlanEditorDescriptor = new EditorDescriptor(
QueryPlanEditor,
QueryPlanEditor.ID,
'QueryPlan',
'sql/parts/queryPlan/queryPlanEditor',
'QueryPlanEditor'
'QueryPlan'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(createLoginEditorDescriptor, [new SyncDescriptor(QueryPlanInput)]);
.registerEditor(queryPlanEditorDescriptor, [new SyncDescriptor(QueryPlanInput)]);
// Editor
const editDataEditorDescriptor = new EditorDescriptor(
EditDataEditor,
EditDataEditor.ID,
'EditData',
'sql/parts/editData/editor/editDataEditor',
'EditDataEditor'
'EditData'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
@@ -104,6 +103,15 @@ actionRegistry.registerWorkbenchAction(
RunCurrentQueryKeyboardAction.LABEL
);
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
RunCurrentQueryWithActualPlanKeyboardAction,
RunCurrentQueryWithActualPlanKeyboardAction.ID,
RunCurrentQueryWithActualPlanKeyboardAction.LABEL
),
RunCurrentQueryWithActualPlanKeyboardAction.LABEL
);
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
CancelQueryKeyboardAction,
@@ -123,6 +131,14 @@ actionRegistry.registerWorkbenchAction(
RefreshIntellisenseKeyboardAction.LABEL
);
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
ToggleQueryResultsKeyboardAction,
ToggleQueryResultsKeyboardAction.ID,
ToggleQueryResultsKeyboardAction.LABEL
),
ToggleQueryResultsKeyboardAction.LABEL
);
// Grid actions
KeybindingsRegistry.registerCommandAndKeybindingRule({
@@ -198,64 +214,98 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
});
// Intellisense and other configuration options
let registryProperties = {
'sql.messagesDefaultOpen': {
'type': 'boolean',
'description': localize('sql.messagesDefaultOpen', 'True for the messages pane to be open by default; false for closed'),
'default': true
},
'sql.saveAsCsv.includeHeaders': {
'type': 'boolean',
'description': localize('sql.saveAsCsv.includeHeaders', '[Optional] When true, column headers are included when saving results as CSV'),
'default': true
},
'sql.copyIncludeHeaders': {
'type': 'boolean',
'description': localize('sql.copyIncludeHeaders', '[Optional] Configuration options for copying results from the Results View'),
'default': false
},
'sql.copyRemoveNewLine': {
'type': 'boolean',
'description': localize('sql.copyRemoveNewLine', '[Optional] Configuration options for copying multi-line results from the Results View'),
'default': true
},
'sql.showBatchTime': {
'type': 'boolean',
'description': localize('sql.showBatchTime', '[Optional] Should execution time be shown for individual batches'),
'default': false
},
'sql.intelliSense.enableIntelliSense': {
'type': 'boolean',
'default': true,
'description': localize('sql.intelliSense.enableIntelliSense', 'Should IntelliSense be enabled')
},
'sql.intelliSense.enableErrorChecking': {
'type': 'boolean',
'default': true,
'description': localize('sql.intelliSense.enableErrorChecking', 'Should IntelliSense error checking be enabled')
},
'sql.intelliSense.enableSuggestions': {
'type': 'boolean',
'default': true,
'description': localize('sql.intelliSense.enableSuggestions', 'Should IntelliSense suggestions be enabled')
},
'sql.intelliSense.enableQuickInfo': {
'type': 'boolean',
'default': true,
'description': localize('sql.intelliSense.enableQuickInfo', 'Should IntelliSense quick info be enabled')
},
'sql.intelliSense.lowerCaseSuggestions': {
'type': 'boolean',
'default': false,
'description': localize('sql.intelliSense.lowerCaseSuggestions', 'Should IntelliSense suggestions be lowercase')
}
};
// Setup keybindings
let initialShortcuts = [
{ name: 'sp_help', primary: KeyMod.Alt + KeyCode.F2 },
// Note: using Ctrl+Shift+N since Ctrl+N is used for "open editor at index" by default. This means it's different from SSMS
{ name: 'sp_who', primary: KeyMod.WinCtrl + KeyMod.Shift + KeyCode.KEY_1 },
{ name: 'sp_lock', primary: KeyMod.WinCtrl + KeyMod.Shift + KeyCode.KEY_2 }
];
for (let i = 0; i < 9; i++) {
const queryIndex = i + 1;
let settingKey = `sql.query.shortcut${queryIndex}`;
let defaultVal = i < initialShortcuts.length ? initialShortcuts[i].name : '';
let defaultPrimary = i < initialShortcuts.length ? initialShortcuts[i].primary : null;
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: `workbench.action.query.shortcut${queryIndex}`,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: QueryEditorVisibleCondition,
primary: defaultPrimary,
handler: accessor => {
accessor.get(IInstantiationService).createInstance(RunQueryShortcutAction).run(queryIndex);
}
});
registryProperties[settingKey] = {
'type': 'string',
'default': defaultVal,
'description': localize('queryShortcutDescription',
'Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter',
queryIndex)
};
}
// Register the query-related configuration options
let configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'sqlEditor',
'title': 'SQL Editor',
'type': 'object',
'properties': {
'sql.messagesDefaultOpen': {
'type': 'boolean',
'description': localize('sql.messagesDefaultOpen', 'True for the messages pane to be open by default; false for closed'),
'default': true
},
'sql.saveAsCsv.includeHeaders': {
'type': 'boolean',
'description': localize('sql.saveAsCsv.includeHeaders', '[Optional] When true, column headers are included when saving results as CSV'),
'default': true
},
'sql.copyIncludeHeaders': {
'type': 'boolean',
'description': localize('sql.copyIncludeHeaders', '[Optional] Configuration options for copying results from the Results View'),
'default': false
},
'sql.copyRemoveNewLine': {
'type': 'boolean',
'description': localize('sql.copyRemoveNewLine', '[Optional] Configuration options for copying multi-line results from the Results View'),
'default': true
},
'sql.showBatchTime': {
'type': 'boolean',
'description': localize('sql.showBatchTime', '[Optional] Should execution time be shown for individual batches'),
'default': false
},
'sql.intelliSense.enableIntelliSense': {
'type': 'boolean',
'default': true,
'description': localize('sql.intelliSense.enableIntelliSense', 'Should IntelliSense be enabled')
},
'sql.intelliSense.enableErrorChecking': {
'type': 'boolean',
'default': true,
'description': localize('sql.intelliSense.enableErrorChecking', 'Should IntelliSense error checking be enabled')
},
'sql.intelliSense.enableSuggestions': {
'type': 'boolean',
'default': true,
'description': localize('sql.intelliSense.enableSuggestions', 'Should IntelliSense suggestions be enabled')
},
'sql.intelliSense.enableQuickInfo': {
'type': 'boolean',
'default': true,
'description': localize('sql.intelliSense.enableQuickInfo', 'Should IntelliSense quick info be enabled')
},
'sql.intelliSense.lowerCaseSuggestions': {
'type': 'boolean',
'default': false,
'description': localize('sql.intelliSense.lowerCaseSuggestions', 'Should IntelliSense suggestions be lowercase')
}
}
'properties': registryProperties
});

View File

@@ -8,6 +8,8 @@ import { IConnectableInput } from 'sql/parts/connection/common/connectionManagem
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { QueryEditorService } from 'sql/parts/query/services/queryEditorService';
import URI from 'vs/base/common/uri';
export interface IQueryEditorOptions extends IEditorOptions {
// Tells IQueryEditorService.queryEditorCheck to not open this input in the QueryEditor.
@@ -32,4 +34,13 @@ export interface IQueryEditorService {
// Clears any QueryEditor data for the given URI held by this service
onQueryInputClosed(uri: string): void;
/**
* Handles updating of SQL files on a save as event. These need special consideration
* due to query results and other information being tied to the URI of the file
* @param {URI} oldResource URI of the file before the save as was completed
* @param {URI} newResource URI of the file after the save as operation was completed
* @memberof IQueryEditorService
*/
onSaveAsCompleted(oldResource: URI, newResource: URI): void;
}

View File

@@ -141,7 +141,7 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
public getEncoding(): string { return this._sql.getEncoding(); }
public suggestFileName(): string { return this._sql.suggestFileName(); }
public getName(): string { return this._sql.getName(); }
public hasAssociatedFilePath(): boolean { return this._sql.hasAssociatedFilePath; }
public get hasAssociatedFilePath(): boolean { return this._sql.hasAssociatedFilePath; }
public setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {
this._sql.setEncoding(encoding, mode);
@@ -158,6 +158,11 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
this.showQueryResultsEditor();
}
public runQueryString(text: string): void {
this._queryModelService.runQueryString(this.uri, text, this.uri, this);
this.showQueryResultsEditor();
}
public onConnectStart(): void {
this._runQueryEnabled = false;
this._cancelQueryEnabled = false;
@@ -189,6 +194,8 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
this.runQuery(selection);
} else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.estimatedQueryPlan) {
this.runQuery(selection, { displayEstimatedQueryPlan: true });
} else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.actualQueryPlan) {
this.runQuery(selection, { displayActualQueryPlan: true });
}
}
this._updateTaskbar.fire();
@@ -232,7 +239,6 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
this._sql.close();
this._results.close();
super.close();
}
/**

View File

@@ -20,7 +20,7 @@ export const IQueryManagementService = createDecorator<IQueryManagementService>(
export interface IQueryManagementService {
_serviceBrand: any;
addQueryRequestHandler(queryType: string, runner: QueryRequestHandler): IDisposable;
addQueryRequestHandler(queryType: string, runner: IQueryRequestHandler): IDisposable;
registerRunner(runner: QueryRunner, uri: string): void;
cancelQuery(ownerUri: string): Thenable<data.QueryCancelResult>;
@@ -57,7 +57,7 @@ export interface IQueryManagementService {
/*
* An object that can handle basic request-response actions related to queries
*/
export interface QueryRequestHandler {
export interface IQueryRequestHandler {
cancelQuery(ownerUri: string): Thenable<data.QueryCancelResult>;
runQuery(ownerUri: string, selection: data.ISelectionData, runOptions?: data.ExecutionPlanOptions): Thenable<void>;
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
@@ -83,7 +83,7 @@ export class QueryManagementService implements IQueryManagementService {
public static readonly DefaultQueryType = 'MSSQL';
public _serviceBrand: any;
private _requestHandlers = new Map<string, QueryRequestHandler>();
private _requestHandlers = new Map<string, IQueryRequestHandler>();
// public for testing only
public _queryRunners = new Map<string, QueryRunner>();
@@ -132,7 +132,7 @@ export class QueryManagementService implements IQueryManagementService {
this.enqueueOrRun(sendNotification, runner);
}
public addQueryRequestHandler(queryType: string, handler: QueryRequestHandler): IDisposable {
public addQueryRequestHandler(queryType: string, handler: IQueryRequestHandler): IDisposable {
this._requestHandlers.set(queryType, handler);
return {
@@ -155,7 +155,7 @@ export class QueryManagementService implements IQueryManagementService {
TelemetryUtils.addTelemetry(this._telemetryService, eventName, data);
}
private _runAction<T>(uri: string, action: (handler: QueryRequestHandler) => Thenable<T>): Thenable<T> {
private _runAction<T>(uri: string, action: (handler: IQueryRequestHandler) => Thenable<T>): Thenable<T> {
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
if (!providerId) {

View File

@@ -54,10 +54,6 @@ export class QueryResultsInput extends EditorInput {
return false;
}
public setVisibleTrue(): void {
this._visible = true;
}
public setBootstrappedTrue(): void {
this._hasBootstrapped = true;
}
@@ -102,6 +98,10 @@ export class QueryResultsInput extends EditorInput {
return this._visible;
}
set visible(visible: boolean) {
this._visible = visible;
}
get uri(): string {
return this._uri;
}

View File

@@ -3,15 +3,20 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EditorInput, Extensions, IEditorRegistry, IEditorDescriptor } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor';
import { IEditorDescriptor, IEditorRegistry, Extensions } from 'vs/workbench/browser/editor';
import { Registry } from 'vs/platform/registry/common/platform';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export interface IEditorDescriptorService {
_serviceBrand: any;
getEditor(input: EditorInput): IEditorDescriptor;
}
export class EditorDescriptorService implements IEditorDescriptorService {
public _serviceBrand: any;
constructor() {
}

View File

@@ -10,7 +10,7 @@ import * as DOM from 'vs/base/browser/dom';
import { Builder, Dimension, withElementById } from 'vs/base/browser/builder';
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { BaseEditor, EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IEditorControl, Position, IEditor } from 'vs/platform/editor/common/editor';
import { VerticalFlexibleSash, HorizontalFlexibleSash, IFlexibleSash } from 'sql/parts/query/views/flexibleSash';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
@@ -38,14 +38,13 @@ import { QueryInput } from 'sql/parts/query/common/queryInput';
import { QueryResultsEditor } from 'sql/parts/query/editor/queryResultsEditor';
import * as queryContext from 'sql/parts/query/common/queryContext';
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
import { ITextFileService, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles';
import {
RunQueryAction, CancelQueryAction, ListDatabasesAction, ListDatabasesActionItem,
ConnectDatabaseAction, ToggleConnectDatabaseAction, EstimatedQueryPlanAction
ConnectDatabaseAction, ToggleConnectDatabaseAction, EstimatedQueryPlanAction,
ActualQueryPlanAction
} from 'sql/parts/query/execution/queryActions';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { IEditorDescriptorService } from 'sql/parts/query/editor/editorDescriptorService';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
@@ -60,9 +59,6 @@ export class QueryEditor extends BaseEditor {
// The height of the tabs above the editor
private readonly _tabHeight: number = 35;
// The height of the taskbar above the editor
private readonly _taskbarHeight: number = 28;
// The minimum width/height of the editors hosted in the QueryEditor
private readonly _minEditorSize: number = 220;
@@ -90,6 +86,7 @@ export class QueryEditor extends BaseEditor {
private _changeConnectionAction: ConnectDatabaseAction;
private _listDatabasesAction: ListDatabasesAction;
private _estimatedQueryPlanAction: EstimatedQueryPlanAction;
private _actualQueryPlanAction: ActualQueryPlanAction;
constructor(
@ITelemetryService _telemetryService: ITelemetryService,
@@ -101,25 +98,15 @@ export class QueryEditor extends BaseEditor {
@IEditorDescriptorService private _editorDescriptorService: IEditorDescriptorService,
@IEditorGroupService private _editorGroupService: IEditorGroupService,
@IContextKeyService contextKeyService: IContextKeyService,
@ITextFileService private _textFileService: ITextFileService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
editorOrientation?: Orientation
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(QueryEditor.ID, _telemetryService, themeService);
if (editorOrientation) {
this._orientation = editorOrientation;
} else {
this._orientation = Orientation.HORIZONTAL;
}
this._orientation = Orientation.HORIZONTAL;
if (contextKeyService) {
this.queryEditorVisible = queryContext.QueryEditorVisibleContext.bindTo(contextKeyService);
}
if (this._textFileService && this._textFileService.models) {
this._textFileService.models.onModelSaved(event => this._onModelSaved(event));
}
}
// PROPERTIES //////////////////////////////////////////////////////////
@@ -144,18 +131,6 @@ export class QueryEditor extends BaseEditor {
: undefined;
}
private _onModelSaved(event: TextFileModelChangeEvent): void {
if (event.resource.toString() !== this.uri) {
TaskUtilities.replaceConnection(this.uri, event.resource.toString(), this._connectionManagementService).then(result => {
if (result && result.connected) {
this.currentQueryInput.onConnectSuccess();
} else {
this.currentQueryInput.onConnectReject();
}
});
}
}
// PUBLIC METHODS ////////////////////////////////////////////////////////////
public get currentQueryInput(): QueryInput {
return <QueryInput>this.input;
@@ -327,11 +302,28 @@ export class QueryEditor extends BaseEditor {
this._createEditor(<QueryResultsInput>input.results, this._resultsEditorContainer)
.then(result => {
this._onResultsEditorCreated(<QueryResultsEditor>result, input.results, this.options);
this._setResultsEditorVisible();
this._doLayout();
this.resultsEditorVisibility = true;
this.hideQueryResultsView = false;
this._doLayout(true);
});
}
private hideQueryResultsView = false;
/**
* Toggle the visibility of the view state of results
*/
public toggleResultsEditorVisibility(): void {
let input = <QueryInput>this.input;
let hideResults = this.hideQueryResultsView;
this.hideQueryResultsView = !this.hideQueryResultsView;
if (!input.results) {
return;
}
this.resultsEditorVisibility = hideResults;
this._doLayout();
}
/**
* Returns the underlying SQL editor's text selection in a 0-indexed format. Returns undefined if there
* is no selected text.
@@ -374,6 +366,23 @@ export class QueryEditor extends BaseEditor {
return true;
}
public getSelectionText(): string {
if (this._sqlEditor && this._sqlEditor.getControl()) {
let control = this._sqlEditor.getControl();
let codeEditor: CodeEditor = <CodeEditor>control;
let vscodeSelection = control.getSelection();
if (codeEditor && vscodeSelection) {
let model = codeEditor.getModel();
let value = model.getValueInRange(vscodeSelection);
if (value !== undefined && value.length > 0) {
return value;
}
}
}
return '';
}
/**
* Calls the run method of this editor's RunQueryAction
*/
@@ -388,6 +397,13 @@ export class QueryEditor extends BaseEditor {
this._runQueryAction.runCurrent();
}
/**
* Calls the runCurrentQueryWithActualPlan method of this editor's ActualQueryPlanAction
*/
public runCurrentQueryWithActualPlan(): void {
this._actualQueryPlanAction.run();
}
/**
* Calls the run method of this editor's CancelQueryAction
*/
@@ -418,6 +434,7 @@ export class QueryEditor extends BaseEditor {
this._changeConnectionAction = this._instantiationService.createInstance(ConnectDatabaseAction, this, true);
this._listDatabasesAction = this._instantiationService.createInstance(ListDatabasesAction, this);
this._estimatedQueryPlanAction = this._instantiationService.createInstance(EstimatedQueryPlanAction, this);
this._actualQueryPlanAction = this._instantiationService.createInstance(ActualQueryPlanAction, this);
// Create HTML Elements for the taskbar
let separator = Taskbar.createTaskbarSeparator();
@@ -464,6 +481,10 @@ export class QueryEditor extends BaseEditor {
*/
private _updateInput(oldInput: QueryInput, newInput: QueryInput, options?: EditorOptions): TPromise<void> {
if (this._sqlEditor) {
this._sqlEditor.clearInput();
}
if (oldInput) {
this._disposeEditors();
}
@@ -548,12 +569,11 @@ export class QueryEditor extends BaseEditor {
if (!descriptor) {
return TPromise.wrapError(new Error(strings.format('Can not find a registered editor for the input {0}', editorInput)));
}
return this._instantiationService.createInstance(<EditorDescriptor>descriptor)
.then((editor: BaseEditor) => {
editor.create(new Builder(container));
editor.setVisible(this.isVisible(), this.position);
return editor;
});
let editor = descriptor.instantiate(this._instantiationService);
editor.create(new Builder(container));
editor.setVisible(this.isVisible(), this.position);
return TPromise.as(editor);
}
/**
@@ -618,7 +638,7 @@ export class QueryEditor extends BaseEditor {
this._sash = this._register(new HorizontalFlexibleSash(parentElement, this._minEditorSize));
} else {
this._sash = this._register(new VerticalFlexibleSash(parentElement, this._minEditorSize));
this._sash.setEdge(this._taskbarHeight + this._tabHeight);
this._sash.setEdge(this.getTaskBarHeight() + this._tabHeight);
}
this._setSashDimension();
@@ -635,7 +655,7 @@ export class QueryEditor extends BaseEditor {
if (this._orientation === Orientation.HORIZONTAL) {
this._sash.setDimenesion(this._dimension);
} else {
this._sash.setDimenesion(new Dimension(this._dimension.width, this._dimension.height - this._taskbarHeight));
this._sash.setDimenesion(new Dimension(this._dimension.width, this._dimension.height - this.getTaskBarHeight()));
}
}
@@ -645,7 +665,7 @@ export class QueryEditor extends BaseEditor {
* the IFlexibleSash could be horizontal or vertical. The same logic is used for horizontal
* and vertical sashes.
*/
private _doLayout(): void {
private _doLayout(skipResizeGridContent: boolean = false): void {
if (!this._isResultsEditorVisible() && this._sqlEditor) {
this._doLayoutSql();
return;
@@ -660,14 +680,21 @@ export class QueryEditor extends BaseEditor {
this._doLayoutVertical();
}
this._resizeGridContents();
if (!skipResizeGridContent) {
this._resizeGridContents();
}
}
private getTaskBarHeight(): number {
let taskBarElement = this.taskbar.getContainer().getHTMLElement();
return DOM.getContentHeight(taskBarElement);
}
private _doLayoutHorizontal(): void {
let splitPointTop: number = this._sash.getSplitPoint();
let parent: ClientRect = this.getContainer().getHTMLElement().getBoundingClientRect();
let sqlEditorHeight = splitPointTop - (parent.top + this._taskbarHeight);
let sqlEditorHeight = splitPointTop - (parent.top + this.getTaskBarHeight());
let titleBar = withElementById('workbench.parts.titlebar');
if (titleBar) {
@@ -675,7 +702,7 @@ export class QueryEditor extends BaseEditor {
}
let queryResultsEditorHeight = parent.bottom - splitPointTop;
this._resultsEditorContainer.hidden = false;
this._sqlEditorContainer.style.height = `${sqlEditorHeight}px`;
this._sqlEditorContainer.style.width = `${this._dimension.width}px`;
this._sqlEditorContainer.style.top = `${this._editorTopOffset}px`;
@@ -695,21 +722,30 @@ export class QueryEditor extends BaseEditor {
let sqlEditorWidth = splitPointLeft;
let queryResultsEditorWidth = parent.width - splitPointLeft;
let taskbarHeight = this.getTaskBarHeight();
this._sqlEditorContainer.style.width = `${sqlEditorWidth}px`;
this._sqlEditorContainer.style.height = `${this._dimension.height - this._taskbarHeight}px`;
this._sqlEditorContainer.style.height = `${this._dimension.height - taskbarHeight}px`;
this._sqlEditorContainer.style.left = `0px`;
this._resultsEditorContainer.hidden = false;
this._resultsEditorContainer.style.width = `${queryResultsEditorWidth}px`;
this._resultsEditorContainer.style.height = `${this._dimension.height - this._taskbarHeight}px`;
this._resultsEditorContainer.style.height = `${this._dimension.height - taskbarHeight}px`;
this._resultsEditorContainer.style.left = `${splitPointLeft}px`;
this._sqlEditor.layout(new Dimension(sqlEditorWidth, this._dimension.height - this._taskbarHeight));
this._resultsEditor.layout(new Dimension(queryResultsEditorWidth, this._dimension.height - this._taskbarHeight));
this._sqlEditor.layout(new Dimension(sqlEditorWidth, this._dimension.height - taskbarHeight));
this._resultsEditor.layout(new Dimension(queryResultsEditorWidth, this._dimension.height - taskbarHeight));
}
private _doLayoutSql() {
if ( this._resultsEditorContainer) {
this._resultsEditorContainer.style.width = '0px';
this._resultsEditorContainer.style.height = '0px';
this._resultsEditorContainer.style.left = '0px';
this._resultsEditorContainer.hidden = true;
}
if (this._dimension) {
this._sqlEditor.layout(new Dimension(this._dimension.width, this._dimension.height - this._taskbarHeight));
this._sqlEditor.layout(new Dimension(this._dimension.width, this._dimension.height - this.getTaskBarHeight()));
}
}
@@ -749,6 +785,7 @@ export class QueryEditor extends BaseEditor {
this._resultsEditorContainer.parentElement.removeChild(this._resultsEditorContainer);
}
this._resultsEditorContainer = null;
this.hideQueryResultsView = false;
}
}
@@ -766,9 +803,9 @@ export class QueryEditor extends BaseEditor {
return input.results.visible;
}
private _setResultsEditorVisible(): void {
set resultsEditorVisibility(isVisible: boolean) {
let input: QueryInput = <QueryInput>this.input;
input.results.setVisibleTrue();
input.results.visible = isVisible;
}
/**

View File

@@ -5,10 +5,51 @@
import nls = require('vs/nls');
import { Action} from 'vs/base/common/actions';
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';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import * as data from 'data';
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
import * as Constants from 'sql/parts/query/common/constants';
import * as ConnectionConstants from 'sql/parts/connection/common/constants';
const singleQuote = '\'';
export function isConnected(editor: QueryEditor, connectionManagementService: IConnectionManagementService): boolean {
if (!editor || !editor.currentQueryInput) {
return false;
}
return connectionManagementService.isConnected(editor.currentQueryInput.uri);
}
function runActionOnActiveQueryEditor(editorService: IWorkbenchEditorService, action: (QueryEditor) => void): void {
const candidates = [editorService.getActiveEditor(), ...editorService.getVisibleEditors()].filter(e => e instanceof QueryEditor);
if (candidates.length > 0) {
action(candidates[0]);
}
}
function escapeSqlString(input: string, escapeChar: string) {
if (!input) {
return input;
}
let output = '';
for (let i = 0; i < input.length; i++) {
let char = input.charAt(i);
output += char;
if (escapeChar === char) {
output += char;
}
}
return output;
}
/**
* Locates the active editor and calls runQuery() on the editor if it is a QueryEditor.
@@ -63,6 +104,29 @@ export class RunCurrentQueryKeyboardAction extends Action {
}
}
export class RunCurrentQueryWithActualPlanKeyboardAction extends Action {
public static ID = 'runCurrentQueryWithActualPlanKeyboardAction';
public static LABEL = nls.localize('runCurrentQueryWithActualPlanKeyboardAction', 'Run Current Query with Actual Plan');
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.runCurrentQueryWithActualPlan();
}
return TPromise.as(null);
}
}
/**
* Locates the active editor and calls cancelQuery() on the editor if it is a QueryEditor.
*/
@@ -115,3 +179,183 @@ export class RefreshIntellisenseKeyboardAction extends Action {
return TPromise.as(null);
}
}
/**
* Hide the query results
*/
export class ToggleQueryResultsKeyboardAction extends Action {
public static ID = 'toggleQueryResultsKeyboardAction';
public static LABEL = nls.localize('toggleQueryResultsKeyboardAction', 'Toggle Query Results');
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.toggleResultsEditorVisibility();
}
return TPromise.as(null);
}
}
/**
* Action class that runs a query in the active SQL text document.
*/
export class RunQueryShortcutAction extends Action {
public static ID = 'runQueryShortcutAction';
constructor(
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
@IQueryModelService protected _queryModelService: IQueryModelService,
@IQueryManagementService private _queryManagementService: IQueryManagementService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService
) {
super(RunQueryShortcutAction.ID);
}
public run(index: number): TPromise<void> {
let promise: Thenable<void> = TPromise.as(null);
runActionOnActiveQueryEditor(this._editorService, (editor) => {
promise = this.runQueryShortcut(editor, index);
});
return new TPromise((resolve, reject) => {
promise.then(success => resolve(null), err => resolve(null));
});
}
/**
* Runs one of the optionally registered query shortcuts. This will lookup the shortcut's stored procedure
* reference from the settings, and if found will execute it plus any
*
* @param {QueryEditor} editor
* @param {number} shortcutIndex which shortcut should be run?
* @memberof RunQueryShortcutAction
*/
public runQueryShortcut(editor: QueryEditor, shortcutIndex: number): Thenable<void> {
if (!editor) {
throw new Error(nls.localize('queryShortcutNoEditor', 'Editor parameter is required for a shortcut to be executed'));
}
if (isConnected(editor, this._connectionManagementService)) {
let shortcutText = this.getShortcutText(shortcutIndex);
if (!shortcutText.trim()) {
// no point going further
return TPromise.as(null);
}
// if the selection isn't empty then execute the selection
// otherwise, either run the statement or the script depending on parameter
let parameterText: string = editor.getSelectionText();
return this.escapeStringParamIfNeeded(editor, shortcutText, parameterText).then((escapedParam) => {
let queryString = `${shortcutText} ${escapedParam}`;
editor.currentQueryInput.runQueryString(queryString);
}).then(success => null, err => {
// swallow errors for now
return null;
});
} else {
return TPromise.as(null);
}
}
private getShortcutText(shortcutIndex: number) {
let shortcutSetting = Constants.shortcutStart + shortcutIndex;
let querySettings = WorkbenchUtils.getSqlConfigSection(this._workspaceConfigurationService, Constants.querySection);
let shortcutText = querySettings[shortcutSetting];
return shortcutText;
}
private escapeStringParamIfNeeded(editor: QueryEditor, shortcutText: string, parameterText: string): Thenable<string> {
if (parameterText && parameterText.length > 0) {
if (this.canQueryProcMetadata(editor)) {
let dbName = this.getDatabaseName(editor);
let query = `exec dbo.sp_sproc_columns @procedure_name = N'${escapeSqlString(shortcutText, singleQuote)}', @procedure_owner = null, @procedure_qualifier = N'${escapeSqlString(dbName, singleQuote)}'`;
return this._queryManagementService.runQueryAndReturn(editor.uri, query)
.then(result => {
switch(this.isProcWithSingleArgument(result)) {
case 1:
// sproc was found and it meets criteria of having 1 string param
// if selection is quoted, leave as-is. Else quote
let trimmedText = parameterText.trim();
if (trimmedText.length > 0) {
if (trimmedText.charAt(0) !== singleQuote || trimmedText.charAt(trimmedText.length - 1) !== singleQuote) {
// Note: SSMS uses the original text, but this causes issues if you have spaces. We intentionally use
// trimmed text since it's likely to be more accurate in this case. For non-quoted cases it shouldn't matter
return `'${trimmedText}'`;
}
}
break;
case -1:
// sproc was found but didn't meet criteria, so append as-is
case 0:
// sproc wasn't found, just append as-is and hope it works
break;
}
return parameterText;
}, err => {
return parameterText;
});
}
return TPromise.as(parameterText);
}
return TPromise.as('');
}
private isProcWithSingleArgument(result: data.SimpleExecuteResult): number {
let columnTypeOrdinal = this.getColumnIndex(result.columnInfo, 'COLUMN_TYPE');
let dataTypeOrdinal = this.getColumnIndex(result.columnInfo, 'DATA_TYPE');
if (columnTypeOrdinal && dataTypeOrdinal) {
let count = 0;
for (let row of result.rows) {
let columnType = parseInt(row[columnTypeOrdinal].displayValue);
if (columnType !== 5) {
if (count > 0) // more than one argument.
{
return -1;
}
let dataType = parseInt(row[dataTypeOrdinal].displayValue);
if (dataType === -9 || // nvarchar
dataType === 12 || // varchar
dataType === -8 || // nchar
dataType === 1 || // char
dataType === -1 || // text
dataType === -10 // ntext
) {
count++;
} else {
// not a string
return -1;
}
}
}
return count;
}
return -1; // Couldn't process so return default value
}
private getColumnIndex(columnInfo: data.IDbColumn[], columnName: string): number {
return columnInfo ? columnInfo.findIndex(c => c.columnName === columnName) : undefined;
}
private canQueryProcMetadata(editor: QueryEditor): boolean {
let info = this._connectionManagementService.getConnectionInfo(editor.uri);
return (info && info.providerId === ConnectionConstants.mssqlProviderName);
}
private getDatabaseName(editor: QueryEditor): string {
let info = this._connectionManagementService.getConnectionInfo(editor.uri);
return info.connectionProfile.databaseName;
}
}

View File

@@ -13,6 +13,9 @@ 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 { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IReadOnlyModel } from 'vs/editor/common/editorCommon';
import { IModel, ICommonCodeEditor } from 'vs/editor/common/editorCommon';
import { ISelectionData } from 'data';
import {
@@ -24,6 +27,8 @@ import {
} from 'sql/parts/connection/common/connectionManagement';
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
import * as Constants from 'sql/parts/query/common/constants';
/**
* Action class that query-based Actions will extend. This base class automatically handles activating and
@@ -103,7 +108,7 @@ export class RunQueryAction extends QueryTaskbarAction {
constructor(
editor: QueryEditor,
@IQueryModelService private _queryModelService: IQueryModelService,
@IQueryModelService protected _queryModelService: IQueryModelService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, RunQueryAction.ID, RunQueryAction.EnabledClass);
@@ -148,7 +153,6 @@ export class RunQueryAction extends QueryTaskbarAction {
// 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();
@@ -157,7 +161,7 @@ export class RunQueryAction extends QueryTaskbarAction {
}
}
private isCursorPosition(selection: ISelectionData) {
protected isCursorPosition(selection: ISelectionData) {
return selection.startLine === selection.endLine
&& selection.startColumn === selection.endColumn;
}
@@ -233,6 +237,46 @@ export class EstimatedQueryPlanAction extends QueryTaskbarAction {
}
}
export class ActualQueryPlanAction extends QueryTaskbarAction {
public static EnabledClass = 'actualQueryPlan';
public static ID = 'actualQueryPlanAction';
constructor(
editor: QueryEditor,
@IQueryModelService private _queryModelService: IQueryModelService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, ActualQueryPlanAction.ID, ActualQueryPlanAction.EnabledClass);
this.label = nls.localize('actualQueryPlan', "Actual");
}
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.actualQueryPlan, 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(), {
displayActualQueryPlan: true
});
}
}
}
/**
* Action class that disconnects the connection associated with the current query file.
*/

View File

@@ -35,8 +35,9 @@ export interface IQueryModelService {
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;
runQueryString(uri: string, selection: string, title: string, queryInput: QueryInput);
cancelQuery(input: QueryRunner | string): void;
disposeQuery(uri: string): Thenable<void>;
disposeQuery(uri: string): void;
isRunningQuery(uri: string): boolean;
getDataService(uri: string): DataService;

View File

@@ -7,17 +7,14 @@
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 QueryRunner, { EventType as QREvents } 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 * as data from 'data';
import { ISlickRange } from 'angular2-slickgrid';
import * as nls from 'vs/nls';
@@ -28,6 +25,9 @@ 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';
import * as types from 'vs/base/common/types';
const selectionSnippetMaxLen = 100;
interface QueryEvent {
type: string;
@@ -41,8 +41,9 @@ class QueryInfo {
public queryRunner: QueryRunner;
public dataService: DataService;
public queryEventQueue: QueryEvent[];
public selection: Array<ISelectionData>;
public selection: Array<data.ISelectionData>;
public queryInput: QueryInput;
public selectionSnippet: string;
// Notes if the angular components have obtained the DataService. If not, all messages sent
// via the data service will be lost.
@@ -65,12 +66,12 @@ export class QueryModelService implements IQueryModelService {
private _queryInfoMap: Map<string, QueryInfo>;
private _onRunQueryStart: Emitter<string>;
private _onRunQueryComplete: Emitter<string>;
private _onEditSessionReady: Emitter<EditSessionReadyParams>;
private _onEditSessionReady: Emitter<data.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; }
public get onEditSessionReady(): Event<data.EditSessionReadyParams> { return this._onEditSessionReady.event; }
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
@@ -80,7 +81,7 @@ export class QueryModelService implements IQueryModelService {
this._queryInfoMap = new Map<string, QueryInfo>();
this._onRunQueryStart = new Emitter<string>();
this._onRunQueryComplete = new Emitter<string>();
this._onEditSessionReady = new Emitter<EditSessionReadyParams>();
this._onEditSessionReady = new Emitter<data.EditSessionReadyParams>();
// Register Statusbar items
(<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor(
@@ -88,12 +89,12 @@ export class QueryModelService implements IQueryModelService {
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 */
));
// (<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 /////////////////////////////////////////////////////////
@@ -140,13 +141,13 @@ export class QueryModelService implements IQueryModelService {
/**
* 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> {
public getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Thenable<data.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> {
public getEditRows(uri: string, rowStart: number, numberOfRows: number): Thenable<data.EditSubsetResult> {
return this._queryInfoMap.get(uri).queryRunner.getEditRows(rowStart, numberOfRows).then(results => {
return results;
});
@@ -190,15 +191,23 @@ export class QueryModelService implements IQueryModelService {
/**
* 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 {
public runQuery(uri: string, selection: data.ISelectionData,
title: string, queryInput: QueryInput, runOptions?: data.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,
public runQueryStatement(uri: string, selection: data.ISelectionData,
title: string, queryInput: QueryInput): void {
this.doRunQuery(uri, selection, title, queryInput, true);
}
/**
* Run the current SQL statement for the given URI
*/
public runQueryString(uri: string, selection: string,
title: string, queryInput: QueryInput): void {
this.doRunQuery(uri, selection, title, queryInput, true);
}
@@ -206,9 +215,9 @@ export class QueryModelService implements IQueryModelService {
/**
* Run Query implementation
*/
private doRunQuery(uri: string, selection: ISelectionData,
private doRunQuery(uri: string, selection: data.ISelectionData | string,
title: string, queryInput: QueryInput,
runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): void {
runCurrentStatement: boolean, runOptions?: data.ExecutionPlanOptions): void {
// Reuse existing query runner if it exists
let queryRunner: QueryRunner;
let info: QueryInfo;
@@ -225,62 +234,77 @@ export class QueryModelService implements IQueryModelService {
// If the query is not in progress, we can reuse the query runner
queryRunner = existingRunner;
info.selection = [];
info.selectionSnippet = undefined;
} 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);
info = this.initQueryRunner(uri, title);
queryRunner = info.queryRunner;
}
this._getQueryInfo(uri).queryInput = queryInput;
info.queryInput = queryInput;
if (runCurrentStatement) {
if (types.isString(selection)) {
// Run the query string in this case
if (selection.length < selectionSnippetMaxLen) {
info.selectionSnippet = selection;
} else {
info.selectionSnippet = selection.substring(0, selectionSnippetMaxLen - 3) + '...';
}
queryRunner.runQuery(selection, runOptions);
} else 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);
private initQueryRunner(uri: string, title: string): QueryInfo {
let queryRunner = this._instantiationService.createInstance(QueryRunner, uri, title);
let info = new QueryInfo();
queryRunner.addListener(QREvents.RESULT_SET, e => {
this._fireQueryEvent(uri, 'resultSet', e);
});
queryRunner.eventEmitter.on('batchStart', (batch) => {
queryRunner.addListener(QREvents.BATCH_START, b => {
let link = undefined;
if (batch.selection) {
link = {
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1)
};
let messageText = LocalizedConstants.runQueryBatchStartMessage;
if (b.selection) {
if (info.selectionSnippet) {
// This indicates it's a query string. Do not include line information since it'll be inaccurate, but show some of the
// executed query text
messageText = nls.localize('runQueryStringBatchStartMessage', 'Started executing query "{0}"', info.selectionSnippet);
} else {
link = {
text: strings.format(LocalizedConstants.runQueryBatchStartLine, b.selection.startLine + 1)
};
}
}
let message = {
message: LocalizedConstants.runQueryBatchStartMessage,
batchId: batch.id,
message: messageText,
batchId: b.id,
isError: false,
time: new Date().toLocaleTimeString(),
link: link
};
this._fireQueryEvent(uri, 'message', message);
info.selection.push(this._validateSelection(batch.selection));
info.selection.push(this._validateSelection(b.selection));
});
queryRunner.eventEmitter.on('message', (message) => {
this._fireQueryEvent(uri, 'message', message);
queryRunner.addListener(QREvents.MESSAGE, m => {
this._fireQueryEvent(uri, 'message', m);
});
queryRunner.eventEmitter.on('complete', (totalMilliseconds) => {
queryRunner.addListener(QREvents.COMPLETE, totalMilliseconds => {
this._onRunQueryComplete.fire(uri);
this._fireQueryEvent(uri, 'complete', totalMilliseconds);
});
queryRunner.eventEmitter.on('start', () => {
queryRunner.addListener(QREvents.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;
return info;
}
public cancelQuery(input: QueryRunner | string): void {
@@ -312,13 +336,12 @@ export class QueryModelService implements IQueryModelService {
}
public disposeQuery(ownerUri: string): Thenable<void> {
public disposeQuery(ownerUri: string): void {
// Get existing query runner
let queryRunner = this._getQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.dispose();
queryRunner.disposeQuery();
}
return TPromise.as(null);
}
// EDIT DATA METHODS /////////////////////////////////////////////////////
@@ -341,10 +364,10 @@ export class QueryModelService implements IQueryModelService {
// 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) => {
queryRunner.addListener(QREvents.RESULT_SET, resultSet => {
this._fireQueryEvent(ownerUri, 'resultSet', resultSet);
});
queryRunner.eventEmitter.on('batchStart', (batch) => {
queryRunner.addListener(QREvents.BATCH_START, batch => {
let link = undefined;
if (batch.selection) {
link = {
@@ -361,20 +384,20 @@ export class QueryModelService implements IQueryModelService {
};
this._fireQueryEvent(ownerUri, 'message', message);
});
queryRunner.eventEmitter.on('message', (message) => {
queryRunner.addListener(QREvents.MESSAGE, message => {
this._fireQueryEvent(ownerUri, 'message', message);
});
queryRunner.eventEmitter.on('complete', (totalMilliseconds) => {
queryRunner.addListener(QREvents.COMPLETE, totalMilliseconds => {
this._onRunQueryComplete.fire(ownerUri);
this._fireQueryEvent(ownerUri, 'complete', totalMilliseconds);
});
queryRunner.eventEmitter.on('start', () => {
queryRunner.addListener(QREvents.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');
queryRunner.addListener(QREvents.EDIT_SESSION_READY, e => {
this._onEditSessionReady.fire(e);
this._fireQueryEvent(e.ownerUri, 'editSessionReady');
});
info = new QueryInfo();
@@ -399,7 +422,7 @@ export class QueryModelService implements IQueryModelService {
return TPromise.as(null);
}
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult> {
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<data.EditUpdateCellResult> {
// Get existing query runner
let queryRunner = this._getQueryRunner(ownerUri);
if (queryRunner) {
@@ -423,7 +446,7 @@ export class QueryModelService implements IQueryModelService {
return TPromise.as(null);
}
public createRow(ownerUri: string): Thenable<EditCreateRowResult> {
public createRow(ownerUri: string): Thenable<data.EditCreateRowResult> {
// Get existing query runner
let queryRunner = this._getQueryRunner(ownerUri);
if (queryRunner) {
@@ -441,7 +464,7 @@ export class QueryModelService implements IQueryModelService {
return TPromise.as(null);
}
public revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<EditRevertCellResult> {
public revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<data.EditRevertCellResult> {
// Get existing query runner
let queryRunner = this._getQueryRunner(ownerUri);
if (queryRunner) {
@@ -516,9 +539,9 @@ export class QueryModelService implements IQueryModelService {
// 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 {
private _validateSelection(selection: data.ISelectionData): data.ISelectionData {
if (!selection) {
selection = <ISelectionData>{};
selection = <data.ISelectionData>{};
}
selection.endColumn = selection ? Math.max(0, selection.endColumn) : 0;
selection.endLine = selection ? Math.max(0, selection.endLine) : 0;

View File

@@ -5,32 +5,48 @@
'use strict';
import {
BatchSummary,
QueryCancelResult,
QueryExecuteBatchNotificationParams,
QueryExecuteCompleteNotificationResult,
QueryExecuteResultSetCompleteNotificationParams,
QueryExecuteMessageParams,
QueryExecuteSubsetParams, QueryExecuteSubsetResult,
EditSubsetParams, EditSubsetResult, EditUpdateCellResult, EditCreateRowResult,
EditRevertCellResult, ISelectionData, IResultMessage, ExecutionPlanOptions
} from 'data';
import * as data 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 { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import * as types from 'vs/base/common/types';
import { EventEmitter } from 'vs/base/common/eventEmitter';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as os from 'os';
export interface IEditSessionReadyEvent {
ownerUri: string;
success: boolean;
message: string;
}
export const enum EventType {
START = 'start',
COMPLETE = 'complete',
MESSAGE = 'message',
BATCH_START = 'batchStart',
BATCH_COMPLETE = 'batchComplete',
RESULT_SET = 'resultSet',
EDIT_SESSION_READY = 'editSessionReady'
}
export interface IEventType {
start: void;
complete: string;
message: data.IResultMessage;
batchStart: data.BatchSummary;
batchComplete: data.BatchSummary;
resultSet: data.ResultSetSummary;
editSessionReady: IEditSessionReadyEvent;
}
/*
* Query Runner class which handles running a query, reports the results to the content manager,
@@ -38,57 +54,22 @@ import * as os from 'os';
*/
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();
private _totalElapsedMilliseconds: number = 0;
private _isExecuting: boolean = false;
private _hasCompleted: boolean = false;
private _batchSets: data.BatchSummary[] = [];
private _eventEmitter = new EventEmitter();
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(private _ownerUri: string,
private _editorTitle: string,
constructor(
public uri: string,
public title: 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;
}
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService,
@IClipboardService private _clipboardService: IClipboardService
) { }
get isExecuting(): boolean {
return this._isExecuting;
@@ -98,26 +79,34 @@ export default class QueryRunner {
return this._hasCompleted;
}
get batchSets(): data.BatchSummary[] {
return this._batchSets;
}
// PUBLIC METHODS ======================================================
public addListener<K extends keyof IEventType>(event: K, f: (e: IEventType[K]) => void): IDisposable {
return this._eventEmitter.addListener(event, f);
}
/**
* Cancels the running query, if there is one
*/
public cancelQuery(): Thenable<QueryCancelResult> {
return this._queryManagementService.cancelQuery(this._uri);
public cancelQuery(): Thenable<data.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>;
public runQuery(input: string, runOptions?: data.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> {
public runQuery(input: data.ISelectionData, runOptions?: data.ExecutionPlanOptions): Thenable<void>;
public runQuery(input, runOptions?: data.ExecutionPlanOptions): Thenable<void> {
return this.doRunQuery(input, false, runOptions);
}
@@ -125,7 +114,7 @@ export default class QueryRunner {
* 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> {
public runQueryStatement(input: data.ISelectionData): Thenable<void> {
return this.doRunQuery(input, true);
}
@@ -133,13 +122,13 @@ export default class QueryRunner {
* 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 = [];
private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: data.ExecutionPlanOptions): Thenable<void>;
private doRunQuery(input: data.ISelectionData, runCurrentStatement: boolean, runOptions?: data.ExecutionPlanOptions): Thenable<void>;
private doRunQuery(input, runCurrentStatement: boolean, runOptions?: data.ExecutionPlanOptions): Thenable<void> {
let ownerUri = this.uri;
this._batchSets = [];
this._hasCompleted = false;
if (typeof input === 'object' || input === undefined) {
if (types.isObject(input) || types.isUndefinedOrNull(input)) {
// Update internal state to show that we're executing the query
this._resultLineOffset = input ? input.startLine : 0;
this._isExecuting = true;
@@ -148,51 +137,50 @@ export default class QueryRunner {
// 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') {
? this._queryManagementService.runQueryStatement(ownerUri, input.startLine, input.startColumn).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e))
: this._queryManagementService.runQuery(ownerUri, input, runOptions).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
} else if (types.isString(input)) {
// 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());
return this._queryManagementService.runQueryString(ownerUri, input).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
} 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);
};
// The query has started, so lets fire up the result pane
this._eventEmitter.emit(EventType.START);
this._queryManagementService.registerRunner(this, this.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));
};
private handleFailureRunQueryResult(error: any) {
// Attempting to launch the query failed, show the error message
const eol = this.getEolString();
let message = nls.localize('query.ExecutionFailedError', 'Execution failed due to an unexpected error: {0}\t{1}', eol, error);
this.handleMessage(<data.QueryExecuteMessageParams> {
ownerUri: this.uri,
message: {
isError: true,
message: message
}
});
this.handleQueryComplete(<data.QueryExecuteCompleteNotificationResult> { ownerUri: this.uri });
}
/**
* Handle a QueryComplete from the service layer
*/
public handleQueryComplete(result: QueryExecuteCompleteNotificationResult): void {
public handleQueryComplete(result: data.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) => {
this.batchSets.map(batch => {
if (batch.selection) {
batch.selection.startLine = batch.selection.startLine + this._resultLineOffset;
batch.selection.endLine = batch.selection.endLine + this._resultLineOffset;
@@ -200,13 +188,13 @@ export default class QueryRunner {
});
// We're done with this query so shut down any waiting mechanisms
this.eventEmitter.emit('complete', Utils.parseNumAsTimeString(this._totalElapsedMilliseconds));
this._eventEmitter.emit(EventType.COMPLETE, Utils.parseNumAsTimeString(this._totalElapsedMilliseconds));
}
/**
* Handle a BatchStart from the service layer
*/
public handleBatchStart(result: QueryExecuteBatchNotificationParams): void {
public handleBatchStart(result: data.QueryExecuteBatchNotificationParams): void {
let batch = result.batchSummary;
// Recalculate the start and end lines, relative to the result line offset
@@ -219,55 +207,55 @@ export default class QueryRunner {
batch.resultSetSummaries = [];
// Store the batch
this._batchSets[batch.id] = batch;
this.eventEmitter.emit('batchStart', batch);
this.batchSets[batch.id] = batch;
this._eventEmitter.emit(EventType.BATCH_START, batch);
}
/**
* Handle a BatchComplete from the service layer
*/
public handleBatchComplete(result: QueryExecuteBatchNotificationParams): void {
let batch: BatchSummary = result.batchSummary;
public handleBatchComplete(result: data.QueryExecuteBatchNotificationParams): void {
let batch: data.BatchSummary = result.batchSummary;
// Store the batch again to get the rest of the data
this._batchSets[batch.id] = batch;
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);
this._eventEmitter.emit(EventType.BATCH_COMPLETE, batch);
}
/**
* Handle a ResultSetComplete from the service layer
*/
public handleResultSetComplete(result: QueryExecuteResultSetCompleteNotificationParams): void {
public handleResultSetComplete(result: data.QueryExecuteResultSetCompleteNotificationParams): void {
if (result && result.resultSetSummary) {
let resultSet = result.resultSetSummary;
let batchSet: BatchSummary;
let batchSet: data.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];
if (this.batchSets.length > 0) {
batchSet = this.batchSets[0];
} else {
batchSet = <BatchSummary>{
batchSet = <data.BatchSummary>{
id: 0,
selection: undefined,
hasError: false,
resultSetSummaries: []
};
this._batchSets[0] = batchSet;
this.batchSets[0] = batchSet;
}
} else {
batchSet = this._batchSets[resultSet.batchId];
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);
this._eventEmitter.emit(EventType.RESULT_SET, resultSet);
}
}
}
@@ -275,20 +263,20 @@ export default class QueryRunner {
/**
* Handle a Mssage from the service layer
*/
public handleMessage(obj: QueryExecuteMessageParams): void {
public handleMessage(obj: data.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);
this._eventEmitter.emit(EventType.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> {
public getQueryRows(rowStart: number, numberOfRows: number, batchIndex: number, resultSetIndex: number): Thenable<data.QueryExecuteSubsetResult> {
const self = this;
let rowData: QueryExecuteSubsetParams = <QueryExecuteSubsetParams>{
let rowData: data.QueryExecuteSubsetParams = <data.QueryExecuteSubsetParams>{
ownerUri: this.uri,
resultSetIndex: resultSetIndex,
rowsCount: numberOfRows,
@@ -296,7 +284,7 @@ export default class QueryRunner {
batchIndex: batchIndex
};
return new Promise<QueryExecuteSubsetResult>((resolve, reject) => {
return new Promise<data.QueryExecuteSubsetResult>((resolve, reject) => {
self._queryManagementService.getQueryRows(rowData).then(result => {
resolve(result);
}, error => {
@@ -310,8 +298,6 @@ export default class QueryRunner {
* 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;
@@ -319,15 +305,15 @@ export default class QueryRunner {
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);
this._eventEmitter.emit(EventType.START);
this._queryManagementService.registerRunner(this, ownerUri);
}, error => {
// Attempting to launch the query failed, show the error message
// TODO issue #228 add statusview callbacks here
self._isExecuting = false;
this._isExecuting = false;
self._messageService.show(Severity.Error, nls.localize('query.initEditExecutionFailed', 'Init Edit Execution failed: ') + error);
this._messageService.show(Severity.Error, nls.localize('query.initEditExecutionFailed', 'Init Edit Execution failed: ') + error);
});
}
@@ -336,15 +322,15 @@ export default class QueryRunner {
* @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> {
public getEditRows(rowStart: number, numberOfRows: number): Thenable<data.EditSubsetResult> {
const self = this;
let rowData: EditSubsetParams = {
let rowData: data.EditSubsetParams = {
ownerUri: this.uri,
rowCount: numberOfRows,
rowStartIndex: rowStart
};
return new Promise<EditSubsetResult>((resolve, reject) => {
return new Promise<data.EditSubsetResult>((resolve, reject) => {
self._queryManagementService.getEditRows(rowData).then(result => {
if (!result.hasOwnProperty('rowCount')) {
let error = `Nothing returned from subset query`;
@@ -361,10 +347,10 @@ export default class QueryRunner {
}
public handleEditSessionReady(ownerUri: string, success: boolean, message: string): void {
this.eventEmitter.emit('editSessionReady', ownerUri, success, message);
this._eventEmitter.emit(EventType.EDIT_SESSION_READY, { ownerUri, success, message });
}
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult> {
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<data.EditUpdateCellResult> {
return this._queryManagementService.updateCell(ownerUri, rowId, columnId, newValue);
}
@@ -372,7 +358,7 @@ export default class QueryRunner {
return this._queryManagementService.commitEdit(ownerUri);
}
public createRow(ownerUri: string): Thenable<EditCreateRowResult> {
public createRow(ownerUri: string): Thenable<data.EditCreateRowResult> {
return this._queryManagementService.createRow(ownerUri).then(result => {
return result;
});
@@ -382,7 +368,7 @@ export default class QueryRunner {
return this._queryManagementService.deleteRow(ownerUri, rowId);
}
public revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<EditRevertCellResult> {
public revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<data.EditRevertCellResult> {
return this._queryManagementService.revertCell(ownerUri, rowId, columnId).then(result => {
return result;
});
@@ -400,16 +386,8 @@ export default class QueryRunner {
* 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);
});
});
public disposeQuery(): void {
this._queryManagementService.disposeQuery(this.uri);
}
get totalElapsedMilliseconds(): number {
@@ -426,6 +404,7 @@ export default class QueryRunner {
copyResults(selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void {
const self = this;
let copyString = '';
const eol = this.getEolString();
// create a mapping of the ranges to get promises
let tasks = selection.map((range, i) => {
@@ -434,7 +413,7 @@ export default class QueryRunner {
if (self.shouldIncludeHeaders(includeHeaders)) {
let columnHeaders = self.getColumnHeaders(batchId, resultId, range);
if (columnHeaders !== undefined) {
copyString += columnHeaders.join('\t') + os.EOL;
copyString += columnHeaders.join('\t') + eol;
}
}
@@ -448,7 +427,7 @@ export default class QueryRunner {
: cellObjects.map(x => x.displayValue);
copyString += cells.join('\t');
if (rowIndex < result.resultSubset.rows.length - 1) {
copyString += os.EOL;
copyString += eol;
}
}
});
@@ -461,11 +440,16 @@ export default class QueryRunner {
p = p.then(tasks[i]);
}
p.then(() => {
WorkbenchUtils.executeCopy(copyString);
this._clipboardService.writeText(copyString);
});
}
}
private getEolString(): string {
const { eol } = this._workspaceConfigurationService.getConfiguration<{ eol: string }>('files');
return eol;
}
private shouldIncludeHeaders(includeHeaders: boolean): boolean {
if (includeHeaders !== undefined) {
// Respect the value explicity passed into the method
@@ -484,7 +468,7 @@ export default class QueryRunner {
private getColumnHeaders(batchId: number, resultId: number, range: ISlickRange): string[] {
let headers: string[] = undefined;
let batchSummary: BatchSummary = this.batchSets[batchId];
let batchSummary: data.BatchSummary = this.batchSets[batchId];
if (batchSummary !== undefined) {
let resultSetSummary = batchSummary.resultSetSummaries[resultId];
headers = resultSetSummary.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => {
@@ -511,15 +495,14 @@ export default class QueryRunner {
// get config copyRemoveNewLine option from vscode config
let showBatchTime: boolean = WorkbenchUtils.getSqlConfigValue<boolean>(this._workspaceConfigurationService, Constants.configShowBatchTime);
if (showBatchTime) {
let message: IResultMessage = {
let message: data.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);
this._eventEmitter.emit(EventType.MESSAGE, message);
}
}
}

View File

@@ -3,27 +3,33 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
import { IConnectableInput } from 'sql/parts/connection/common/connectionManagement';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IQueryEditorService, IQueryEditorOptions } from 'sql/parts/query/common/queryEditorService';
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
import { sqlModeId, untitledFilePrefix, getSupportedInputResource } from 'sql/parts/common/customInputConverter';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { IMode } from 'vs/editor/common/modes';
import { IModel } from 'vs/editor/common/editorCommon';
import { IEditor, IEditorInput, Position } from 'vs/platform/editor/common/editor';
import { CodeEditor } from 'vs/editor/browser/codeEditor';
import { IEditorGroup } from 'vs/workbench/common/editor';
import { IUntitledEditorService, UNTITLED_SCHEMA } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput';
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
import URI from 'vs/base/common/uri';
import { IConnectableInput } from 'sql/parts/connection/common/connectionManagement';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IMode } from 'vs/editor/common/modes';
import { IModel } from 'vs/editor/common/editorCommon';
import { IEditor, IEditorInput, Position } from 'vs/platform/editor/common/editor';
import { CodeEditor } from 'vs/editor/browser/codeEditor';
import { IQueryEditorService, IQueryEditorOptions } from 'sql/parts/query/common/queryEditorService';
import { indexOf } from 'vs/platform/files/common/files';
import { IMessageService } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
import nls = require('vs/nls');
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
import { sqlModeId, untitledFilePrefix, getSupportedInputResource } from 'sql/parts/common/customInputConverter';
import URI from 'vs/base/common/uri';
import paths = require('vs/base/common/paths');
import { isLinux } from 'vs/base/common/platform';
const fs = require('fs');
@@ -56,6 +62,7 @@ export class QueryEditorService implements IQueryEditorService {
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
@IEditorGroupService private _editorGroupService: IEditorGroupService,
@IMessageService private _messageService: IMessageService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
) {
QueryEditorService.editorService = _editorService;
QueryEditorService.instantiationService = _instantiationService;
@@ -145,6 +152,31 @@ export class QueryEditorService implements IQueryEditorService {
public onQueryInputClosed(uri: string): void {
}
onSaveAsCompleted(oldResource: URI, newResource: URI): void {
let oldResourceString: string = oldResource.toString();
const stacks = this._editorGroupService.getStacksModel();
stacks.groups.forEach(group => {
group.getEditors().forEach(input => {
if (input instanceof QueryInput) {
const resource = input.getResource();
// Update Editor if file (or any parent of the input) got renamed or moved
// Note: must check the new file name for this since this method is called after the rename is completed
if (paths.isEqualOrParent(resource.fsPath, newResource.fsPath, !isLinux /* ignorecase */)) {
// In this case, we know that this is a straight rename so support this as a rename / replace operation
TaskUtilities.replaceConnection(oldResourceString, newResource.toString(), this._connectionManagementService).then(result => {
if (result && result.connected) {
input.onConnectSuccess();
} else {
input.onConnectReject();
}
});
}
}
});
});
}
////// Public static functions
// These functions are static to reduce extra lines needed in the vscode code base