mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-20 01:25:37 -05:00
More layering and compile strictness (#8973)
* add more folders to strictire compile, add more strict compile options * update ci * wip * add more layering and fix issues * add more strictness * remove unnecessary assertion * add missing checks * fix indentation * remove jsdoc
This commit is contained in:
@@ -1,141 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
|
||||
|
||||
export interface IGridDataProvider {
|
||||
|
||||
/**
|
||||
* Gets N rows of data
|
||||
* @param rowStart 0-indexed start row to retrieve data from
|
||||
* @param numberOfRows total number of rows of data to retrieve
|
||||
*/
|
||||
getRowData(rowStart: number, numberOfRows: number): Thenable<azdata.QueryExecuteSubsetResult>;
|
||||
|
||||
/**
|
||||
* Sends a copy request to copy data to the clipboard
|
||||
* @param selection The selection range to copy
|
||||
* @param batchId The batch id of the result to copy from
|
||||
* @param resultId The result id of the result to copy from
|
||||
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
||||
*/
|
||||
copyResults(selection: Slick.Range[], includeHeaders?: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Gets the EOL terminator to use for this data type.
|
||||
*/
|
||||
getEolString(): string;
|
||||
|
||||
shouldIncludeHeaders(includeHeaders: boolean): boolean;
|
||||
|
||||
shouldRemoveNewLines(): boolean;
|
||||
|
||||
getColumnHeaders(range: Slick.Range): string[] | undefined;
|
||||
|
||||
readonly canSerialize: boolean;
|
||||
|
||||
serializeResults(format: SaveFormat, selection: Slick.Range[]): Thenable<void>;
|
||||
|
||||
}
|
||||
|
||||
export async function getResultsString(provider: IGridDataProvider, selection: Slick.Range[], includeHeaders?: boolean): Promise<string> {
|
||||
let headers: Map<number, string> = new Map(); // Maps a column index -> header
|
||||
let rows: Map<number, Map<number, string>> = new Map(); // Maps row index -> column index -> actual row value
|
||||
const eol = provider.getEolString();
|
||||
|
||||
// create a mapping of the ranges to get promises
|
||||
let tasks: (() => Promise<void>)[] = selection.map((range) => {
|
||||
return async (): Promise<void> => {
|
||||
let startCol = range.fromCell;
|
||||
let startRow = range.fromRow;
|
||||
|
||||
const result = await provider.getRowData(range.fromRow, range.toRow - range.fromRow + 1);
|
||||
// If there was a previous selection separate it with a line break. Currently
|
||||
// when there are multiple selections they are never on the same line
|
||||
let columnHeaders = provider.getColumnHeaders(range);
|
||||
if (columnHeaders !== undefined) {
|
||||
let idx = 0;
|
||||
for (let header of columnHeaders) {
|
||||
headers.set(startCol + idx, header);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
// Iterate over the rows to paste into the copy string
|
||||
for (let rowIndex: number = 0; rowIndex < result.resultSubset.rows.length; rowIndex++) {
|
||||
let row = result.resultSubset.rows[rowIndex];
|
||||
let cellObjects = row.slice(range.fromCell, (range.toCell + 1));
|
||||
// Remove newlines if requested
|
||||
let cells = provider.shouldRemoveNewLines()
|
||||
? cellObjects.map(x => removeNewLines(x.displayValue))
|
||||
: cellObjects.map(x => x.displayValue);
|
||||
|
||||
let idx = 0;
|
||||
for (let cell of cells) {
|
||||
let map = rows.get(rowIndex + startRow);
|
||||
if (!map) {
|
||||
map = new Map();
|
||||
rows.set(rowIndex + startRow, map);
|
||||
}
|
||||
|
||||
map.set(startCol + idx, cell);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Set the tasks gathered above to execute
|
||||
let actionedTasks: Promise<void>[] = tasks.map(t => { return t(); });
|
||||
|
||||
// Make sure all these tasks have executed
|
||||
await Promise.all(actionedTasks);
|
||||
|
||||
const sortResults = (e1: [number, any], e2: [number, any]) => {
|
||||
return e1[0] - e2[0];
|
||||
};
|
||||
headers = new Map([...headers].sort(sortResults));
|
||||
rows = new Map([...rows].sort(sortResults));
|
||||
|
||||
let copyString = '';
|
||||
if (includeHeaders) {
|
||||
copyString = [...headers.values()].join('\t').concat(eol);
|
||||
}
|
||||
|
||||
const rowKeys = [...headers.keys()];
|
||||
for (let rowEntry of rows) {
|
||||
let rowMap = rowEntry[1];
|
||||
for (let rowIdx of rowKeys) {
|
||||
|
||||
let value = rowMap.get(rowIdx);
|
||||
if (value) {
|
||||
copyString = copyString.concat(value);
|
||||
}
|
||||
copyString = copyString.concat('\t');
|
||||
}
|
||||
// Removes the tab seperator from the end of a row
|
||||
copyString = copyString.slice(0, -1 * '\t'.length);
|
||||
copyString = copyString.concat(eol);
|
||||
}
|
||||
// Removes EoL from the end of the result
|
||||
copyString = copyString.slice(0, -1 * eol.length);
|
||||
|
||||
return copyString;
|
||||
}
|
||||
|
||||
|
||||
function removeNewLines(inputString: string): string {
|
||||
// This regex removes all newlines in all OS types
|
||||
// Windows(CRLF): \r\n
|
||||
// Linux(LF)/Modern MacOS: \n
|
||||
// Old MacOs: \r
|
||||
if (types.isUndefinedOrNull(inputString)) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
let outputString: string = inputString.replace(/(\r\n|\n|\r)/gm, '');
|
||||
return outputString;
|
||||
}
|
||||
@@ -1,352 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import QueryRunner from 'sql/platform/query/common/queryRunner';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as azdata from 'azdata';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { keys } from 'vs/base/common/map';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { IAdsTelemetryService, ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry';
|
||||
|
||||
export const SERVICE_ID = 'queryManagementService';
|
||||
|
||||
export const IQueryManagementService = createDecorator<IQueryManagementService>(SERVICE_ID);
|
||||
|
||||
export interface IQueryManagementService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
onHandlerAdded: Event<string>;
|
||||
|
||||
addQueryRequestHandler(queryType: string, runner: IQueryRequestHandler): IDisposable;
|
||||
isProviderRegistered(providerId: string): boolean;
|
||||
getRegisteredProviders(): string[];
|
||||
registerRunner(runner: QueryRunner, uri: string): void;
|
||||
|
||||
cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult>;
|
||||
runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
|
||||
runQueryStatement(ownerUri: string, line: number, column: number): Promise<void>;
|
||||
runQueryString(ownerUri: string, queryString: string): Promise<void>;
|
||||
runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult>;
|
||||
parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult>;
|
||||
getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<azdata.QueryExecuteSubsetResult>;
|
||||
disposeQuery(ownerUri: string): Promise<void>;
|
||||
saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult>;
|
||||
setQueryExecutionOptions(uri: string, options: azdata.QueryExecutionOptions): Promise<void>;
|
||||
|
||||
// Callbacks
|
||||
onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void;
|
||||
onBatchStart(batchInfo: azdata.QueryExecuteBatchNotificationParams): void;
|
||||
onBatchComplete(batchInfo: azdata.QueryExecuteBatchNotificationParams): void;
|
||||
onResultSetAvailable(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void;
|
||||
onResultSetUpdated(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void;
|
||||
onMessage(message: azdata.QueryExecuteMessageParams): void;
|
||||
|
||||
// Edit Data Callbacks
|
||||
onEditSessionReady(ownerUri: string, success: boolean, message: string): void;
|
||||
|
||||
// Edit Data Functions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void>;
|
||||
disposeEdit(ownerUri: string): Promise<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult>;
|
||||
commitEdit(ownerUri: string): Promise<void>;
|
||||
createRow(ownerUri: string): Promise<azdata.EditCreateRowResult>;
|
||||
deleteRow(ownerUri: string, rowId: number): Promise<void>;
|
||||
revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult>;
|
||||
revertRow(ownerUri: string, rowId: number): Promise<void>;
|
||||
getEditRows(rowData: azdata.EditSubsetParams): Promise<azdata.EditSubsetResult>;
|
||||
}
|
||||
|
||||
/*
|
||||
* An object that can handle basic request-response actions related to queries
|
||||
*/
|
||||
export interface IQueryRequestHandler {
|
||||
cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult>;
|
||||
runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
|
||||
runQueryStatement(ownerUri: string, line: number, column: number): Promise<void>;
|
||||
runQueryString(ownerUri: string, queryString: string): Promise<void>;
|
||||
runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult>;
|
||||
parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult>;
|
||||
getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<azdata.QueryExecuteSubsetResult>;
|
||||
disposeQuery(ownerUri: string): Promise<void>;
|
||||
saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult>;
|
||||
setQueryExecutionOptions(ownerUri: string, options: azdata.QueryExecutionOptions): Promise<void>;
|
||||
|
||||
// Edit Data actions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void>;
|
||||
disposeEdit(ownerUri: string): Promise<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult>;
|
||||
commitEdit(ownerUri: string): Promise<void>;
|
||||
createRow(ownerUri: string): Promise<azdata.EditCreateRowResult>;
|
||||
deleteRow(ownerUri: string, rowId: number): Promise<void>;
|
||||
revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult>;
|
||||
revertRow(ownerUri: string, rowId: number): Promise<void>;
|
||||
getEditRows(rowData: azdata.EditSubsetParams): Promise<azdata.EditSubsetResult>;
|
||||
}
|
||||
|
||||
export class QueryManagementService implements IQueryManagementService {
|
||||
public _serviceBrand: undefined;
|
||||
|
||||
private _requestHandlers = new Map<string, IQueryRequestHandler>();
|
||||
private _onHandlerAddedEmitter = new Emitter<string>();
|
||||
// public for testing only
|
||||
public _queryRunners = new Map<string, QueryRunner>();
|
||||
|
||||
// public for testing only
|
||||
public _handlerCallbackQueue: ((run: QueryRunner) => void)[] = [];
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService,
|
||||
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
|
||||
) {
|
||||
}
|
||||
|
||||
// Registers queryRunners with their uris to distribute notifications.
|
||||
// Ensures that notifications are handled in the correct order by handling
|
||||
// enqueued handlers first.
|
||||
// public for testing only
|
||||
public registerRunner(runner: QueryRunner, uri: string): void {
|
||||
// If enqueueOrRun was called before registerRunner for the current query,
|
||||
// _handlerCallbackQueue will be non-empty. Run all handlers in the queue first
|
||||
// so that notifications are handled in order they arrived
|
||||
while (this._handlerCallbackQueue.length > 0) {
|
||||
let handler = this._handlerCallbackQueue.shift()!;
|
||||
handler(runner);
|
||||
}
|
||||
|
||||
// Set the runner for any other handlers if the runner is in use by the
|
||||
// current query or a subsequent query
|
||||
if (!runner.hasCompleted) {
|
||||
this._queryRunners.set(uri, runner);
|
||||
}
|
||||
}
|
||||
|
||||
// Handles logic to run the given handlerCallback at the appropriate time. If the given runner is
|
||||
// undefined, the handlerCallback is put on the _handlerCallbackQueue to be run once the runner is set
|
||||
// public for testing only
|
||||
private enqueueOrRun(handlerCallback: (runnerParam: QueryRunner) => void, runner: QueryRunner): void {
|
||||
if (runner === undefined) {
|
||||
this._handlerCallbackQueue.push(handlerCallback);
|
||||
} else {
|
||||
handlerCallback(runner);
|
||||
}
|
||||
}
|
||||
|
||||
private _notify(ownerUri: string, sendNotification: (runner: QueryRunner) => void): void {
|
||||
let runner = this._queryRunners.get(ownerUri);
|
||||
this.enqueueOrRun(sendNotification, runner!);
|
||||
}
|
||||
|
||||
public addQueryRequestHandler(queryType: string, handler: IQueryRequestHandler): IDisposable {
|
||||
this._requestHandlers.set(queryType, handler);
|
||||
this._onHandlerAddedEmitter.fire(queryType);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public get onHandlerAdded(): Event<string> {
|
||||
return this._onHandlerAddedEmitter.event;
|
||||
}
|
||||
|
||||
public isProviderRegistered(providerId: string): boolean {
|
||||
let handler = this._requestHandlers.get(providerId);
|
||||
return !!handler;
|
||||
}
|
||||
|
||||
public getRegisteredProviders(): string[] {
|
||||
return Array.from(keys(this._requestHandlers));
|
||||
}
|
||||
|
||||
private addTelemetry(eventName: string, ownerUri: string, runOptions?: azdata.ExecutionPlanOptions): void {
|
||||
const providerId: string = this._connectionService.getProviderIdFromUri(ownerUri);
|
||||
const data: ITelemetryEventProperties = {
|
||||
provider: providerId,
|
||||
};
|
||||
if (runOptions) {
|
||||
assign(data, {
|
||||
displayEstimatedQueryPlan: runOptions.displayEstimatedQueryPlan,
|
||||
displayActualQueryPlan: runOptions.displayActualQueryPlan
|
||||
});
|
||||
}
|
||||
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, eventName).withAdditionalProperties(data).send();
|
||||
}
|
||||
|
||||
private _runAction<T>(uri: string, action: (handler: IQueryRequestHandler) => Promise<T>, fallBackToDefaultProvider: boolean = false): Promise<T> {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
|
||||
|
||||
if (!providerId && fallBackToDefaultProvider) {
|
||||
providerId = this._connectionService.getDefaultProviderId();
|
||||
}
|
||||
|
||||
if (!providerId) {
|
||||
return Promise.reject(new Error('Connection is required in order to interact with queries'));
|
||||
}
|
||||
let handler = this._requestHandlers.get(providerId);
|
||||
if (handler) {
|
||||
return action(handler);
|
||||
} else {
|
||||
return Promise.reject(new Error('No Handler Registered'));
|
||||
}
|
||||
}
|
||||
|
||||
public cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult> {
|
||||
this.addTelemetry(TelemetryKeys.CancelQuery, ownerUri);
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.cancelQuery(ownerUri);
|
||||
});
|
||||
}
|
||||
public runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
|
||||
this.addTelemetry(TelemetryKeys.RunQuery, ownerUri, runOptions);
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.runQuery(ownerUri, selection, runOptions);
|
||||
});
|
||||
}
|
||||
public runQueryStatement(ownerUri: string, line: number, column: number): Promise<void> {
|
||||
this.addTelemetry(TelemetryKeys.RunQueryStatement, ownerUri);
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.runQueryStatement(ownerUri, line, column);
|
||||
});
|
||||
}
|
||||
public runQueryString(ownerUri: string, queryString: string): Promise<void> {
|
||||
this.addTelemetry(TelemetryKeys.RunQueryString, ownerUri);
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.runQueryString(ownerUri, queryString);
|
||||
});
|
||||
}
|
||||
public runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.runQueryAndReturn(ownerUri, queryString);
|
||||
});
|
||||
}
|
||||
public parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.parseSyntax(ownerUri, query);
|
||||
});
|
||||
}
|
||||
public getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<azdata.QueryExecuteSubsetResult> {
|
||||
return this._runAction(rowData.ownerUri, (runner) => {
|
||||
return runner.getQueryRows(rowData);
|
||||
});
|
||||
}
|
||||
public disposeQuery(ownerUri: string): Promise<void> {
|
||||
this._queryRunners.delete(ownerUri);
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.disposeQuery(ownerUri);
|
||||
});
|
||||
}
|
||||
public setQueryExecutionOptions(ownerUri: string, options: azdata.QueryExecutionOptions): Promise<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.setQueryExecutionOptions(ownerUri, options);
|
||||
}, true);
|
||||
}
|
||||
|
||||
public saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult> {
|
||||
return this._runAction(requestParams.ownerUri, (runner) => {
|
||||
return runner.saveResults(requestParams);
|
||||
});
|
||||
}
|
||||
|
||||
public onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void {
|
||||
this._notify(result.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleQueryComplete(result);
|
||||
});
|
||||
}
|
||||
public onBatchStart(batchInfo: azdata.QueryExecuteBatchNotificationParams): void {
|
||||
this._notify(batchInfo.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleBatchStart(batchInfo);
|
||||
});
|
||||
}
|
||||
|
||||
public onBatchComplete(batchInfo: azdata.QueryExecuteBatchNotificationParams): void {
|
||||
this._notify(batchInfo.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleBatchComplete(batchInfo);
|
||||
});
|
||||
}
|
||||
|
||||
public onResultSetAvailable(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void {
|
||||
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleResultSetAvailable(resultSetInfo);
|
||||
});
|
||||
}
|
||||
|
||||
public onResultSetUpdated(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void {
|
||||
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleResultSetUpdated(resultSetInfo);
|
||||
});
|
||||
}
|
||||
|
||||
public onMessage(message: azdata.QueryExecuteMessageParams): void {
|
||||
this._notify(message.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
// Edit Data Functions
|
||||
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
|
||||
});
|
||||
}
|
||||
|
||||
public onEditSessionReady(ownerUri: string, success: boolean, message: string): void {
|
||||
this._notify(ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleEditSessionReady(ownerUri, success, message);
|
||||
});
|
||||
}
|
||||
|
||||
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.updateCell(ownerUri, rowId, columnId, newValue);
|
||||
});
|
||||
}
|
||||
|
||||
public commitEdit(ownerUri: string): Promise<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.commitEdit(ownerUri);
|
||||
});
|
||||
}
|
||||
|
||||
public createRow(ownerUri: string): Promise<azdata.EditCreateRowResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.createRow(ownerUri);
|
||||
});
|
||||
}
|
||||
|
||||
public deleteRow(ownerUri: string, rowId: number): Promise<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.deleteRow(ownerUri, rowId);
|
||||
});
|
||||
}
|
||||
|
||||
public disposeEdit(ownerUri: string): Promise<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.disposeEdit(ownerUri);
|
||||
});
|
||||
}
|
||||
|
||||
public revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.revertCell(ownerUri, rowId, columnId);
|
||||
});
|
||||
}
|
||||
|
||||
public revertRow(ownerUri: string, rowId: number): Promise<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.revertRow(ownerUri, rowId);
|
||||
});
|
||||
}
|
||||
|
||||
public getEditRows(rowData: azdata.EditSubsetParams): Promise<azdata.EditSubsetResult> {
|
||||
return this._runAction(rowData.ownerUri, (runner) => {
|
||||
return runner.getEditRows(rowData);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import QueryRunner, { IQueryMessage } from 'sql/platform/query/common/queryRunner';
|
||||
import { DataService } from 'sql/workbench/contrib/grid/common/dataService';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
|
||||
import {
|
||||
ISelectionData,
|
||||
ResultSetSubset,
|
||||
EditUpdateCellResult,
|
||||
EditSessionReadyParams,
|
||||
EditSubsetResult,
|
||||
EditCreateRowResult,
|
||||
EditRevertCellResult,
|
||||
ExecutionPlanOptions,
|
||||
queryeditor
|
||||
} from 'azdata';
|
||||
import { QueryInfo } from 'sql/platform/query/common/queryModelService';
|
||||
|
||||
export const SERVICE_ID = 'queryModelService';
|
||||
|
||||
export const IQueryModelService = createDecorator<IQueryModelService>(SERVICE_ID);
|
||||
|
||||
export interface IQueryPlanInfo {
|
||||
providerId: string;
|
||||
fileUri: string;
|
||||
planXml: string;
|
||||
}
|
||||
|
||||
export interface IQueryInfo {
|
||||
selection: ISelectionData[];
|
||||
messages: IQueryMessage[];
|
||||
}
|
||||
|
||||
export interface IQueryEvent {
|
||||
type: queryeditor.QueryEventType;
|
||||
uri: string;
|
||||
queryInfo: IQueryInfo;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the logic of handling running queries and grid interactions for all URIs.
|
||||
*/
|
||||
export interface IQueryModelService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
getQueryRunner(uri: string): QueryRunner | undefined;
|
||||
|
||||
getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<ResultSetSubset | undefined>;
|
||||
runQuery(uri: string, selection: ISelectionData | undefined, queryInput: QueryEditorInput, runOptions?: ExecutionPlanOptions): void;
|
||||
runQueryStatement(uri: string, selection: ISelectionData | undefined, queryInput: QueryEditorInput): void;
|
||||
runQueryString(uri: string, selection: string | undefined, queryInput: QueryEditorInput): void;
|
||||
cancelQuery(input: QueryRunner | string): void;
|
||||
disposeQuery(uri: string): void;
|
||||
isRunningQuery(uri: string): boolean;
|
||||
|
||||
getDataService(uri: string): DataService;
|
||||
refreshResultsets(uri: string): void;
|
||||
sendGridContentEvent(uri: string, eventName: string): void;
|
||||
resizeResultsets(uri: string): void;
|
||||
onLoaded(uri: string): void;
|
||||
|
||||
copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void;
|
||||
showCommitError(error: string): void;
|
||||
|
||||
onRunQueryStart: Event<string>;
|
||||
onRunQueryUpdate: Event<string>;
|
||||
onRunQueryComplete: Event<string>;
|
||||
onQueryEvent: Event<IQueryEvent>;
|
||||
|
||||
// Edit Data Functions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void;
|
||||
disposeEdit(ownerUri: string): Promise<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<EditUpdateCellResult | undefined>;
|
||||
commitEdit(ownerUri: string): Promise<void>;
|
||||
createRow(ownerUri: string): Promise<EditCreateRowResult | undefined>;
|
||||
deleteRow(ownerUri: string, rowId: number): Promise<void>;
|
||||
revertCell(ownerUri: string, rowId: number, columnId: number): Promise<EditRevertCellResult | undefined>;
|
||||
revertRow(ownerUri: string, rowId: number): Promise<void>;
|
||||
getEditRows(ownerUri: string, rowStart: number, numberOfRows: number): Promise<EditSubsetResult | undefined>;
|
||||
|
||||
_getQueryInfo(uri: string): QueryInfo | undefined;
|
||||
// Edit Data Callbacks
|
||||
onEditSessionReady: Event<EditSessionReadyParams>;
|
||||
}
|
||||
@@ -1,669 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as GridContentEvents from 'sql/workbench/contrib/grid/common/gridContentEvents';
|
||||
import * as LocalizedConstants from 'sql/workbench/contrib/query/common/localizedConstants';
|
||||
import QueryRunner from 'sql/platform/query/common/queryRunner';
|
||||
import { DataService } from 'sql/workbench/contrib/grid/common/dataService';
|
||||
import { IQueryModelService, IQueryEvent } from 'sql/platform/query/common/queryModel';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
|
||||
|
||||
const selectionSnippetMaxLen = 100;
|
||||
|
||||
export interface QueryEvent {
|
||||
type: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds information about the state of a query runner
|
||||
*/
|
||||
export class QueryInfo {
|
||||
public queryRunner: QueryRunner;
|
||||
public dataService: DataService;
|
||||
public queryEventQueue: QueryEvent[];
|
||||
public selection: Array<azdata.ISelectionData>;
|
||||
public queryInput: QueryEditorInput;
|
||||
public selectionSnippet?: string;
|
||||
|
||||
// Notes if the angular components have obtained the DataService. If not, all messages sent
|
||||
// via the data service will be lost.
|
||||
public dataServiceReady: boolean;
|
||||
|
||||
constructor() {
|
||||
this.dataServiceReady = false;
|
||||
this.queryEventQueue = [];
|
||||
this.selection = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles running queries and grid interactions for all URIs. Interacts with each URI's results grid via a DataService instance
|
||||
*/
|
||||
export class QueryModelService implements IQueryModelService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
||||
private _queryInfoMap: Map<string, QueryInfo>;
|
||||
private _onRunQueryStart: Emitter<string>;
|
||||
private _onRunQueryUpdate: Emitter<string>;
|
||||
private _onRunQueryComplete: Emitter<string>;
|
||||
private _onQueryEvent: Emitter<IQueryEvent>;
|
||||
private _onEditSessionReady: Emitter<azdata.EditSessionReadyParams>;
|
||||
|
||||
// EVENTS /////////////////////////////////////////////////////////////
|
||||
public get onRunQueryStart(): Event<string> { return this._onRunQueryStart.event; }
|
||||
public get onRunQueryUpdate(): Event<string> { return this._onRunQueryUpdate.event; }
|
||||
public get onRunQueryComplete(): Event<string> { return this._onRunQueryComplete.event; }
|
||||
public get onQueryEvent(): Event<IQueryEvent> { return this._onQueryEvent.event; }
|
||||
public get onEditSessionReady(): Event<azdata.EditSessionReadyParams> { return this._onEditSessionReady.event; }
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@INotificationService private _notificationService: INotificationService
|
||||
) {
|
||||
this._queryInfoMap = new Map<string, QueryInfo>();
|
||||
this._onRunQueryStart = new Emitter<string>();
|
||||
this._onRunQueryUpdate = new Emitter<string>();
|
||||
this._onRunQueryComplete = new Emitter<string>();
|
||||
this._onQueryEvent = new Emitter<IQueryEvent>();
|
||||
this._onEditSessionReady = new Emitter<azdata.EditSessionReadyParams>();
|
||||
}
|
||||
|
||||
// IQUERYMODEL /////////////////////////////////////////////////////////
|
||||
public getDataService(uri: string): DataService {
|
||||
let dataService: DataService | undefined;
|
||||
if (this._queryInfoMap.has(uri)) {
|
||||
dataService = this._getQueryInfo(uri)!.dataService;
|
||||
}
|
||||
if (!dataService) {
|
||||
throw new Error('Could not find data service for uri: ' + uri);
|
||||
}
|
||||
|
||||
return dataService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force all grids to re-render. This is needed to re-render the grids when switching
|
||||
* between different URIs.
|
||||
*/
|
||||
public refreshResultsets(uri: string): void {
|
||||
this._fireGridContentEvent(uri, GridContentEvents.RefreshContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the grid UI to fit the current screen size.
|
||||
*/
|
||||
public resizeResultsets(uri: string): void {
|
||||
this._fireGridContentEvent(uri, GridContentEvents.ResizeContents);
|
||||
}
|
||||
|
||||
public sendGridContentEvent(uri: string, eventName: string): void {
|
||||
this._fireGridContentEvent(uri, eventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called by a component's DataService when the component has finished loading.
|
||||
* Sends all previously enqueued query events to the DataService and signals to stop enqueuing
|
||||
* any further events.
|
||||
*/
|
||||
public onLoaded(uri: string) {
|
||||
if (this._queryInfoMap.has(uri)) {
|
||||
let info = this._getQueryInfo(uri)!;
|
||||
info.dataServiceReady = true;
|
||||
this._sendQueuedEvents(uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more data rows from the current resultSets from the service layer
|
||||
*/
|
||||
public getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<azdata.ResultSetSubset | undefined> {
|
||||
if (this._queryInfoMap.has(uri)) {
|
||||
return this._getQueryInfo(uri)!.queryRunner.getQueryRows(rowStart, numberOfRows, batchId, resultId).then(results => {
|
||||
return results.resultSubset;
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
public getEditRows(uri: string, rowStart: number, numberOfRows: number): Promise<azdata.EditSubsetResult | undefined> {
|
||||
if (this._queryInfoMap.has(uri)) {
|
||||
return this._queryInfoMap.get(uri)!.queryRunner.getEditRows(rowStart, numberOfRows).then(results => {
|
||||
return results;
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
public async copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): Promise<void> {
|
||||
if (this._queryInfoMap.has(uri)) {
|
||||
return this._queryInfoMap.get(uri)!.queryRunner.copyResults(selection, batchId, resultId, includeHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
public showCommitError(error: string): void {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize('commitEditFailed', "Commit row failed: ") + error
|
||||
});
|
||||
}
|
||||
|
||||
public isRunningQuery(uri: string): boolean {
|
||||
return !this._queryInfoMap.has(uri)
|
||||
? false
|
||||
: this._getQueryInfo(uri)!.queryRunner.isExecuting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a query for the given URI with the given text selection
|
||||
*/
|
||||
public async runQuery(uri: string, selection: azdata.ISelectionData, queryInput: QueryEditorInput, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
|
||||
return this.doRunQuery(uri, selection, queryInput, false, runOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the current SQL statement for the given URI
|
||||
*/
|
||||
public async runQueryStatement(uri: string, selection: azdata.ISelectionData, queryInput: QueryEditorInput): Promise<void> {
|
||||
return this.doRunQuery(uri, selection, queryInput, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the current SQL statement for the given URI
|
||||
*/
|
||||
public async runQueryString(uri: string, selection: string, queryInput: QueryEditorInput): Promise<void> {
|
||||
return this.doRunQuery(uri, selection, queryInput, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Query implementation
|
||||
*/
|
||||
private async doRunQuery(uri: string, selection: azdata.ISelectionData | string, queryInput: QueryEditorInput,
|
||||
runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
|
||||
// Reuse existing query runner if it exists
|
||||
let queryRunner: QueryRunner | undefined;
|
||||
let info: QueryInfo;
|
||||
|
||||
if (this._queryInfoMap.has(uri)) {
|
||||
info = this._getQueryInfo(uri)!;
|
||||
let existingRunner: QueryRunner = info.queryRunner;
|
||||
|
||||
// If the query is already in progress, don't attempt to send it
|
||||
if (existingRunner.isExecuting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the query is not in progress, we can reuse the query runner
|
||||
queryRunner = existingRunner;
|
||||
info.selection = [];
|
||||
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 = this.initQueryRunner(uri);
|
||||
queryRunner = info.queryRunner;
|
||||
}
|
||||
|
||||
info.queryInput = queryInput;
|
||||
|
||||
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) + '...';
|
||||
}
|
||||
return queryRunner.runQuery(selection, runOptions);
|
||||
} else if (runCurrentStatement) {
|
||||
return queryRunner.runQueryStatement(selection);
|
||||
} else {
|
||||
return queryRunner.runQuery(selection, runOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private initQueryRunner(uri: string): QueryInfo {
|
||||
let queryRunner = this._instantiationService.createInstance(QueryRunner, uri);
|
||||
let info = new QueryInfo();
|
||||
queryRunner.onResultSet(e => {
|
||||
this._fireQueryEvent(uri, 'resultSet', e);
|
||||
});
|
||||
queryRunner.onBatchStart(b => {
|
||||
let link = undefined;
|
||||
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: messageText,
|
||||
batchId: b.id,
|
||||
isError: false,
|
||||
time: new Date().toLocaleTimeString(),
|
||||
link: link
|
||||
};
|
||||
this._fireQueryEvent(uri, 'message', message);
|
||||
info.selection.push(this._validateSelection(b.selection));
|
||||
});
|
||||
queryRunner.onMessage(m => {
|
||||
this._fireQueryEvent(uri, 'message', m);
|
||||
});
|
||||
queryRunner.onQueryEnd(totalMilliseconds => {
|
||||
this._onRunQueryComplete.fire(uri);
|
||||
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStop',
|
||||
uri: uri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
}
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
// fire UI event
|
||||
this._fireQueryEvent(uri, 'complete', totalMilliseconds);
|
||||
});
|
||||
queryRunner.onQueryStart(() => {
|
||||
this._onRunQueryStart.fire(uri);
|
||||
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStart',
|
||||
uri: uri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
}
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
this._fireQueryEvent(uri, 'start');
|
||||
});
|
||||
queryRunner.onResultSetUpdate(() => {
|
||||
this._onRunQueryUpdate.fire(uri);
|
||||
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryUpdate',
|
||||
uri: uri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
}
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
this._fireQueryEvent(uri, 'update');
|
||||
});
|
||||
|
||||
queryRunner.onQueryPlanAvailable(planInfo => {
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'executionPlan',
|
||||
uri: planInfo.fileUri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
},
|
||||
params: planInfo
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
});
|
||||
|
||||
queryRunner.onVisualize(resultSetInfo => {
|
||||
let event: IQueryEvent = {
|
||||
type: 'visualize',
|
||||
uri: uri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
},
|
||||
params: resultSetInfo
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
});
|
||||
|
||||
info.queryRunner = queryRunner;
|
||||
info.dataService = this._instantiationService.createInstance(DataService, uri);
|
||||
this._queryInfoMap.set(uri, info);
|
||||
return info;
|
||||
}
|
||||
|
||||
public cancelQuery(input: QueryRunner | string): void {
|
||||
let queryRunner: QueryRunner | undefined;
|
||||
|
||||
if (typeof input === 'string') {
|
||||
if (this._queryInfoMap.has(input)) {
|
||||
queryRunner = this._getQueryInfo(input)!.queryRunner;
|
||||
}
|
||||
} else {
|
||||
queryRunner = input;
|
||||
}
|
||||
|
||||
if (queryRunner === undefined || !queryRunner.isExecuting) {
|
||||
// TODO: Cannot cancel query as no query is running.
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch the spinner to canceling, which will be reset when the query execute sends back its completed event
|
||||
// TODO indicate on the status bar that the query is being canceled
|
||||
|
||||
// Cancel the query
|
||||
queryRunner.cancelQuery().then(success => undefined, error => {
|
||||
// On error, show error message and notify that the query is complete so that buttons and other status indicators
|
||||
// can be correct
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: strings.format(LocalizedConstants.msgCancelQueryFailed, error)
|
||||
});
|
||||
this._fireQueryEvent(queryRunner!.uri, 'complete', 0);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public async disposeQuery(ownerUri: string): Promise<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this.internalGetQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
await queryRunner.disposeQuery();
|
||||
}
|
||||
// remove our info map
|
||||
if (this._queryInfoMap.has(ownerUri)) {
|
||||
this._queryInfoMap.delete(ownerUri);
|
||||
}
|
||||
}
|
||||
|
||||
// EDIT DATA METHODS /////////////////////////////////////////////////////
|
||||
async initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> {
|
||||
// Reuse existing query runner if it exists
|
||||
let queryRunner: QueryRunner;
|
||||
let info: QueryInfo;
|
||||
|
||||
if (this._queryInfoMap.has(ownerUri)) {
|
||||
info = this._getQueryInfo(ownerUri)!;
|
||||
let existingRunner: QueryRunner = info.queryRunner;
|
||||
|
||||
// If the initialization is already in progress
|
||||
if (existingRunner.isExecuting) {
|
||||
return;
|
||||
}
|
||||
|
||||
queryRunner = existingRunner;
|
||||
} else {
|
||||
info = new QueryInfo();
|
||||
|
||||
// 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);
|
||||
const resultSetEventType = 'resultSet';
|
||||
queryRunner.onResultSet(resultSet => {
|
||||
this._fireQueryEvent(ownerUri, resultSetEventType, resultSet);
|
||||
});
|
||||
queryRunner.onResultSetUpdate(resultSetSummary => {
|
||||
this._fireQueryEvent(ownerUri, resultSetEventType, resultSetSummary);
|
||||
});
|
||||
queryRunner.onBatchStart(batch => {
|
||||
let link = undefined;
|
||||
let messageText = LocalizedConstants.runQueryBatchStartMessage;
|
||||
if (batch.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, batch.selection.startLine + 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
let message = {
|
||||
message: messageText,
|
||||
batchId: batch.id,
|
||||
isError: false,
|
||||
time: new Date().toLocaleTimeString(),
|
||||
link: link
|
||||
};
|
||||
this._fireQueryEvent(ownerUri, 'message', message);
|
||||
});
|
||||
queryRunner.onMessage(message => {
|
||||
this._fireQueryEvent(ownerUri, 'message', message);
|
||||
});
|
||||
queryRunner.onQueryEnd(totalMilliseconds => {
|
||||
this._onRunQueryComplete.fire(ownerUri);
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStop',
|
||||
uri: ownerUri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
},
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
// fire UI event
|
||||
this._fireQueryEvent(ownerUri, 'complete', totalMilliseconds);
|
||||
});
|
||||
queryRunner.onQueryStart(() => {
|
||||
this._onRunQueryStart.fire(ownerUri);
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStart',
|
||||
uri: ownerUri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
},
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
// fire UI event
|
||||
this._fireQueryEvent(ownerUri, 'start');
|
||||
});
|
||||
queryRunner.onEditSessionReady(e => {
|
||||
this._onEditSessionReady.fire(e);
|
||||
this._fireQueryEvent(e.ownerUri, 'editSessionReady');
|
||||
});
|
||||
|
||||
info.queryRunner = queryRunner;
|
||||
info.dataService = this._instantiationService.createInstance(DataService, ownerUri);
|
||||
this._queryInfoMap.set(ownerUri, info);
|
||||
}
|
||||
|
||||
if (queryString) {
|
||||
if (queryString.length < selectionSnippetMaxLen) {
|
||||
info.selectionSnippet = queryString;
|
||||
} else {
|
||||
info.selectionSnippet = queryString.substring(0, selectionSnippetMaxLen - 3) + '...';
|
||||
}
|
||||
}
|
||||
|
||||
return queryRunner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
|
||||
}
|
||||
|
||||
public cancelInitializeEdit(input: QueryRunner | string): void {
|
||||
// TODO: Implement query cancellation service
|
||||
}
|
||||
|
||||
public disposeEdit(ownerUri: string): Promise<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this.internalGetQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.disposeEdit(ownerUri);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult | undefined> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this.internalGetQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.updateCell(ownerUri, rowId, columnId, newValue).then((result) => result, error => {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize('updateCellFailed', "Update cell failed: ") + error.message
|
||||
});
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
public commitEdit(ownerUri: string): Promise<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this.internalGetQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.commitEdit(ownerUri).then(() => { }, error => {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize('commitEditFailed', "Commit row failed: ") + error.message
|
||||
});
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public createRow(ownerUri: string): Promise<azdata.EditCreateRowResult | undefined> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this.internalGetQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.createRow(ownerUri);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
public deleteRow(ownerUri: string, rowId: number): Promise<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this.internalGetQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.deleteRow(ownerUri, rowId);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult | undefined> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this.internalGetQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.revertCell(ownerUri, rowId, columnId);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
public revertRow(ownerUri: string, rowId: number): Promise<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this.internalGetQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.revertRow(ownerUri, rowId);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public getQueryRunner(ownerUri: string): QueryRunner | undefined {
|
||||
let queryRunner: QueryRunner | undefined = undefined;
|
||||
if (this._queryInfoMap.has(ownerUri)) {
|
||||
queryRunner = this._getQueryInfo(ownerUri)!.queryRunner;
|
||||
}
|
||||
// return undefined if not found or is already executing
|
||||
return queryRunner;
|
||||
}
|
||||
|
||||
// PRIVATE METHODS //////////////////////////////////////////////////////
|
||||
|
||||
private internalGetQueryRunner(ownerUri: string): QueryRunner | undefined {
|
||||
let queryRunner: QueryRunner | undefined;
|
||||
if (this._queryInfoMap.has(ownerUri)) {
|
||||
let existingRunner = this._getQueryInfo(ownerUri)!.queryRunner;
|
||||
// If the query is not already executing then set it up
|
||||
if (!existingRunner.isExecuting) {
|
||||
queryRunner = this._getQueryInfo(ownerUri)!.queryRunner;
|
||||
}
|
||||
}
|
||||
// return undefined if not found or is already executing
|
||||
return queryRunner;
|
||||
}
|
||||
|
||||
private _fireGridContentEvent(uri: string, type: string): void {
|
||||
let info = this._getQueryInfo(uri);
|
||||
|
||||
if (info && info.dataServiceReady) {
|
||||
let service: DataService = this.getDataService(uri);
|
||||
if (service) {
|
||||
// There is no need to queue up these events like there is for the query events because
|
||||
// if the DataService is not yet ready there will be no grid content to update
|
||||
service.gridContentObserver.next(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _fireQueryEvent(uri: string, type: string, data?: any) {
|
||||
let info = this._getQueryInfo(uri);
|
||||
|
||||
if (info && info.dataServiceReady) {
|
||||
let service: DataService = this.getDataService(uri);
|
||||
service.queryEventObserver.next({
|
||||
type: type,
|
||||
data: data
|
||||
});
|
||||
} else if (info) {
|
||||
let queueItem: QueryEvent = { type: type, data: data };
|
||||
info.queryEventQueue.push(queueItem);
|
||||
}
|
||||
}
|
||||
|
||||
private _sendQueuedEvents(uri: string): void {
|
||||
let info = this._getQueryInfo(uri);
|
||||
while (info && info.queryEventQueue.length > 0) {
|
||||
let event = info.queryEventQueue.shift()!;
|
||||
this._fireQueryEvent(uri, event.type, event.data);
|
||||
}
|
||||
}
|
||||
|
||||
public _getQueryInfo(uri: string): QueryInfo | undefined {
|
||||
return this._queryInfoMap.get(uri);
|
||||
}
|
||||
|
||||
// TODO remove this funciton and its usages when #821 in vscode-mssql is fixed and
|
||||
// the SqlToolsService version is updated in this repo - coquagli 4/19/2017
|
||||
private _validateSelection(selection: azdata.ISelectionData): azdata.ISelectionData {
|
||||
if (!selection) {
|
||||
selection = <azdata.ISelectionData>{};
|
||||
}
|
||||
selection.endColumn = selection ? Math.max(0, selection.endColumn) : 0;
|
||||
selection.endLine = selection ? Math.max(0, selection.endLine) : 0;
|
||||
selection.startColumn = selection ? Math.max(0, selection.startColumn) : 0;
|
||||
selection.startLine = selection ? Math.max(0, selection.startLine) : 0;
|
||||
return selection;
|
||||
}
|
||||
}
|
||||
@@ -1,682 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
import { IQueryManagementService } from 'sql/platform/query/common/queryManagement';
|
||||
import * as Utils from 'sql/platform/connection/common/utils';
|
||||
import { SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { IQueryPlanInfo } from 'sql/platform/query/common/queryModel';
|
||||
import { ResultSerializer } from 'sql/workbench/contrib/query/common/resultSerializer';
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
|
||||
import { IGridDataProvider, getResultsString } from 'sql/platform/query/common/gridDataProvider';
|
||||
import { getErrorMessage } from 'vs/base/common/errors';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
|
||||
export interface IEditSessionReadyEvent {
|
||||
ownerUri: string;
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface IQueryMessage extends azdata.IResultMessage {
|
||||
selection?: azdata.ISelectionData;
|
||||
}
|
||||
|
||||
/*
|
||||
* Query Runner class which handles running a query, reports the results to the content manager,
|
||||
* and handles getting more rows from the service layer and disposing when the content is closed.
|
||||
*/
|
||||
export default class QueryRunner extends Disposable {
|
||||
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
||||
private _resultLineOffset: number;
|
||||
private _resultColumnOffset: number;
|
||||
private _totalElapsedMilliseconds: number = 0;
|
||||
private _isExecuting: boolean = false;
|
||||
private _hasCompleted: boolean = false;
|
||||
private _batchSets: azdata.BatchSummary[] = [];
|
||||
private _messages: IQueryMessage[] = [];
|
||||
private registered = false;
|
||||
|
||||
private _isQueryPlan: boolean = false;
|
||||
public get isQueryPlan(): boolean { return this._isQueryPlan; }
|
||||
private _planXml = new Deferred<string>();
|
||||
public get planXml(): Promise<string> { return this._planXml.promise; }
|
||||
|
||||
private _onMessage = this._register(new Emitter<IQueryMessage>());
|
||||
public get onMessage(): Event<IQueryMessage> { return this._onMessage.event; } // this is the only way typemoq can moq this... needs investigation @todo anthonydresser 5/2/2019
|
||||
|
||||
private _onResultSet = this._register(new Emitter<azdata.ResultSetSummary>());
|
||||
public readonly onResultSet = this._onResultSet.event;
|
||||
|
||||
private _onResultSetUpdate = this._register(new Emitter<azdata.ResultSetSummary>());
|
||||
public readonly onResultSetUpdate = this._onResultSetUpdate.event;
|
||||
|
||||
private _onQueryStart = this._register(new Emitter<void>());
|
||||
public readonly onQueryStart: Event<void> = this._onQueryStart.event;
|
||||
|
||||
private _onQueryEnd = this._register(new Emitter<string>());
|
||||
public get onQueryEnd(): Event<string> { return this._onQueryEnd.event; }
|
||||
|
||||
private _onBatchStart = this._register(new Emitter<azdata.BatchSummary>());
|
||||
public readonly onBatchStart: Event<azdata.BatchSummary> = this._onBatchStart.event;
|
||||
|
||||
private _onBatchEnd = this._register(new Emitter<azdata.BatchSummary>());
|
||||
public readonly onBatchEnd: Event<azdata.BatchSummary> = this._onBatchEnd.event;
|
||||
|
||||
private _onEditSessionReady = this._register(new Emitter<IEditSessionReadyEvent>());
|
||||
public readonly onEditSessionReady = this._onEditSessionReady.event;
|
||||
|
||||
private _onQueryPlanAvailable = this._register(new Emitter<IQueryPlanInfo>());
|
||||
public readonly onQueryPlanAvailable = this._onQueryPlanAvailable.event;
|
||||
|
||||
private _onVisualize = this._register(new Emitter<azdata.ResultSetSummary>());
|
||||
public readonly onVisualize = this._onVisualize.event;
|
||||
|
||||
private _queryStartTime?: Date;
|
||||
public get queryStartTime(): Date | undefined {
|
||||
return this._queryStartTime;
|
||||
}
|
||||
private _queryEndTime?: Date;
|
||||
public get queryEndTime(): Date | undefined {
|
||||
return this._queryEndTime;
|
||||
}
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
constructor(
|
||||
public uri: string,
|
||||
@IQueryManagementService private _queryManagementService: IQueryManagementService,
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService,
|
||||
@ILogService private _logService: ILogService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get isExecuting(): boolean {
|
||||
return this._isExecuting;
|
||||
}
|
||||
|
||||
get hasCompleted(): boolean {
|
||||
return this._hasCompleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* For public use only, for private use, directly access the member
|
||||
*/
|
||||
public get batchSets(): azdata.BatchSummary[] {
|
||||
return this._batchSets.slice(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* For public use only, for private use, directly access the member
|
||||
*/
|
||||
public get messages(): IQueryMessage[] {
|
||||
return this._messages.slice(0);
|
||||
}
|
||||
|
||||
// PUBLIC METHODS ======================================================
|
||||
|
||||
/**
|
||||
* Cancels the running query, if there is one
|
||||
*/
|
||||
public cancelQuery(): Promise<azdata.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?: azdata.ExecutionPlanOptions): Promise<void>;
|
||||
/**
|
||||
* Runs the query by pulling the query from the document using the provided selection data
|
||||
* @param input selection data
|
||||
*/
|
||||
public runQuery(input: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
|
||||
public runQuery(input: string | azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
|
||||
if (types.isString(input)) {
|
||||
return this.doRunQuery(input, false, runOptions);
|
||||
} else {
|
||||
return this.doRunQuery(input, false, runOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the current SQL statement by pulling the query from the document using the provided selection data
|
||||
* @param input selection data
|
||||
*/
|
||||
public runQueryStatement(input: azdata.ISelectionData): Promise<void> {
|
||||
return this.doRunQuery(input, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation that runs the query with the provided query
|
||||
* @param input Query string to execute
|
||||
*/
|
||||
private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
|
||||
private doRunQuery(input: azdata.ISelectionData, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
|
||||
private doRunQuery(input: string | azdata.ISelectionData, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
|
||||
if (this.isExecuting) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this._planXml = new Deferred<string>();
|
||||
this._batchSets = [];
|
||||
this._hasCompleted = false;
|
||||
this._queryStartTime = undefined;
|
||||
this._queryEndTime = undefined;
|
||||
this._messages = [];
|
||||
if (isSelectionOrUndefined(input)) {
|
||||
// Update internal state to show that we're executing the query
|
||||
this._resultLineOffset = input ? input.startLine : 0;
|
||||
this._resultColumnOffset = input ? input.startColumn : 0;
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
|
||||
this._onQueryStart.fire();
|
||||
|
||||
// Send the request to execute the query
|
||||
return runCurrentStatement
|
||||
? this._queryManagementService.runQueryStatement(this.uri, input.startLine, input.startColumn).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e))
|
||||
: this._queryManagementService.runQuery(this.uri, input, runOptions).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
|
||||
} else {
|
||||
// Update internal state to show that we're executing the query
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
|
||||
this._onQueryStart.fire();
|
||||
|
||||
return this._queryManagementService.runQueryString(this.uri, input).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
|
||||
}
|
||||
}
|
||||
|
||||
private handleSuccessRunQueryResult() {
|
||||
// this isn't exact, but its the best we can do
|
||||
this._queryStartTime = new Date();
|
||||
// The query has started, so lets fire up the result pane
|
||||
if (!this.registered) {
|
||||
this.registered = true;
|
||||
this._queryManagementService.registerRunner(this, this.uri);
|
||||
}
|
||||
}
|
||||
|
||||
private handleFailureRunQueryResult(error: any) {
|
||||
// Attempting to launch the query failed, show the error message
|
||||
const eol = getEolString(this._textResourcePropertiesService, this.uri);
|
||||
if (error instanceof Error) {
|
||||
error = error.message;
|
||||
}
|
||||
let message = nls.localize('query.ExecutionFailedError', "Execution failed due to an unexpected error: {0}\t{1}", eol, error);
|
||||
this.handleMessage(<azdata.QueryExecuteMessageParams>{
|
||||
ownerUri: this.uri,
|
||||
message: {
|
||||
isError: true,
|
||||
message: message
|
||||
}
|
||||
});
|
||||
this.handleQueryComplete(<azdata.QueryExecuteCompleteNotificationResult>{ ownerUri: this.uri });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a QueryComplete from the service layer
|
||||
*/
|
||||
public handleQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void {
|
||||
// this also isn't exact but its the best we can do
|
||||
this._queryEndTime = new Date();
|
||||
|
||||
// Store the batch sets we got back as a source of "truth"
|
||||
this._isExecuting = false;
|
||||
this._hasCompleted = true;
|
||||
this._batchSets = result.batchSummaries ? result.batchSummaries : [];
|
||||
|
||||
this._batchSets.map(batch => {
|
||||
if (batch.selection) {
|
||||
batch.selection.startLine += this._resultLineOffset;
|
||||
batch.selection.startColumn += this._resultColumnOffset;
|
||||
batch.selection.endLine += this._resultLineOffset;
|
||||
batch.selection.endColumn += this._resultColumnOffset;
|
||||
}
|
||||
});
|
||||
|
||||
let timeStamp = Utils.parseNumAsTimeString(this._totalElapsedMilliseconds);
|
||||
// We're done with this query so shut down any waiting mechanisms
|
||||
|
||||
let message = {
|
||||
message: nls.localize('query.message.executionTime', "Total execution time: {0}", timeStamp),
|
||||
isError: false,
|
||||
time: undefined
|
||||
};
|
||||
this._messages.push(message);
|
||||
|
||||
this._onQueryEnd.fire(timeStamp);
|
||||
this._onMessage.fire(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a BatchStart from the service layer
|
||||
*/
|
||||
public handleBatchStart(result: azdata.QueryExecuteBatchNotificationParams): void {
|
||||
let batch = result.batchSummary;
|
||||
|
||||
// Recalculate the start and end lines, relative to the result line offset
|
||||
if (batch.selection) {
|
||||
batch.selection.startLine += this._resultLineOffset;
|
||||
batch.selection.startColumn += this._resultColumnOffset;
|
||||
batch.selection.endLine += this._resultLineOffset;
|
||||
batch.selection.endColumn += this._resultColumnOffset;
|
||||
}
|
||||
|
||||
// Set the result sets as an empty array so that as result sets complete we can add to the list
|
||||
batch.resultSetSummaries = [];
|
||||
|
||||
// Store the batch
|
||||
this._batchSets[batch.id] = batch;
|
||||
|
||||
let message = {
|
||||
// account for index by 1
|
||||
message: nls.localize('query.message.startQuery', "Started executing query at Line {0}", batch.selection.startLine + 1),
|
||||
time: new Date(batch.executionStart).toLocaleTimeString(),
|
||||
selection: batch.selection,
|
||||
isError: false
|
||||
};
|
||||
this._messages.push(message);
|
||||
this._onMessage.fire(message);
|
||||
this._onBatchStart.fire(batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a BatchComplete from the service layer
|
||||
*/
|
||||
public handleBatchComplete(result: azdata.QueryExecuteBatchNotificationParams): void {
|
||||
let batch: azdata.BatchSummary = result.batchSummary;
|
||||
|
||||
// Store the batch again to get the rest of the data
|
||||
this._batchSets[batch.id] = batch;
|
||||
let executionTime = <number>(Utils.parseTimeString(batch.executionElapsed) || 0);
|
||||
this._totalElapsedMilliseconds += executionTime;
|
||||
if (executionTime > 0) {
|
||||
// send a time message in the format used for query complete
|
||||
this.sendBatchTimeMessage(batch.id, Utils.parseNumAsTimeString(executionTime));
|
||||
}
|
||||
|
||||
this._onBatchEnd.fire(batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a ResultSetComplete from the service layer
|
||||
*/
|
||||
public handleResultSetAvailable(result: azdata.QueryExecuteResultSetNotificationParams): void {
|
||||
if (result && result.resultSetSummary) {
|
||||
let resultSet = result.resultSetSummary;
|
||||
let batchSet: azdata.BatchSummary;
|
||||
if (!resultSet.batchId) {
|
||||
// Missing the batchId or processing batchId==0. In this case, default to always using the first batch in the list
|
||||
// or create one in the case the DMP extension didn't obey the contract perfectly
|
||||
if (this._batchSets.length > 0) {
|
||||
batchSet = this._batchSets[0];
|
||||
} else {
|
||||
batchSet = <azdata.BatchSummary><unknown>{
|
||||
id: 0,
|
||||
selection: undefined,
|
||||
hasError: false,
|
||||
resultSetSummaries: []
|
||||
};
|
||||
this._batchSets[0] = batchSet;
|
||||
}
|
||||
} else {
|
||||
batchSet = this._batchSets[resultSet.batchId];
|
||||
}
|
||||
// handle getting queryPlanxml if we need too
|
||||
// check if this result has show plan, this needs work, it won't work for any other provider
|
||||
let hasShowPlan = !!find(result.resultSetSummary.columnInfo, e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
|
||||
if (hasShowPlan) {
|
||||
this._isQueryPlan = true;
|
||||
|
||||
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => {
|
||||
if (e.resultSubset.rows) {
|
||||
this._planXml.resolve(e.resultSubset.rows[0][0].displayValue);
|
||||
}
|
||||
}).catch((e) => this._logService.error(e));
|
||||
}
|
||||
// we will just ignore the set if we already have it
|
||||
// ideally this should never happen
|
||||
if (batchSet && !batchSet.resultSetSummaries[resultSet.id]) {
|
||||
// Store the result set in the batch and emit that a result set has completed
|
||||
batchSet.resultSetSummaries[resultSet.id] = resultSet;
|
||||
this._onResultSet.fire(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public handleResultSetUpdated(result: azdata.QueryExecuteResultSetNotificationParams): void {
|
||||
if (result && result.resultSetSummary) {
|
||||
let resultSet = result.resultSetSummary;
|
||||
let batchSet: azdata.BatchSummary;
|
||||
batchSet = this._batchSets[resultSet.batchId];
|
||||
// handle getting queryPlanxml if we need too
|
||||
// check if this result has show plan, this needs work, it won't work for any other provider
|
||||
let hasShowPlan = !!find(result.resultSetSummary.columnInfo, e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
|
||||
if (hasShowPlan) {
|
||||
this._isQueryPlan = true;
|
||||
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => {
|
||||
|
||||
if (e.resultSubset.rows) {
|
||||
let planXmlString = e.resultSubset.rows[0][0].displayValue;
|
||||
this._planXml.resolve(e.resultSubset.rows[0][0].displayValue);
|
||||
// fire query plan available event if execution is completed
|
||||
if (result.resultSetSummary.complete) {
|
||||
this._onQueryPlanAvailable.fire({
|
||||
providerId: mssqlProviderName,
|
||||
fileUri: result.ownerUri,
|
||||
planXml: planXmlString
|
||||
});
|
||||
}
|
||||
}
|
||||
}).catch((e) => this._logService.error(e));
|
||||
}
|
||||
if (batchSet) {
|
||||
// Store the result set in the batch and emit that a result set has completed
|
||||
batchSet.resultSetSummaries[resultSet.id] = resultSet;
|
||||
this._onResultSetUpdate.fire(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a Mssage from the service layer
|
||||
*/
|
||||
public handleMessage(obj: azdata.QueryExecuteMessageParams): void {
|
||||
let message = obj.message;
|
||||
message.time = new Date(message.time!).toLocaleTimeString();
|
||||
this._messages.push(message);
|
||||
|
||||
// Send the message to the results pane
|
||||
this._onMessage.fire(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more data rows from the current resultSets from the service layer
|
||||
*/
|
||||
public getQueryRows(rowStart: number, numberOfRows: number, batchIndex: number, resultSetIndex: number): Promise<azdata.QueryExecuteSubsetResult> {
|
||||
let rowData: azdata.QueryExecuteSubsetParams = <azdata.QueryExecuteSubsetParams>{
|
||||
ownerUri: this.uri,
|
||||
resultSetIndex: resultSetIndex,
|
||||
rowsCount: numberOfRows,
|
||||
rowsStartIndex: rowStart,
|
||||
batchIndex: batchIndex
|
||||
};
|
||||
|
||||
return this._queryManagementService.getQueryRows(rowData).then(r => r, error => {
|
||||
// this._notificationService.notify({
|
||||
// severity: Severity.Error,
|
||||
// message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error)
|
||||
// });
|
||||
return error;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a session ready event for Edit Data
|
||||
*/
|
||||
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> {
|
||||
// Update internal state to show that we're executing the query
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
|
||||
return this._queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString).then(result => {
|
||||
// The query has started, so lets fire up the result pane
|
||||
this._onQueryStart.fire();
|
||||
this._queryManagementService.registerRunner(this, ownerUri);
|
||||
}, error => {
|
||||
// Attempting to launch the query failed, show the error message
|
||||
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
this._isExecuting = false;
|
||||
this._notificationService.error(nls.localize('query.initEditExecutionFailed', "Initialize edit data session failed: ") + error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a number of rows from an edit session
|
||||
* @param rowStart The index of the row to start returning (inclusive)
|
||||
* @param numberOfRows The number of rows to return
|
||||
*/
|
||||
public getEditRows(rowStart: number, numberOfRows: number): Promise<azdata.EditSubsetResult> {
|
||||
const self = this;
|
||||
let rowData: azdata.EditSubsetParams = {
|
||||
ownerUri: this.uri,
|
||||
rowCount: numberOfRows,
|
||||
rowStartIndex: rowStart
|
||||
};
|
||||
|
||||
return new Promise<azdata.EditSubsetResult>((resolve, reject) => {
|
||||
self._queryManagementService.getEditRows(rowData).then(result => {
|
||||
if (!result.hasOwnProperty('rowCount')) {
|
||||
let error = `Nothing returned from subset query`;
|
||||
self._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: error
|
||||
});
|
||||
reject(error);
|
||||
}
|
||||
resolve(result);
|
||||
}, error => {
|
||||
// let errorMessage = nls.localize('query.moreRowsFailedError', "Something went wrong getting more rows:");
|
||||
// self._notificationService.notify({
|
||||
// severity: Severity.Error,
|
||||
// message: `${errorMessage} ${error}`
|
||||
// });
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public handleEditSessionReady(ownerUri: string, success: boolean, message: string): void {
|
||||
this._onEditSessionReady.fire({ ownerUri, success, message });
|
||||
}
|
||||
|
||||
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult> {
|
||||
return this._queryManagementService.updateCell(ownerUri, rowId, columnId, newValue);
|
||||
}
|
||||
|
||||
public commitEdit(ownerUri: string): Promise<void> {
|
||||
return this._queryManagementService.commitEdit(ownerUri);
|
||||
}
|
||||
|
||||
public createRow(ownerUri: string): Promise<azdata.EditCreateRowResult> {
|
||||
return this._queryManagementService.createRow(ownerUri).then(result => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public deleteRow(ownerUri: string, rowId: number): Promise<void> {
|
||||
return this._queryManagementService.deleteRow(ownerUri, rowId);
|
||||
}
|
||||
|
||||
public revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult> {
|
||||
return this._queryManagementService.revertCell(ownerUri, rowId, columnId).then(result => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public revertRow(ownerUri: string, rowId: number): Promise<void> {
|
||||
return this._queryManagementService.revertRow(ownerUri, rowId);
|
||||
}
|
||||
|
||||
public disposeEdit(ownerUri: string): Promise<void> {
|
||||
return this._queryManagementService.disposeEdit(ownerUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the Query from the service client
|
||||
*/
|
||||
public async disposeQuery(): Promise<void> {
|
||||
await this._queryManagementService.disposeQuery(this.uri);
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._batchSets = undefined!;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
get totalElapsedMilliseconds(): number {
|
||||
return this._totalElapsedMilliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a copy request
|
||||
* @param selection The selection range to copy
|
||||
* @param batchId The batch id of the result to copy from
|
||||
* @param resultId The result id of the result to copy from
|
||||
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
||||
*/
|
||||
async copyResults(selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): Promise<void> {
|
||||
let provider = this.getGridDataProvider(batchId, resultId);
|
||||
return provider.copyResults(selection, includeHeaders);
|
||||
}
|
||||
|
||||
|
||||
public getColumnHeaders(batchId: number, resultId: number, range: Slick.Range): string[] | undefined {
|
||||
let headers: string[] | undefined = undefined;
|
||||
let batchSummary: azdata.BatchSummary = this._batchSets[batchId];
|
||||
if (batchSummary !== undefined) {
|
||||
let resultSetSummary = batchSummary.resultSetSummaries[resultId];
|
||||
headers = resultSetSummary.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => {
|
||||
return info.columnName;
|
||||
});
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private sendBatchTimeMessage(batchId: number, executionTime: string): void {
|
||||
// get config copyRemoveNewLine option from vscode config
|
||||
let showBatchTime = this._configurationService.getValue<boolean>('sql.showBatchTime');
|
||||
if (showBatchTime) {
|
||||
let message: IQueryMessage = {
|
||||
batchId: batchId,
|
||||
message: nls.localize('elapsedBatchTime', "Batch execution time: {0}", executionTime),
|
||||
time: undefined,
|
||||
isError: false
|
||||
};
|
||||
this._messages.push(message);
|
||||
// Send the message to the results pane
|
||||
this._onMessage.fire(message);
|
||||
}
|
||||
}
|
||||
|
||||
public serializeResults(batchId: number, resultSetId: number, format: SaveFormat, selection: Slick.Range[]) {
|
||||
return this.instantiationService.createInstance(ResultSerializer).saveResults(this.uri, { selection, format, batchIndex: batchId, resultSetNumber: resultSetId });
|
||||
}
|
||||
|
||||
public getGridDataProvider(batchId: number, resultSetId: number): IGridDataProvider {
|
||||
return this.instantiationService.createInstance(QueryGridDataProvider, this, batchId, resultSetId);
|
||||
}
|
||||
|
||||
public notifyVisualizeRequested(batchId: number, resultSetId: number): void {
|
||||
let result: azdata.ResultSetSummary = {
|
||||
batchId: batchId,
|
||||
id: resultSetId,
|
||||
columnInfo: this.batchSets[batchId].resultSetSummaries[resultSetId].columnInfo,
|
||||
complete: true,
|
||||
rowCount: this.batchSets[batchId].resultSetSummaries[resultSetId].rowCount
|
||||
};
|
||||
this._onVisualize.fire(result);
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryGridDataProvider implements IGridDataProvider {
|
||||
|
||||
constructor(
|
||||
private queryRunner: QueryRunner,
|
||||
private batchId: number,
|
||||
private resultSetId: number,
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IClipboardService private _clipboardService: IClipboardService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
}
|
||||
|
||||
getRowData(rowStart: number, numberOfRows: number): Promise<azdata.QueryExecuteSubsetResult> {
|
||||
return this.queryRunner.getQueryRows(rowStart, numberOfRows, this.batchId, this.resultSetId);
|
||||
}
|
||||
|
||||
copyResults(selection: Slick.Range[], includeHeaders?: boolean): Promise<void> {
|
||||
return this.copyResultsAsync(selection, includeHeaders);
|
||||
}
|
||||
|
||||
private async copyResultsAsync(selection: Slick.Range[], includeHeaders?: boolean): Promise<void> {
|
||||
try {
|
||||
let results = await getResultsString(this, selection, includeHeaders);
|
||||
await this._clipboardService.writeText(results);
|
||||
} catch (error) {
|
||||
this._notificationService.error(nls.localize('copyFailed', "Copy failed with error {0}", getErrorMessage(error)));
|
||||
}
|
||||
}
|
||||
getEolString(): string {
|
||||
return getEolString(this._textResourcePropertiesService, this.queryRunner.uri);
|
||||
}
|
||||
shouldIncludeHeaders(includeHeaders: boolean): boolean {
|
||||
return shouldIncludeHeaders(includeHeaders, this._configurationService);
|
||||
}
|
||||
shouldRemoveNewLines(): boolean {
|
||||
return shouldRemoveNewLines(this._configurationService);
|
||||
}
|
||||
getColumnHeaders(range: Slick.Range): string[] | undefined {
|
||||
return this.queryRunner.getColumnHeaders(this.batchId, this.resultSetId, range);
|
||||
}
|
||||
|
||||
get canSerialize(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
serializeResults(format: SaveFormat, selection: Slick.Range[]): Promise<void> {
|
||||
return this.queryRunner.serializeResults(this.batchId, this.resultSetId, format, selection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function getEolString(textResourcePropertiesService: ITextResourcePropertiesService, uri: string): string {
|
||||
return textResourcePropertiesService.getEOL(URI.parse(uri), 'sql');
|
||||
}
|
||||
|
||||
export function shouldIncludeHeaders(includeHeaders: boolean, configurationService: IConfigurationService): boolean {
|
||||
if (includeHeaders !== undefined) {
|
||||
// Respect the value explicity passed into the method
|
||||
return includeHeaders;
|
||||
}
|
||||
// else get config option from vscode config
|
||||
includeHeaders = configurationService.getValue<boolean>('sql.copyIncludeHeaders');
|
||||
return !!includeHeaders;
|
||||
}
|
||||
|
||||
export function shouldRemoveNewLines(configurationService: IConfigurationService): boolean {
|
||||
// get config copyRemoveNewLine option from vscode config
|
||||
let removeNewLines = configurationService.getValue<boolean>('sql.copyRemoveNewLine');
|
||||
return !!removeNewLines;
|
||||
}
|
||||
|
||||
function isSelectionOrUndefined(input: string | azdata.ISelectionData | undefined): input is azdata.ISelectionData | undefined {
|
||||
return types.isObject(input) || types.isUndefinedOrNull(input);
|
||||
}
|
||||
Reference in New Issue
Block a user