mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
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:
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 */
|
||||
|
||||
8
src/sql/parts/query/common/media/flavorStatus.css
Normal file
8
src/sql/parts/query/common/media/flavorStatus.css
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user