Query Runner Tests (#10252)

* rework some code and write an inital test

* fix strict

* add more to standard test

* add to existing workflow test

* fix tests

* simplify the code

* add more tests

* remove bad import

* fix compile

* fix timestampiong
This commit is contained in:
Anthony Dresser
2020-05-06 13:38:12 -07:00
committed by GitHub
parent 4199cec393
commit df5df38a55
25 changed files with 856 additions and 430 deletions

View File

@@ -12,6 +12,28 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo'; import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService'; import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
/**
* A range in the editor. This interface is suitable for serialization.
*/
export interface IRange {
/**
* Line number on which the range starts (starts at 1).
*/
readonly startLineNumber: number;
/**
* Column on which the range starts in line `startLineNumber` (starts at 1).
*/
readonly startColumn: number;
/**
* Line number on which the range ends.
*/
readonly endLineNumber: number;
/**
* Column on which the range ends in line `endLineNumber`.
*/
readonly endColumn: number;
}
/** /**
* Options for the actions that could happen after connecting is complete * Options for the actions that could happen after connecting is complete
*/ */
@@ -302,7 +324,7 @@ export interface INewConnectionParams {
connectionType: ConnectionType; connectionType: ConnectionType;
input?: IConnectableInput; input?: IConnectableInput;
runQueryOnCompletion?: RunQueryOnConnectionMode; runQueryOnCompletion?: RunQueryOnConnectionMode;
querySelection?: azdata.ISelectionData; queryRange?: IRange;
showDashboard?: boolean; showDashboard?: boolean;
providers?: string[]; providers?: string[];
isEditConnection?: boolean; isEditConnection?: boolean;

View File

@@ -14,8 +14,9 @@ import { IConnectionManagementService, IConnectableInput, INewConnectionParams,
import { QueryResultsInput } from 'sql/workbench/common/editor/query/queryResultsInput'; import { QueryResultsInput } from 'sql/workbench/common/editor/query/queryResultsInput';
import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel'; import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel';
import { ISelectionData, ExecutionPlanOptions } from 'azdata'; import { ExecutionPlanOptions } from 'azdata';
import { startsWith } from 'vs/base/common/strings'; import { startsWith } from 'vs/base/common/strings';
import { IRange } from 'vs/editor/common/core/range';
const MAX_SIZE = 13; const MAX_SIZE = 13;
@@ -229,13 +230,13 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
} }
// State update funtions // State update funtions
public runQuery(selection?: ISelectionData, executePlanOptions?: ExecutionPlanOptions): void { public runQuery(range?: IRange, executePlanOptions?: ExecutionPlanOptions): void {
this.queryModelService.runQuery(this.uri, selection, executePlanOptions); this.queryModelService.runQuery(this.uri, range, executePlanOptions);
this.state.executing = true; this.state.executing = true;
} }
public runQueryStatement(selection?: ISelectionData): void { public runQueryStatement(range?: IRange): void {
this.queryModelService.runQueryStatement(this.uri, selection); this.queryModelService.runQueryStatement(this.uri, range);
this.state.executing = true; this.state.executing = true;
} }
@@ -269,15 +270,15 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
let isRunningQuery = this.queryModelService.isRunningQuery(this.uri); let isRunningQuery = this.queryModelService.isRunningQuery(this.uri);
if (!isRunningQuery && params && params.runQueryOnCompletion) { if (!isRunningQuery && params && params.runQueryOnCompletion) {
let selection: ISelectionData | undefined = params ? params.querySelection : undefined; let range: IRange | undefined = params ? params.queryRange : undefined;
if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeCurrentQuery) { if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeCurrentQuery) {
this.runQueryStatement(selection); this.runQueryStatement(range);
} else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeQuery) { } else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeQuery) {
this.runQuery(selection); this.runQuery(range);
} else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.estimatedQueryPlan) { } else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.estimatedQueryPlan) {
this.runQuery(selection, { displayEstimatedQueryPlan: true }); this.runQuery(range, { displayEstimatedQueryPlan: true });
} else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.actualQueryPlan) { } else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.actualQueryPlan) {
this.runQuery(selection, { displayActualQueryPlan: true }); this.runQuery(range, { displayActualQueryPlan: true });
} }
} }
this._onDidChangeLabel.fire(); this._onDidChangeLabel.fire();

View File

@@ -8,6 +8,7 @@ import 'vs/css!./media/chartView';
import { IPanelView } from 'sql/base/browser/ui/panel/panel'; import { IPanelView } from 'sql/base/browser/ui/panel/panel';
import { Insight } from './insight'; import { Insight } from './insight';
import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import { ICellValue } from 'sql/workbench/services/query/common/query';
import { ChartOptions, IChartOption, ControlType } from './chartOptions'; import { ChartOptions, IChartOption, ControlType } from './chartOptions';
import { Extensions, IInsightRegistry, IInsightData } from 'sql/platform/dashboard/browser/insightRegistry'; import { Extensions, IInsightRegistry, IInsightData } from 'sql/platform/dashboard/browser/insightRegistry';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
@@ -28,7 +29,6 @@ import { ChartState } from 'sql/workbench/common/editor/query/chartState';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { find } from 'vs/base/common/arrays'; import { find } from 'vs/base/common/arrays';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { DbCellValue } from 'azdata';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution); const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
@@ -213,7 +213,7 @@ export class ChartView extends Disposable implements IPanelView {
this.shouldGraph(); this.shouldGraph();
} }
public setData(rows: DbCellValue[][], columns: string[]): void { public setData(rows: ICellValue[][], columns: string[]): void {
if (!rows) { if (!rows) {
this._data = { columns: [], rows: [] }; this._data = { columns: [], rows: [] };
this._notificationService.error(nls.localize('charting.failedToGetRows', "Failed to get rows for the dataset to chart.")); this._notificationService.error(nls.localize('charting.failedToGetRows', "Failed to get rows for the dataset to chart."));
@@ -238,7 +238,7 @@ export class ChartView extends Disposable implements IPanelView {
let summary = batch.resultSetSummaries[this._currentData.resultId]; let summary = batch.resultSetSummaries[this._currentData.resultId];
if (summary) { if (summary) {
this._queryRunner.getQueryRows(0, summary.rowCount, this._currentData.batchId, this._currentData.resultId).then(d => { this._queryRunner.getQueryRows(0, summary.rowCount, this._currentData.batchId, this._currentData.resultId).then(d => {
let rows = d.resultSubset.rows; let rows = d.rows;
let columns = summary.columnInfo.map(c => c.columnName); let columns = summary.columnInfo.map(c => c.columnName);
this.setData(rows, columns); this.setData(rows, columns);
}); });

View File

@@ -14,6 +14,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IDataResource } from 'sql/workbench/services/notebook/browser/sql/sqlSessionManager'; import { IDataResource } from 'sql/workbench/services/notebook/browser/sql/sqlSessionManager';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { getEolString, shouldIncludeHeaders, shouldRemoveNewLines } from 'sql/workbench/services/query/common/queryRunner'; import { getEolString, shouldIncludeHeaders, shouldRemoveNewLines } from 'sql/workbench/services/query/common/queryRunner';
import { ICellValue, ResultSetSummary, ResultSetSubset } from 'sql/workbench/services/query/common/query';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { attachTableStyler } from 'sql/platform/theme/common/styler'; import { attachTableStyler } from 'sql/platform/theme/common/styler';
@@ -215,7 +216,7 @@ class DataResourceTable extends GridTableBase<any> {
gridDataProvider.getRowData(0, rowCount).then(result => { gridDataProvider.getRowData(0, rowCount).then(result => {
let range = new Slick.Range(0, 0, rowCount - 1, columnCount - 1); let range = new Slick.Range(0, 0, rowCount - 1, columnCount - 1);
let columns = gridDataProvider.getColumnHeaders(range); let columns = gridDataProvider.getColumnHeaders(range);
this._chart.setData(result.resultSubset.rows, columns); this._chart.setData(result.rows, columns);
}); });
} }
@@ -226,9 +227,9 @@ class DataResourceTable extends GridTableBase<any> {
} }
class DataResourceDataProvider implements IGridDataProvider { class DataResourceDataProvider implements IGridDataProvider {
private rows: azdata.DbCellValue[][]; private rows: ICellValue[][];
constructor(source: IDataResource, constructor(source: IDataResource,
private resultSet: azdata.ResultSetSummary, private resultSet: ResultSetSummary,
private documentUri: string, private documentUri: string,
@INotificationService private _notificationService: INotificationService, @INotificationService private _notificationService: INotificationService,
@IClipboardService private _clipboardService: IClipboardService, @IClipboardService private _clipboardService: IClipboardService,
@@ -256,17 +257,14 @@ class DataResourceDataProvider implements IGridDataProvider {
}); });
} }
getRowData(rowStart: number, numberOfRows: number): Thenable<azdata.QueryExecuteSubsetResult> { getRowData(rowStart: number, numberOfRows: number): Thenable<ResultSetSubset> {
let rowEnd = rowStart + numberOfRows; let rowEnd = rowStart + numberOfRows;
if (rowEnd > this.rows.length) { if (rowEnd > this.rows.length) {
rowEnd = this.rows.length; rowEnd = this.rows.length;
} }
let resultSubset: azdata.QueryExecuteSubsetResult = { let resultSubset: ResultSetSubset = {
message: undefined, rowCount: rowEnd - rowStart,
resultSubset: { rows: this.rows.slice(rowStart, rowEnd)
rowCount: rowEnd - rowStart,
rows: this.rows.slice(rowStart, rowEnd)
}
}; };
return Promise.resolve(resultSubset); return Promise.resolve(resultSubset);
} }
@@ -326,7 +324,7 @@ class DataResourceDataProvider implements IGridDataProvider {
maxRow = singleSelection.toRow + 1; maxRow = singleSelection.toRow + 1;
columns = columns.slice(singleSelection.fromCell, singleSelection.toCell + 1); columns = columns.slice(singleSelection.fromCell, singleSelection.toCell + 1);
} }
let getRows: ((index: number, rowCount: number) => azdata.DbCellValue[][]) = (index, rowCount) => { let getRows: ((index: number, rowCount: number) => ICellValue[][]) = (index, rowCount) => {
// Offset for selections by adding the selection startRow to the index // Offset for selections by adding the selection startRow to the index
index = index + minRow; index = index + minRow;
if (rowLength === 0 || index < 0 || index >= maxRow) { if (rowLength === 0 || index < 0 || index >= maxRow) {

View File

@@ -7,6 +7,7 @@ import 'vs/css!./media/gridPanel';
import { attachTableStyler } from 'sql/platform/theme/common/styler'; import { attachTableStyler } from 'sql/platform/theme/common/styler';
import QueryRunner, { QueryGridDataProvider } from 'sql/workbench/services/query/common/queryRunner'; import QueryRunner, { QueryGridDataProvider } from 'sql/workbench/services/query/common/queryRunner';
import { ResultSetSummary, IColumn } from 'sql/workbench/services/query/common/query';
import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView'; import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView';
import { Table } from 'sql/base/browser/ui/table/table'; import { Table } from 'sql/base/browser/ui/table/table';
import { ScrollableSplitView, IView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview'; import { ScrollableSplitView, IView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview';
@@ -21,8 +22,6 @@ import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugi
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin'; import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { ITableStyles, ITableMouseEvent } from 'sql/base/browser/ui/table/interfaces'; import { ITableStyles, ITableMouseEvent } from 'sql/base/browser/ui/table/interfaces';
import * as azdata from 'azdata';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@@ -123,7 +122,7 @@ export class GridPanel extends Disposable {
} }
this.reset(); this.reset();
})); }));
this.addResultSet(this.runner.batchSets.reduce<azdata.ResultSetSummary[]>((p, e) => { this.addResultSet(this.runner.batchSets.reduce<ResultSetSummary[]>((p, e) => {
if (this.configurationService.getValue<boolean>('sql.results.streaming')) { if (this.configurationService.getValue<boolean>('sql.results.streaming')) {
p = p.concat(e.resultSetSummaries); p = p.concat(e.resultSetSummaries);
} else { } else {
@@ -141,8 +140,8 @@ export class GridPanel extends Disposable {
this.splitView.setScrollPosition(this.state.scrollPosition); this.splitView.setScrollPosition(this.state.scrollPosition);
} }
private onResultSet(resultSet: azdata.ResultSetSummary | azdata.ResultSetSummary[]) { private onResultSet(resultSet: ResultSetSummary | ResultSetSummary[]) {
let resultsToAdd: azdata.ResultSetSummary[]; let resultsToAdd: ResultSetSummary[];
if (!Array.isArray(resultSet)) { if (!Array.isArray(resultSet)) {
resultsToAdd = [resultSet]; resultsToAdd = [resultSet];
} else { } else {
@@ -170,8 +169,8 @@ export class GridPanel extends Disposable {
} }
} }
private updateResultSet(resultSet: azdata.ResultSetSummary | azdata.ResultSetSummary[]) { private updateResultSet(resultSet: ResultSetSummary | ResultSetSummary[]) {
let resultsToUpdate: azdata.ResultSetSummary[]; let resultsToUpdate: ResultSetSummary[];
if (!Array.isArray(resultSet)) { if (!Array.isArray(resultSet)) {
resultsToUpdate = [resultSet]; resultsToUpdate = [resultSet];
} else { } else {
@@ -203,7 +202,7 @@ export class GridPanel extends Disposable {
} }
} }
private addResultSet(resultSet: azdata.ResultSetSummary[]) { private addResultSet(resultSet: ResultSetSummary[]) {
let tables: GridTable<any>[] = []; let tables: GridTable<any>[] = [];
for (let set of resultSet) { for (let set of resultSet) {
@@ -316,7 +315,7 @@ export class GridPanel extends Disposable {
export interface IDataSet { export interface IDataSet {
rowCount: number; rowCount: number;
columnInfo: azdata.IDbColumn[]; columnInfo: IColumn[];
} }
export abstract class GridTableBase<T> extends Disposable implements IView { export abstract class GridTableBase<T> extends Disposable implements IView {
@@ -363,7 +362,7 @@ export abstract class GridTableBase<T> extends Disposable implements IView {
constructor( constructor(
state: GridTableState, state: GridTableState,
protected _resultSet: azdata.ResultSetSummary, protected _resultSet: ResultSetSummary,
protected contextMenuService: IContextMenuService, protected contextMenuService: IContextMenuService,
protected instantiationService: IInstantiationService, protected instantiationService: IInstantiationService,
protected editorService: IEditorService, protected editorService: IEditorService,
@@ -394,7 +393,7 @@ export abstract class GridTableBase<T> extends Disposable implements IView {
abstract get gridDataProvider(): IGridDataProvider; abstract get gridDataProvider(): IGridDataProvider;
public get resultSet(): azdata.ResultSetSummary { public get resultSet(): ResultSetSummary {
return this._resultSet; return this._resultSet;
} }
@@ -587,7 +586,7 @@ export abstract class GridTableBase<T> extends Disposable implements IView {
// handle if a showplan link was clicked // handle if a showplan link was clicked
if (column && (column.isXml || column.isJson)) { if (column && (column.isXml || column.isJson)) {
this.gridDataProvider.getRowData(event.cell.row, 1).then(async d => { this.gridDataProvider.getRowData(event.cell.row, 1).then(async d => {
let value = d.resultSubset.rows[0][event.cell.cell - 1]; let value = d.rows[0][event.cell.cell - 1];
let content = value.displayValue; let content = value.displayValue;
const input = this.untitledEditorService.create({ mode: column.isXml ? 'xml' : 'json', initialValue: content }); const input = this.untitledEditorService.create({ mode: column.isXml ? 'xml' : 'json', initialValue: content });
@@ -598,7 +597,7 @@ export abstract class GridTableBase<T> extends Disposable implements IView {
} }
} }
public updateResult(resultSet: azdata.ResultSetSummary) { public updateResult(resultSet: ResultSetSummary) {
this._resultSet = resultSet; this._resultSet = resultSet;
if (this.table && this.visible) { if (this.table && this.visible) {
this.dataProvider.length = resultSet.rowCount; this.dataProvider.length = resultSet.rowCount;
@@ -655,10 +654,10 @@ export abstract class GridTableBase<T> extends Disposable implements IView {
private loadData(offset: number, count: number): Thenable<T[]> { private loadData(offset: number, count: number): Thenable<T[]> {
return this.gridDataProvider.getRowData(offset, count).then(response => { return this.gridDataProvider.getRowData(offset, count).then(response => {
if (!response.resultSubset) { if (!response) {
return []; return [];
} }
return response.resultSubset.rows.map(r => { return response.rows.map(r => {
let dataWithSchema = {}; let dataWithSchema = {};
// skip the first column since its a number column // skip the first column since its a number column
for (let i = 1; i < this.columns.length; i++) { for (let i = 1; i < this.columns.length; i++) {
@@ -756,7 +755,7 @@ class GridTable<T> extends GridTableBase<T> {
private _gridDataProvider: IGridDataProvider; private _gridDataProvider: IGridDataProvider;
constructor( constructor(
private _runner: QueryRunner, private _runner: QueryRunner,
resultSet: azdata.ResultSetSummary, resultSet: ResultSetSummary,
state: GridTableState, state: GridTableState,
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService instantiationService: IInstantiationService, @IInstantiationService instantiationService: IInstantiationService,

View File

@@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/messagePanel'; import 'vs/css!./media/messagePanel';
import QueryRunner, { IQueryMessage } from 'sql/workbench/services/query/common/queryRunner'; import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import { IQueryMessage } from 'sql/workbench/services/query/common/query';
import { ISelectionData } from 'azdata';
import { ITreeRenderer, IDataSource, ITreeNode, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { ITreeRenderer, IDataSource, ITreeNode, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
@@ -32,6 +31,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor'; import { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IDataTreeViewState } from 'vs/base/browser/ui/tree/dataTree'; import { IDataTreeViewState } from 'vs/base/browser/ui/tree/dataTree';
import { IRange } from 'vs/editor/common/core/range';
export interface IResultMessageIntern { export interface IResultMessageIntern {
id?: string; id?: string;
@@ -39,8 +39,7 @@ export interface IResultMessageIntern {
isError: boolean; isError: boolean;
time?: string | Date; time?: string | Date;
message: string; message: string;
selection?: ISelectionData; range?: IRange;
} }
export interface IMessagePanelMessage { export interface IMessagePanelMessage {
@@ -49,7 +48,7 @@ export interface IMessagePanelMessage {
} }
export interface IMessagePanelBatchMessage extends IMessagePanelMessage { export interface IMessagePanelBatchMessage extends IMessagePanelMessage {
selection: ISelectionData; range: IRange;
time: string; time: string;
} }
@@ -267,7 +266,7 @@ class MessagePanelDelegate extends CachedListVirtualDelegate<IResultMessageInter
getTemplateId(element: IResultMessageIntern): string { getTemplateId(element: IResultMessageIntern): string {
if (element instanceof Model) { if (element instanceof Model) {
return TemplateIds.MODEL; return TemplateIds.MODEL;
} else if (element.selection) { } else if (element.range) {
return TemplateIds.BATCH; return TemplateIds.BATCH;
} else if (element.isError) { } else if (element.isError) {
return TemplateIds.ERROR; return TemplateIds.ERROR;
@@ -322,14 +321,13 @@ class BatchMessageRenderer implements ITreeRenderer<IResultMessageIntern, void,
} }
templateData.timeStamp.innerText = (node.element.time as Date).toLocaleTimeString(); templateData.timeStamp.innerText = (node.element.time as Date).toLocaleTimeString();
templateData.message.innerText = node.element.message; templateData.message.innerText = node.element.message;
if (node.element.selection) { if (node.element.range) {
const selection = { endColumn: node.element.selection.endColumn + 1, endLineNumber: node.element.selection.endLine + 1, startColumn: node.element.selection.startColumn + 1, startLineNumber: node.element.selection.startLine + 1 };
templateData.disposable.add(addStandardDisposableGenericMouseDownListner(templateData.message, () => { templateData.disposable.add(addStandardDisposableGenericMouseDownListner(templateData.message, () => {
let editor = this.editorService.activeEditorPane as QueryEditor; let editor = this.editorService.activeEditorPane as QueryEditor;
const codeEditor = <ICodeEditor>editor.getControl(); const codeEditor = <ICodeEditor>editor.getControl();
codeEditor.focus(); codeEditor.focus();
codeEditor.setSelection(selection); codeEditor.setSelection(node.element.range);
codeEditor.revealRangeInCenterIfOutsideViewport(selection); codeEditor.revealRangeInCenterIfOutsideViewport(node.element.range);
})); }));
} }
} }

View File

@@ -14,7 +14,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import { append, $ } from 'vs/base/browser/dom'; import { append, $ } from 'vs/base/browser/dom';
import { ISelectionData, QueryExecutionOptions } from 'azdata'; import { QueryExecutionOptions } from 'azdata';
import { import {
IConnectionManagementService, IConnectionManagementService,
IConnectionParams, IConnectionParams,
@@ -43,6 +43,7 @@ import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilit
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement'; import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { IRange } from 'vs/editor/common/core/range';
/** /**
* Action class that query-based Actions will extend. This base class automatically handles activating and * Action class that query-based Actions will extend. This base class automatically handles activating and
@@ -101,12 +102,12 @@ export abstract class QueryTaskbarAction extends Action {
* Connects the given editor to it's current URI. * Connects the given editor to it's current URI.
* Public for testing only. * Public for testing only.
*/ */
protected connectEditor(editor: QueryEditor, runQueryOnCompletion?: RunQueryOnConnectionMode, selection?: ISelectionData): void { protected connectEditor(editor: QueryEditor, runQueryOnCompletion?: RunQueryOnConnectionMode, range?: IRange): void {
let params: INewConnectionParams = { let params: INewConnectionParams = {
input: editor.input, input: editor.input,
connectionType: ConnectionType.editor, connectionType: ConnectionType.editor,
runQueryOnCompletion: runQueryOnCompletion ? runQueryOnCompletion : RunQueryOnConnectionMode.none, runQueryOnCompletion: runQueryOnCompletion ? runQueryOnCompletion : RunQueryOnConnectionMode.none,
querySelection: selection queryRange: range
}; };
this.connectionManagementService.showConnectionDialog(params); this.connectionManagementService.showConnectionDialog(params);
} }
@@ -241,8 +242,8 @@ export class RunQueryAction extends QueryTaskbarAction {
if (this.isConnected(editor)) { if (this.isConnected(editor)) {
// if the selection isn't empty then execute the selection // if the selection isn't empty then execute the selection
// otherwise, either run the statement or the script depending on parameter // otherwise, either run the statement or the script depending on parameter
let selection: ISelectionData = editor.getSelection(false); let selection = editor.getSelection();
if (runCurrentStatement && selection && this.isCursorPosition(selection)) { if (runCurrentStatement && selection) {
editor.input.runQueryStatement(selection); editor.input.runQueryStatement(selection);
} else { } else {
// get the selection again this time with trimming // get the selection again this time with trimming
@@ -251,11 +252,6 @@ export class RunQueryAction extends QueryTaskbarAction {
} }
} }
} }
protected isCursorPosition(selection: ISelectionData) {
return selection.startLine === selection.endLine
&& selection.startColumn === selection.endColumn;
}
} }
/** /**

View File

@@ -23,7 +23,6 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor
import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle'; import { DisposableStore } from 'vs/base/common/lifecycle';
import { ISelectionData } from 'azdata';
import { IActionViewItem, IAction } from 'vs/base/common/actions'; import { IActionViewItem, IAction } from 'vs/base/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
@@ -36,6 +35,7 @@ import { QueryResultsEditor } from 'sql/workbench/contrib/query/browser/queryRes
import * as queryContext from 'sql/workbench/contrib/query/common/queryContext'; import * as queryContext from 'sql/workbench/contrib/query/common/queryContext';
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar'; import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
import * as actions from 'sql/workbench/contrib/query/browser/queryActions'; import * as actions from 'sql/workbench/contrib/query/browser/queryActions';
import { IRange } from 'vs/editor/common/core/range';
const QUERY_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'queryEditorViewState'; const QUERY_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'queryEditorViewState';
@@ -499,7 +499,7 @@ export class QueryEditor extends BaseEditor {
* Returns the underlying SQL editor's text selection in a 0-indexed format. Returns undefined if there * Returns the underlying SQL editor's text selection in a 0-indexed format. Returns undefined if there
* is no selected text. * is no selected text.
*/ */
public getSelection(checkIfRange: boolean = true): ISelectionData { public getSelection(checkIfRange: boolean = true): IRange {
if (this.currentTextEditor && this.currentTextEditor.getControl()) { if (this.currentTextEditor && this.currentTextEditor.getControl()) {
let vscodeSelection = this.currentTextEditor.getControl().getSelection(); let vscodeSelection = this.currentTextEditor.getControl().getSelection();
@@ -508,13 +508,7 @@ export class QueryEditor extends BaseEditor {
!(vscodeSelection.getStartPosition().lineNumber === vscodeSelection.getEndPosition().lineNumber && !(vscodeSelection.getStartPosition().lineNumber === vscodeSelection.getEndPosition().lineNumber &&
vscodeSelection.getStartPosition().column === vscodeSelection.getEndPosition().column); vscodeSelection.getStartPosition().column === vscodeSelection.getEndPosition().column);
if (!checkIfRange || isRange) { if (!checkIfRange || isRange) {
let sqlToolsServiceSelection: ISelectionData = { return vscodeSelection;
startLine: vscodeSelection.getStartPosition().lineNumber - 1,
startColumn: vscodeSelection.getStartPosition().column - 1,
endLine: vscodeSelection.getEndPosition().lineNumber - 1,
endColumn: vscodeSelection.getEndPosition().column - 1,
};
return sqlToolsServiceSelection;
} }
} }
@@ -522,7 +516,7 @@ export class QueryEditor extends BaseEditor {
return undefined; return undefined;
} }
public getAllSelection(): ISelectionData { public getAllSelection(): IRange {
if (this.currentTextEditor && this.currentTextEditor.getControl()) { if (this.currentTextEditor && this.currentTextEditor.getControl()) {
let control = this.currentTextEditor.getControl(); let control = this.currentTextEditor.getControl();
let codeEditor: ICodeEditor = <ICodeEditor>control; let codeEditor: ICodeEditor = <ICodeEditor>control;
@@ -530,13 +524,12 @@ export class QueryEditor extends BaseEditor {
let model = codeEditor.getModel(); let model = codeEditor.getModel();
let totalLines = model.getLineCount(); let totalLines = model.getLineCount();
let endColumn = model.getLineMaxColumn(totalLines); let endColumn = model.getLineMaxColumn(totalLines);
let selection: ISelectionData = { return {
startLine: 0, startLineNumber: 1,
startColumn: 0, startColumn: 1,
endLine: totalLines - 1, endLineNumber: totalLines,
endColumn: endColumn - 1, endColumn: endColumn,
}; };
return selection;
} }
} }
return undefined; return undefined;

View File

@@ -5,8 +5,6 @@
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { ISelectionData } from 'azdata';
import { import {
IConnectionParams, IConnectionParams,
INewConnectionParams, INewConnectionParams,
@@ -34,6 +32,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { IRange } from 'vs/editor/common/core/range';
suite('SQL QueryAction Tests', () => { suite('SQL QueryAction Tests', () => {
@@ -222,9 +221,9 @@ suite('SQL QueryAction Tests', () => {
let countCalledShowDialog: number = 0; let countCalledShowDialog: number = 0;
let countCalledRunQuery: number = 0; let countCalledRunQuery: number = 0;
let showDialogConnectionParams: INewConnectionParams = undefined; let showDialogConnectionParams: INewConnectionParams = undefined;
let runQuerySelection: ISelectionData = undefined; let runQuerySelection: IRange = undefined;
let selectionToReturnInGetSelection: ISelectionData = undefined; let selectionToReturnInGetSelection: IRange = undefined;
let predefinedSelection: ISelectionData = { startLine: 1, startColumn: 2, endLine: 3, endColumn: 4 }; let predefinedSelection: IRange = { startLineNumber: 1, startColumn: 2, endLineNumber: 3, endColumn: 4 };
// ... Mock "getSelection" in QueryEditor // ... Mock "getSelection" in QueryEditor
const workbenchinstantiationService = workbenchInstantiationService(); const workbenchinstantiationService = workbenchInstantiationService();
@@ -234,11 +233,11 @@ suite('SQL QueryAction Tests', () => {
let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Loose, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object); let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Loose, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object);
queryInput.setup(x => x.uri).returns(() => testUri); queryInput.setup(x => x.uri).returns(() => testUri);
queryInput.setup(x => x.runQuery(TypeMoq.It.isAny())).callback((selection: ISelectionData) => { queryInput.setup(x => x.runQuery(TypeMoq.It.isAny())).callback((selection: IRange) => {
runQuerySelection = selection; runQuerySelection = selection;
countCalledRunQuery++; countCalledRunQuery++;
}); });
queryInput.setup(x => x.runQuery(undefined)).callback((selection: ISelectionData) => { queryInput.setup(x => x.runQuery(undefined)).callback((selection: IRange) => {
runQuerySelection = selection; runQuerySelection = selection;
countCalledRunQuery++; countCalledRunQuery++;
}); });
@@ -277,7 +276,7 @@ suite('SQL QueryAction Tests', () => {
assert.equal(countCalledShowDialog, 1, 'run should call showDialog'); assert.equal(countCalledShowDialog, 1, 'run should call showDialog');
assert.equal(countCalledRunQuery, 0, 'run should not call runQuery'); assert.equal(countCalledRunQuery, 0, 'run should not call runQuery');
assert.equal(showDialogConnectionParams.connectionType, ConnectionType.editor, 'connectionType should be queryEditor'); assert.equal(showDialogConnectionParams.connectionType, ConnectionType.editor, 'connectionType should be queryEditor');
assert.equal(showDialogConnectionParams.querySelection, undefined, 'querySelection should be undefined'); assert.equal(showDialogConnectionParams.queryRange, undefined, 'querySelection should be undefined');
////// If I call run on RunQueryAction while disconnected and with a defined selection ////// If I call run on RunQueryAction while disconnected and with a defined selection
isConnected = false; isConnected = false;
@@ -288,11 +287,11 @@ suite('SQL QueryAction Tests', () => {
assert.equal(countCalledShowDialog, 2, 'run should call showDialog again'); assert.equal(countCalledShowDialog, 2, 'run should call showDialog again');
assert.equal(countCalledRunQuery, 0, 'run should not call runQuery'); assert.equal(countCalledRunQuery, 0, 'run should not call runQuery');
assert.equal(showDialogConnectionParams.connectionType, ConnectionType.editor, 'connectionType should be queryEditor'); assert.equal(showDialogConnectionParams.connectionType, ConnectionType.editor, 'connectionType should be queryEditor');
assert.notEqual(showDialogConnectionParams.querySelection, undefined, 'There should not be an undefined selection in runQuery'); assert.notEqual(showDialogConnectionParams.queryRange, undefined, 'There should not be an undefined selection in runQuery');
assert.equal(showDialogConnectionParams.querySelection.startLine, selectionToReturnInGetSelection.startLine, 'startLine should match'); assert.equal(showDialogConnectionParams.queryRange.startLineNumber, selectionToReturnInGetSelection.startLineNumber, 'startLine should match');
assert.equal(showDialogConnectionParams.querySelection.startColumn, selectionToReturnInGetSelection.startColumn, 'startColumn should match'); assert.equal(showDialogConnectionParams.queryRange.startColumn, selectionToReturnInGetSelection.startColumn, 'startColumn should match');
assert.equal(showDialogConnectionParams.querySelection.endLine, selectionToReturnInGetSelection.endLine, 'endLine should match'); assert.equal(showDialogConnectionParams.queryRange.endLineNumber, selectionToReturnInGetSelection.endLineNumber, 'endLine should match');
assert.equal(showDialogConnectionParams.querySelection.endColumn, selectionToReturnInGetSelection.endColumn, 'endColumn should match'); assert.equal(showDialogConnectionParams.queryRange.endColumn, selectionToReturnInGetSelection.endColumn, 'endColumn should match');
////// If I call run on RunQueryAction while connected and with an undefined selection ////// If I call run on RunQueryAction while connected and with an undefined selection
isConnected = true; isConnected = true;
@@ -313,9 +312,9 @@ suite('SQL QueryAction Tests', () => {
assert.equal(countCalledShowDialog, 2, 'run should not call showDialog'); assert.equal(countCalledShowDialog, 2, 'run should not call showDialog');
assert.equal(countCalledRunQuery, 2, 'run should call runQuery again'); assert.equal(countCalledRunQuery, 2, 'run should call runQuery again');
assert.notEqual(runQuerySelection, undefined, 'There should not be an undefined selection in runQuery'); assert.notEqual(runQuerySelection, undefined, 'There should not be an undefined selection in runQuery');
assert.equal(runQuerySelection.startLine, selectionToReturnInGetSelection.startLine, 'startLine should match'); assert.equal(runQuerySelection.startLineNumber, selectionToReturnInGetSelection.startLineNumber, 'startLine should match');
assert.equal(runQuerySelection.startColumn, selectionToReturnInGetSelection.startColumn, 'startColumn should match'); assert.equal(runQuerySelection.startColumn, selectionToReturnInGetSelection.startColumn, 'startColumn should match');
assert.equal(runQuerySelection.endLine, selectionToReturnInGetSelection.endLine, 'endLine should match'); assert.equal(runQuerySelection.endLineNumber, selectionToReturnInGetSelection.endLineNumber, 'endLine should match');
assert.equal(runQuerySelection.endColumn, selectionToReturnInGetSelection.endColumn, 'endColumn should match'); assert.equal(runQuerySelection.endColumn, selectionToReturnInGetSelection.endColumn, 'endColumn should match');
}); });

View File

@@ -349,7 +349,7 @@ suite('SQL ConnectionManagementService tests', () => {
onConnectCanceled: undefined, onConnectCanceled: undefined,
uri: uri uri: uri
}, },
querySelection: undefined, queryRange: undefined,
runQueryOnCompletion: RunQueryOnConnectionMode.none runQueryOnCompletion: RunQueryOnConnectionMode.none
}, },
saveTheConnection: true, saveTheConnection: true,
@@ -456,7 +456,7 @@ suite('SQL ConnectionManagementService tests', () => {
onConnectCanceled: undefined, onConnectCanceled: undefined,
uri: uri1, uri: uri1,
}, },
querySelection: undefined, queryRange: undefined,
runQueryOnCompletion: RunQueryOnConnectionMode.none, runQueryOnCompletion: RunQueryOnConnectionMode.none,
isEditConnection: false isEditConnection: false
}, },
@@ -494,7 +494,7 @@ suite('SQL ConnectionManagementService tests', () => {
onConnectCanceled: undefined, onConnectCanceled: undefined,
uri: uri1 uri: uri1
}, },
querySelection: undefined, queryRange: undefined,
runQueryOnCompletion: RunQueryOnConnectionMode.none, runQueryOnCompletion: RunQueryOnConnectionMode.none,
isEditConnection: false isEditConnection: false
}, },
@@ -705,7 +705,7 @@ suite('SQL ConnectionManagementService tests', () => {
onConnectCanceled: undefined, onConnectCanceled: undefined,
uri: uri uri: uri
}, },
querySelection: undefined, queryRange: undefined,
runQueryOnCompletion: RunQueryOnConnectionMode.none runQueryOnCompletion: RunQueryOnConnectionMode.none
}, },
saveTheConnection: true, saveTheConnection: true,

View File

@@ -0,0 +1,123 @@
/*---------------------------------------------------------------------------------------------
* 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 nls from 'vs/nls';
import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import Severity from 'vs/base/common/severity';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { ILogService } from 'vs/platform/log/common/log';
import { Emitter } from 'vs/base/common/event';
export interface IEditSessionReadyEvent {
ownerUri: string;
success: boolean;
message: string;
}
export default class EditQueryRunner extends QueryRunner {
private readonly _onEditSessionReady = this._register(new Emitter<IEditSessionReadyEvent>());
public readonly onEditSessionReady = this._onEditSessionReady.event;
constructor(
public uri: string,
@INotificationService private readonly notificationService: INotificationService,
@IQueryManagementService queryManagementService: IQueryManagementService,
@IConfigurationService configurationService: IConfigurationService,
@IInstantiationService instantiationService: IInstantiationService,
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
@ILogService logService: ILogService
) {
super(uri, queryManagementService, configurationService, instantiationService, textResourcePropertiesService, logService);
}
/*
* Handle a session ready event for Edit Data
*/
public async 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
try {
await this.queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
// The query has started, so lets fire up the result pane
this._onQueryStart.fire();
this.queryManagementService.registerRunner(this, ownerUri);
} catch (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 async getEditRows(rowStart: number, numberOfRows: number): Promise<azdata.EditSubsetResult> {
let rowData: azdata.EditSubsetParams = {
ownerUri: this.uri,
rowCount: numberOfRows,
rowStartIndex: rowStart
};
const result = await this.queryManagementService.getEditRows(rowData);
if (!result.hasOwnProperty('rowCount')) {
let error = `Nothing returned from subset query`;
this.notificationService.notify({
severity: Severity.Error,
message: error
});
throw new Error(error);
}
return result;
}
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);
}
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);
}
}

View File

@@ -6,12 +6,11 @@
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import { IColumn, ICellValue, ResultSetSubset } from 'sql/workbench/services/query/common/query';
import * as Utils from 'sql/platform/connection/common/utils'; import * as Utils from 'sql/platform/connection/common/utils';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
import { resolveQueryFilePath } from '../common/insightsUtils'; import { resolveQueryFilePath } from '../common/insightsUtils';
import { DbCellValue, IDbColumn, QueryExecuteSubsetResult } from 'azdata';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import * as types from 'vs/base/common/types'; import * as types from 'vs/base/common/types';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
@@ -27,8 +26,8 @@ export class InsightsDialogController {
private _queryRunner: QueryRunner; private _queryRunner: QueryRunner;
private _connectionProfile: IConnectionProfile; private _connectionProfile: IConnectionProfile;
private _connectionUri: string; private _connectionUri: string;
private _columns: IDbColumn[]; private _columns: IColumn[];
private _rows: DbCellValue[][]; private _rows: ICellValue[][];
constructor( constructor(
private readonly _model: IInsightsDialogModel, private readonly _model: IInsightsDialogModel,
@@ -160,13 +159,13 @@ export class InsightsDialogController {
) { ) {
let resultset = batch.resultSetSummaries[0]; let resultset = batch.resultSetSummaries[0];
this._columns = resultset.columnInfo; this._columns = resultset.columnInfo;
let rows: QueryExecuteSubsetResult; let rows: ResultSetSubset;
try { try {
rows = await this._queryRunner.getQueryRows(0, resultset.rowCount, batch.id, resultset.id); rows = await this._queryRunner.getQueryRows(0, resultset.rowCount, batch.id, resultset.id);
} catch (e) { } catch (e) {
return Promise.reject(e); return Promise.reject(e);
} }
this._rows = rows.resultSubset.rows; this._rows = rows.rows;
this.updateModel(); this.updateModel();
} }
} }

View File

@@ -4,13 +4,13 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { InsightsDialogController } from 'sql/workbench/services/insights/browser/insightsDialogController'; import { InsightsDialogController } from 'sql/workbench/services/insights/browser/insightsDialogController';
import QueryRunner, { IQueryMessage } from 'sql/workbench/services/query/common/queryRunner'; import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import { IQueryMessage, BatchSummary, IColumn, ResultSetSubset } from 'sql/workbench/services/query/common/query';
import { ConnectionManagementService } from 'sql/workbench/services/connection/browser/connectionManagementService'; import { ConnectionManagementService } from 'sql/workbench/services/connection/browser/connectionManagementService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import * as azdata from 'azdata';
import { equal } from 'assert'; import { equal } from 'assert';
import { Mock, MockBehavior, It } from 'typemoq'; import { Mock, MockBehavior, It } from 'typemoq';
import { Emitter } from 'vs/base/common/event'; import { Emitter } from 'vs/base/common/event';
@@ -114,12 +114,12 @@ function getPrimedQueryRunner(data: string[][], columns: string[]): IPrimedQuery
querymock.setup(x => x.onQueryEnd).returns(x => emitter.event); querymock.setup(x => x.onQueryEnd).returns(x => emitter.event);
querymock.setup(x => x.onMessage).returns(x => new Emitter<[IQueryMessage]>().event); querymock.setup(x => x.onMessage).returns(x => new Emitter<[IQueryMessage]>().event);
querymock.setup(x => x.batchSets).returns(x => { querymock.setup(x => x.batchSets).returns(x => {
return <Array<azdata.BatchSummary>>[ return <Array<BatchSummary>>[
{ {
id: 0, id: 0,
resultSetSummaries: [ resultSetSummaries: [
{ {
columnInfo: <Array<azdata.IDbColumn>>columns.map(c => { return { columnName: c }; }), columnInfo: <Array<IColumn>>columns.map(c => { return { columnName: c }; }),
id: 0, id: 0,
rowCount: data.length rowCount: data.length
} }
@@ -129,11 +129,9 @@ function getPrimedQueryRunner(data: string[][], columns: string[]): IPrimedQuery
}); });
querymock.setup(x => x.getQueryRows(It.isAnyNumber(), It.isAnyNumber(), It.isAnyNumber(), It.isAnyNumber())) querymock.setup(x => x.getQueryRows(It.isAnyNumber(), It.isAnyNumber(), It.isAnyNumber(), It.isAnyNumber()))
.returns(x => Promise.resolve(<azdata.QueryExecuteSubsetResult>{ .returns(x => Promise.resolve(<ResultSetSubset>{
resultSubset: <azdata.ResultSetSubset>{ rowCount: data.length,
rowCount: data.length, rows: data.map(r => r.map(c => { return { displayValue: c }; }))
rows: data.map(r => r.map(c => { return { displayValue: c }; }))
}
})); }));
querymock.setup(x => x.runQuery(It.isAnyString())).returns(x => Promise.resolve()); querymock.setup(x => x.runQuery(It.isAnyString())).returns(x => Promise.resolve());

View File

@@ -3,9 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { nb, QueryExecuteSubsetResult, IDbColumn, BatchSummary, IResultMessage, ResultSetSummary } from 'azdata'; import { nb, IResultMessage } from 'azdata';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import { BatchSummary, ResultSetSummary, IColumn, ResultSetSubset } from 'sql/workbench/services/query/common/query';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
@@ -343,7 +344,7 @@ class SqlKernel extends Disposable implements nb.IKernel {
this._register(queryRunner.onMessage(messages => { this._register(queryRunner.onMessage(messages => {
// TODO handle showing a messages output (should be updated with all messages, only changing 1 output in total) // TODO handle showing a messages output (should be updated with all messages, only changing 1 output in total)
for (const message of messages) { for (const message of messages) {
if (this._future && isUndefinedOrNull(message.selection)) { if (this._future && isUndefinedOrNull(message.range)) {
this._future.handleMessage(message); this._future.handleMessage(message);
} }
} }
@@ -383,7 +384,7 @@ export class SQLFuture extends Disposable implements FutureInternal {
private doneDeferred = new Deferred<nb.IShellMessage>(); private doneDeferred = new Deferred<nb.IShellMessage>();
private configuredMaxRows: number = MAX_ROWS; private configuredMaxRows: number = MAX_ROWS;
private _outputAddedPromises: Promise<void>[] = []; private _outputAddedPromises: Promise<void>[] = [];
private _querySubsetResultMap: Map<number, QueryExecuteSubsetResult> = new Map<number, QueryExecuteSubsetResult>(); private _querySubsetResultMap: Map<number, ResultSetSubset> = new Map<number, ResultSetSubset>();
private _errorOccurred: boolean = false; private _errorOccurred: boolean = false;
private _stopOnError: boolean = true; private _stopOnError: boolean = true;
constructor( constructor(
@@ -508,11 +509,11 @@ export class SQLFuture extends Disposable implements FutureInternal {
this._querySubsetResultMap.set(resultSet.id, result); this._querySubsetResultMap.set(resultSet.id, result);
deferred.resolve(); deferred.resolve();
}, (err) => { }, (err) => {
this._querySubsetResultMap.set(resultSet.id, { message: '', resultSubset: { rowCount: 0, rows: [] } }); this._querySubsetResultMap.set(resultSet.id, { rowCount: 0, rows: [] });
deferred.reject(err); deferred.reject(err);
}); });
} else { } else {
this._querySubsetResultMap.set(resultSet.id, { message: '', resultSubset: { rowCount: 0, rows: [] } }); this._querySubsetResultMap.set(resultSet.id, { rowCount: 0, rows: [] });
deferred.resolve(); deferred.resolve();
} }
return deferred; return deferred;
@@ -525,7 +526,7 @@ export class SQLFuture extends Disposable implements FutureInternal {
} }
} }
private sendIOPubMessage(subsetResult: QueryExecuteSubsetResult, resultSet: ResultSetSummary): void { private sendIOPubMessage(subsetResult: ResultSetSubset, resultSet: ResultSetSummary): void {
let msg: nb.IIOPubMessage = { let msg: nb.IIOPubMessage = {
channel: 'iopub', channel: 'iopub',
type: 'iopub', type: 'iopub',
@@ -560,7 +561,7 @@ export class SQLFuture extends Disposable implements FutureInternal {
// no-op // no-op
} }
private convertToDataResource(columns: IDbColumn[], subsetResult: QueryExecuteSubsetResult): IDataResource { private convertToDataResource(columns: IColumn[], subsetResult: ResultSetSubset): IDataResource {
let columnsResources: IDataResourceSchema[] = []; let columnsResources: IDataResourceSchema[] = [];
columns.forEach(column => { columns.forEach(column => {
columnsResources.push({ name: escape(column.columnName) }); columnsResources.push({ name: escape(column.columnName) });
@@ -569,7 +570,7 @@ export class SQLFuture extends Disposable implements FutureInternal {
columnsFields.fields = columnsResources; columnsFields.fields = columnsResources;
return { return {
schema: columnsFields, schema: columnsFields,
data: subsetResult.resultSubset.rows.map(row => { data: subsetResult.rows.map(row => {
let rowObject: { [key: string]: any; } = {}; let rowObject: { [key: string]: any; } = {};
row.forEach((val, index) => { row.forEach((val, index) => {
rowObject[index] = val.displayValue; rowObject[index] = val.displayValue;
@@ -579,9 +580,9 @@ export class SQLFuture extends Disposable implements FutureInternal {
}; };
} }
private convertToHtmlTable(columns: IDbColumn[], d: QueryExecuteSubsetResult): string[] { private convertToHtmlTable(columns: IColumn[], d: ResultSetSubset): string[] {
// Adding 3 for <table>, column title rows, </table> // Adding 3 for <table>, column title rows, </table>
let htmlStringArr: string[] = new Array(d.resultSubset.rowCount + 3); let htmlStringArr: string[] = new Array(d.rowCount + 3);
htmlStringArr[0] = '<table>'; htmlStringArr[0] = '<table>';
if (columns.length > 0) { if (columns.length > 0) {
let columnHeaders = '<tr>'; let columnHeaders = '<tr>';
@@ -592,7 +593,7 @@ export class SQLFuture extends Disposable implements FutureInternal {
htmlStringArr[1] = columnHeaders; htmlStringArr[1] = columnHeaders;
} }
let i = 2; let i = 2;
for (const row of d.resultSubset.rows) { for (const row of d.rows) {
let rowData = '<tr>'; let rowData = '<tr>';
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) { for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
rowData += `<td>${escape(row[columnIndex].displayValue)}</td>`; rowData += `<td>${escape(row[columnIndex].displayValue)}</td>`;

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 * as types from 'vs/base/common/types';
import { SaveFormat } from 'sql/workbench/services/query/common/resultSerializer'; import { SaveFormat } from 'sql/workbench/services/query/common/resultSerializer';
import { ResultSetSubset } from 'sql/workbench/services/query/common/query';
export interface IGridDataProvider { export interface IGridDataProvider {
@@ -14,7 +14,7 @@ export interface IGridDataProvider {
* @param rowStart 0-indexed start row to retrieve data from * @param rowStart 0-indexed start row to retrieve data from
* @param numberOfRows total number of rows of data to retrieve * @param numberOfRows total number of rows of data to retrieve
*/ */
getRowData(rowStart: number, numberOfRows: number): Thenable<azdata.QueryExecuteSubsetResult>; getRowData(rowStart: number, numberOfRows: number): Thenable<ResultSetSubset>;
/** /**
* Sends a copy request to copy data to the clipboard * Sends a copy request to copy data to the clipboard
@@ -65,8 +65,8 @@ export async function getResultsString(provider: IGridDataProvider, selection: S
} }
} }
// Iterate over the rows to paste into the copy string // Iterate over the rows to paste into the copy string
for (let rowIndex: number = 0; rowIndex < result.resultSubset.rows.length; rowIndex++) { for (let rowIndex: number = 0; rowIndex < result.rows.length; rowIndex++) {
let row = result.resultSubset.rows[rowIndex]; let row = result.rows[rowIndex];
let cellObjects = row.slice(range.fromCell, (range.toCell + 1)); let cellObjects = row.slice(range.fromCell, (range.toCell + 1));
// Remove newlines if requested // Remove newlines if requested
let cells = provider.shouldRemoveNewLines() let cells = provider.shouldRemoveNewLines()

View File

@@ -0,0 +1,69 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IRange } from 'vs/editor/common/core/range';
export interface IColumn {
columnName: string;
isXml?: boolean;
isJson?: boolean;
}
export interface ResultSetSummary {
id: number;
batchId: number;
rowCount: number;
columnInfo: IColumn[];
complete: boolean;
}
export interface BatchStartSummary {
id: number;
executionStart: string;
range?: IRange;
}
export interface BatchSummary extends BatchStartSummary {
hasError: boolean;
resultSetSummaries: ResultSetSummary[];
}
export interface CompleteBatchSummary extends BatchSummary {
executionElapsed: string;
executionEnd: string;
}
export interface IQueryMessage {
batchId?: number;
isError: boolean;
time?: string;
message: string;
range?: IRange;
}
export interface IResultMessage {
batchId?: number;
isError: boolean;
time?: string;
message: string;
}
export interface QueryExecuteSubsetParams {
ownerUri: string;
batchIndex: number;
resultSetIndex: number;
rowsStartIndex: number;
rowsCount: number;
}
export interface ResultSetSubset {
rowCount: number;
rows: ICellValue[][];
}
export interface ICellValue {
displayValue: string;
isNull?: boolean;
}

View File

@@ -13,11 +13,24 @@ import { Event, Emitter } from 'vs/base/common/event';
import { keys } from 'vs/base/common/map'; import { keys } from 'vs/base/common/map';
import { assign } from 'vs/base/common/objects'; import { assign } from 'vs/base/common/objects';
import { IAdsTelemetryService, ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry'; import { IAdsTelemetryService, ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry';
import EditQueryRunner from 'sql/workbench/services/editData/common/editQueryRunner';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ResultSetSubset } from 'sql/workbench/services/query/common/query';
import { isUndefined } from 'vs/base/common/types';
export const SERVICE_ID = 'queryManagementService'; export const SERVICE_ID = 'queryManagementService';
export const IQueryManagementService = createDecorator<IQueryManagementService>(SERVICE_ID); export const IQueryManagementService = createDecorator<IQueryManagementService>(SERVICE_ID);
export interface QueryCancelResult {
messages: string;
}
export interface ExecutionPlanOptions {
displayEstimatedQueryPlan?: boolean;
displayActualQueryPlan?: boolean;
}
export interface IQueryManagementService { export interface IQueryManagementService {
_serviceBrand: undefined; _serviceBrand: undefined;
@@ -28,13 +41,13 @@ export interface IQueryManagementService {
getRegisteredProviders(): string[]; getRegisteredProviders(): string[];
registerRunner(runner: QueryRunner, uri: string): void; registerRunner(runner: QueryRunner, uri: string): void;
cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult>; cancelQuery(ownerUri: string): Promise<QueryCancelResult>;
runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void>; runQuery(ownerUri: string, range: IRange, runOptions?: ExecutionPlanOptions): Promise<void>;
runQueryStatement(ownerUri: string, line: number, column: number): Promise<void>; runQueryStatement(ownerUri: string, line: number, column: number): Promise<void>;
runQueryString(ownerUri: string, queryString: string): Promise<void>; runQueryString(ownerUri: string, queryString: string): Promise<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult>; runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult>;
parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult>; parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult>;
getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<azdata.QueryExecuteSubsetResult>; getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<ResultSetSubset>;
disposeQuery(ownerUri: string): Promise<void>; disposeQuery(ownerUri: string): Promise<void>;
saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult>; saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult>;
setQueryExecutionOptions(uri: string, options: azdata.QueryExecutionOptions): Promise<void>; setQueryExecutionOptions(uri: string, options: azdata.QueryExecutionOptions): Promise<void>;
@@ -67,7 +80,7 @@ export interface IQueryManagementService {
*/ */
export interface IQueryRequestHandler { export interface IQueryRequestHandler {
cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult>; cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult>;
runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void>; runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: ExecutionPlanOptions): Promise<void>;
runQueryStatement(ownerUri: string, line: number, column: number): Promise<void>; runQueryStatement(ownerUri: string, line: number, column: number): Promise<void>;
runQueryString(ownerUri: string, queryString: string): Promise<void>; runQueryString(ownerUri: string, queryString: string): Promise<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult>; runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult>;
@@ -129,7 +142,7 @@ export class QueryManagementService implements IQueryManagementService {
// Handles logic to run the given handlerCallback at the appropriate time. If the given runner is // 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 // undefined, the handlerCallback is put on the _handlerCallbackQueue to be run once the runner is set
// public for testing only // public for testing only
private enqueueOrRun(handlerCallback: (runnerParam: QueryRunner) => void, runner: QueryRunner): void { private enqueueOrRun(handlerCallback: (runnerParam: QueryRunner) => void, runner?: QueryRunner): void {
if (runner === undefined) { if (runner === undefined) {
this._handlerCallbackQueue.push(handlerCallback); this._handlerCallbackQueue.push(handlerCallback);
} else { } else {
@@ -137,9 +150,9 @@ export class QueryManagementService implements IQueryManagementService {
} }
} }
private _notify(ownerUri: string, sendNotification: (runner: QueryRunner) => void): void { private _notify(ownerUri: string, sendNotification: (runner: QueryRunner | EditQueryRunner) => void): void {
let runner = this._queryRunners.get(ownerUri); let runner = this._queryRunners.get(ownerUri);
this.enqueueOrRun(sendNotification, runner!); this.enqueueOrRun(sendNotification, runner);
} }
public addQueryRequestHandler(queryType: string, handler: IQueryRequestHandler): IDisposable { public addQueryRequestHandler(queryType: string, handler: IQueryRequestHandler): IDisposable {
@@ -165,7 +178,7 @@ export class QueryManagementService implements IQueryManagementService {
return Array.from(keys(this._requestHandlers)); return Array.from(keys(this._requestHandlers));
} }
private addTelemetry(eventName: string, ownerUri: string, runOptions?: azdata.ExecutionPlanOptions): void { private addTelemetry(eventName: string, ownerUri: string, runOptions?: ExecutionPlanOptions): void {
const providerId: string = this._connectionService.getProviderIdFromUri(ownerUri); const providerId: string = this._connectionService.getProviderIdFromUri(ownerUri);
const data: ITelemetryEventProperties = { const data: ITelemetryEventProperties = {
provider: providerId, provider: providerId,
@@ -197,51 +210,59 @@ export class QueryManagementService implements IQueryManagementService {
} }
} }
public cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult> { public cancelQuery(ownerUri: string): Promise<QueryCancelResult> {
this.addTelemetry(TelemetryKeys.CancelQuery, ownerUri); this.addTelemetry(TelemetryKeys.CancelQuery, ownerUri);
return this._runAction(ownerUri, (runner) => { return this._runAction(ownerUri, (runner) => {
return runner.cancelQuery(ownerUri); return runner.cancelQuery(ownerUri);
}); });
} }
public runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
public runQuery(ownerUri: string, range?: IRange, runOptions?: ExecutionPlanOptions): Promise<void> {
this.addTelemetry(TelemetryKeys.RunQuery, ownerUri, runOptions); this.addTelemetry(TelemetryKeys.RunQuery, ownerUri, runOptions);
return this._runAction(ownerUri, (runner) => { return this._runAction(ownerUri, (runner) => {
return runner.runQuery(ownerUri, selection, runOptions); return runner.runQuery(ownerUri, rangeToSelectionData(range), runOptions);
}); });
} }
public runQueryStatement(ownerUri: string, line: number, column: number): Promise<void> { public runQueryStatement(ownerUri: string, line: number, column: number): Promise<void> {
this.addTelemetry(TelemetryKeys.RunQueryStatement, ownerUri); this.addTelemetry(TelemetryKeys.RunQueryStatement, ownerUri);
return this._runAction(ownerUri, (runner) => { return this._runAction(ownerUri, (runner) => {
return runner.runQueryStatement(ownerUri, line, column); return runner.runQueryStatement(ownerUri, line - 1, column - 1); // we are taking in a vscode IRange which is 1 indexed, but our api expected a 0 index
}); });
} }
public runQueryString(ownerUri: string, queryString: string): Promise<void> { public runQueryString(ownerUri: string, queryString: string): Promise<void> {
this.addTelemetry(TelemetryKeys.RunQueryString, ownerUri); this.addTelemetry(TelemetryKeys.RunQueryString, ownerUri);
return this._runAction(ownerUri, (runner) => { return this._runAction(ownerUri, (runner) => {
return runner.runQueryString(ownerUri, queryString); return runner.runQueryString(ownerUri, queryString);
}); });
} }
public runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult> { public runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult> {
return this._runAction(ownerUri, (runner) => { return this._runAction(ownerUri, (runner) => {
return runner.runQueryAndReturn(ownerUri, queryString); return runner.runQueryAndReturn(ownerUri, queryString);
}); });
} }
public parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult> { public parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult> {
return this._runAction(ownerUri, (runner) => { return this._runAction(ownerUri, (runner) => {
return runner.parseSyntax(ownerUri, query); return runner.parseSyntax(ownerUri, query);
}); });
} }
public getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<azdata.QueryExecuteSubsetResult> {
public async getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<ResultSetSubset> {
return this._runAction(rowData.ownerUri, (runner) => { return this._runAction(rowData.ownerUri, (runner) => {
return runner.getQueryRows(rowData); return runner.getQueryRows(rowData).then(r => r.resultSubset);
}); });
} }
public disposeQuery(ownerUri: string): Promise<void> { public disposeQuery(ownerUri: string): Promise<void> {
this._queryRunners.delete(ownerUri); this._queryRunners.delete(ownerUri);
return this._runAction(ownerUri, (runner) => { return this._runAction(ownerUri, (runner) => {
return runner.disposeQuery(ownerUri); return runner.disposeQuery(ownerUri);
}); });
} }
public setQueryExecutionOptions(ownerUri: string, options: azdata.QueryExecutionOptions): Promise<void> { public setQueryExecutionOptions(ownerUri: string, options: azdata.QueryExecutionOptions): Promise<void> {
return this._runAction(ownerUri, (runner) => { return this._runAction(ownerUri, (runner) => {
return runner.setQueryExecutionOptions(ownerUri, options); return runner.setQueryExecutionOptions(ownerUri, options);
@@ -256,37 +277,38 @@ export class QueryManagementService implements IQueryManagementService {
public onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void { public onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void {
this._notify(result.ownerUri, (runner: QueryRunner) => { this._notify(result.ownerUri, (runner: QueryRunner) => {
runner.handleQueryComplete(result); runner.handleQueryComplete(result.batchSummaries.map(s => ({ ...s, range: selectionDataToRange(s.selection) })));
}); });
} }
public onBatchStart(batchInfo: azdata.QueryExecuteBatchNotificationParams): void { public onBatchStart(batchInfo: azdata.QueryExecuteBatchNotificationParams): void {
this._notify(batchInfo.ownerUri, (runner: QueryRunner) => { this._notify(batchInfo.ownerUri, (runner: QueryRunner) => {
runner.handleBatchStart(batchInfo); runner.handleBatchStart({ ...batchInfo.batchSummary, range: selectionDataToRange(batchInfo.batchSummary.selection) });
}); });
} }
public onBatchComplete(batchInfo: azdata.QueryExecuteBatchNotificationParams): void { public onBatchComplete(batchInfo: azdata.QueryExecuteBatchNotificationParams): void {
this._notify(batchInfo.ownerUri, (runner: QueryRunner) => { this._notify(batchInfo.ownerUri, (runner: QueryRunner) => {
runner.handleBatchComplete(batchInfo); runner.handleBatchComplete({ range: selectionDataToRange(batchInfo.batchSummary.selection), ...batchInfo.batchSummary });
}); });
} }
public onResultSetAvailable(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void { public onResultSetAvailable(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void {
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => { this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
runner.handleResultSetAvailable(resultSetInfo); runner.handleResultSetAvailable(resultSetInfo.resultSetSummary);
}); });
} }
public onResultSetUpdated(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void { public onResultSetUpdated(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void {
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => { this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
runner.handleResultSetUpdated(resultSetInfo); runner.handleResultSetUpdated(resultSetInfo.resultSetSummary);
}); });
} }
public onMessage(messagesMap: Map<string, azdata.QueryExecuteMessageParams[]>): void { public onMessage(messagesMap: Map<string, azdata.QueryExecuteMessageParams[]>): void {
for (const [uri, messages] of messagesMap) { for (const [uri, messages] of messagesMap) {
this._notify(uri, (runner: QueryRunner) => { this._notify(uri, (runner: QueryRunner) => {
runner.handleMessage(messages); runner.handleMessage(messages.map(m => m.message));
}); });
} }
} }
@@ -299,8 +321,8 @@ export class QueryManagementService implements IQueryManagementService {
} }
public onEditSessionReady(ownerUri: string, success: boolean, message: string): void { public onEditSessionReady(ownerUri: string, success: boolean, message: string): void {
this._notify(ownerUri, (runner: QueryRunner) => { this._notify(ownerUri, runner => {
runner.handleEditSessionReady(ownerUri, success, message); (runner as EditQueryRunner).handleEditSessionReady(ownerUri, success, message);
}); });
} }
@@ -352,3 +374,11 @@ export class QueryManagementService implements IQueryManagementService {
}); });
} }
} }
function selectionDataToRange(selection?: azdata.ISelectionData): IRange | undefined {
return isUndefined(selection) ? undefined : new Range(selection.startLine + 1, selection.startColumn + 1, selection.endLine + 1, selection.endColumn + 1);
}
function rangeToSelectionData(range?: IRange): azdata.ISelectionData | undefined {
return isUndefined(range) ? undefined : { startLine: range.startLineNumber - 1, startColumn: range.startColumn - 1, endLine: range.endLineNumber - 1, endColumn: range.endColumn - 1 };
}

View File

@@ -3,13 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import QueryRunner, { IQueryMessage } from 'sql/workbench/services/query/common/queryRunner'; import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import { IQueryMessage, ResultSetSubset } from 'sql/workbench/services/query/common/query';
import { DataService } from 'sql/workbench/services/query/common/dataService'; import { DataService } from 'sql/workbench/services/query/common/dataService';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { import {
ISelectionData,
ResultSetSubset,
EditUpdateCellResult, EditUpdateCellResult,
EditSessionReadyParams, EditSessionReadyParams,
EditSubsetResult, EditSubsetResult,
@@ -19,6 +18,7 @@ import {
queryeditor queryeditor
} from 'azdata'; } from 'azdata';
import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService'; import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService';
import { IRange } from 'vs/editor/common/core/range';
export const SERVICE_ID = 'queryModelService'; export const SERVICE_ID = 'queryModelService';
@@ -31,7 +31,7 @@ export interface IQueryPlanInfo {
} }
export interface IQueryInfo { export interface IQueryInfo {
selection: ISelectionData[]; range: IRange[];
messages: IQueryMessage[]; messages: IQueryMessage[];
} }
@@ -51,8 +51,8 @@ export interface IQueryModelService {
getQueryRunner(uri: string): QueryRunner | undefined; getQueryRunner(uri: string): QueryRunner | undefined;
getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<ResultSetSubset | undefined>; getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<ResultSetSubset | undefined>;
runQuery(uri: string, selection: ISelectionData | undefined, runOptions?: ExecutionPlanOptions): void; runQuery(uri: string, range: IRange | undefined, runOptions?: ExecutionPlanOptions): void;
runQueryStatement(uri: string, selection: ISelectionData | undefined): void; runQueryStatement(uri: string, range: IRange | undefined): void;
runQueryString(uri: string, selection: string | undefined): void; runQueryString(uri: string, selection: string | undefined): void;
cancelQuery(input: QueryRunner | string): void; cancelQuery(input: QueryRunner | string): void;
disposeQuery(uri: string): void; disposeQuery(uri: string): void;

View File

@@ -5,6 +5,7 @@
import * as GridContentEvents from 'sql/workbench/services/query/common/gridContentEvents'; import * as GridContentEvents from 'sql/workbench/services/query/common/gridContentEvents';
import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import { ResultSetSubset } from 'sql/workbench/services/query/common/query';
import { DataService } from 'sql/workbench/services/query/common/dataService'; import { DataService } from 'sql/workbench/services/query/common/dataService';
import { IQueryModelService, IQueryEvent } from 'sql/workbench/services/query/common/queryModel'; import { IQueryModelService, IQueryEvent } from 'sql/workbench/services/query/common/queryModel';
@@ -17,6 +18,8 @@ import * as strings from 'vs/base/common/strings';
import * as types from 'vs/base/common/types'; import * as types from 'vs/base/common/types';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import EditQueryRunner from 'sql/workbench/services/editData/common/editQueryRunner';
import { IRange } from 'vs/editor/common/core/range';
const selectionSnippetMaxLen = 100; const selectionSnippetMaxLen = 100;
@@ -29,10 +32,10 @@ export interface QueryEvent {
* Holds information about the state of a query runner * Holds information about the state of a query runner
*/ */
export class QueryInfo { export class QueryInfo {
public queryRunner?: QueryRunner; public queryRunner?: EditQueryRunner;
public dataService?: DataService; public dataService?: DataService;
public queryEventQueue?: QueryEvent[]; public queryEventQueue?: QueryEvent[];
public selection?: Array<azdata.ISelectionData>; public range?: Array<IRange>;
public selectionSnippet?: string; public selectionSnippet?: string;
// Notes if the angular components have obtained the DataService. If not, all messages sent // Notes if the angular components have obtained the DataService. If not, all messages sent
@@ -42,7 +45,7 @@ export class QueryInfo {
constructor() { constructor() {
this.dataServiceReady = false; this.dataServiceReady = false;
this.queryEventQueue = []; this.queryEventQueue = [];
this.selection = []; this.range = [];
} }
} }
@@ -128,10 +131,10 @@ export class QueryModelService implements IQueryModelService {
/** /**
* Get more data rows from the current resultSets from the service layer * 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> { public getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<ResultSetSubset | undefined> {
if (this._queryInfoMap.has(uri)) { if (this._queryInfoMap.has(uri)) {
return this._getQueryInfo(uri)!.queryRunner!.getQueryRows(rowStart, numberOfRows, batchId, resultId).then(results => { return this._getQueryInfo(uri)!.queryRunner!.getQueryRows(rowStart, numberOfRows, batchId, resultId).then(results => {
return results.resultSubset; return results;
}); });
} else { } else {
return Promise.resolve(undefined); return Promise.resolve(undefined);
@@ -170,15 +173,15 @@ export class QueryModelService implements IQueryModelService {
/** /**
* Run a query for the given URI with the given text selection * Run a query for the given URI with the given text selection
*/ */
public async runQuery(uri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void> { public async runQuery(uri: string, range: IRange, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
return this.doRunQuery(uri, selection, false, runOptions); return this.doRunQuery(uri, range, false, runOptions);
} }
/** /**
* Run the current SQL statement for the given URI * Run the current SQL statement for the given URI
*/ */
public async runQueryStatement(uri: string, selection: azdata.ISelectionData): Promise<void> { public async runQueryStatement(uri: string, range: IRange): Promise<void> {
return this.doRunQuery(uri, selection, true); return this.doRunQuery(uri, range, true);
} }
/** /**
@@ -191,7 +194,7 @@ export class QueryModelService implements IQueryModelService {
/** /**
* Run Query implementation * Run Query implementation
*/ */
private async doRunQuery(uri: string, selection: azdata.ISelectionData | string, private async doRunQuery(uri: string, range: IRange | string,
runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void> { runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
// Reuse existing query runner if it exists // Reuse existing query runner if it exists
let queryRunner: QueryRunner | undefined; let queryRunner: QueryRunner | undefined;
@@ -208,7 +211,7 @@ export class QueryModelService implements IQueryModelService {
// If the query is not in progress, we can reuse the query runner // If the query is not in progress, we can reuse the query runner
queryRunner = existingRunner!; queryRunner = existingRunner!;
info.selection = []; info.range = [];
info.selectionSnippet = undefined; info.selectionSnippet = undefined;
} else { } else {
// We do not have a query runner for this editor, so create a new one // We do not have a query runner for this editor, so create a new one
@@ -217,23 +220,23 @@ export class QueryModelService implements IQueryModelService {
queryRunner = info.queryRunner!; queryRunner = info.queryRunner!;
} }
if (types.isString(selection)) { if (types.isString(range)) {
// Run the query string in this case // Run the query string in this case
if (selection.length < selectionSnippetMaxLen) { if (range.length < selectionSnippetMaxLen) {
info.selectionSnippet = selection; info.selectionSnippet = range;
} else { } else {
info.selectionSnippet = selection.substring(0, selectionSnippetMaxLen - 3) + '...'; info.selectionSnippet = range.substring(0, selectionSnippetMaxLen - 3) + '...';
} }
return queryRunner.runQuery(selection, runOptions); return queryRunner.runQuery(range, runOptions);
} else if (runCurrentStatement) { } else if (runCurrentStatement) {
return queryRunner.runQueryStatement(selection); return queryRunner.runQueryStatement(range);
} else { } else {
return queryRunner.runQuery(selection, runOptions); return queryRunner.runQuery(range, runOptions);
} }
} }
private initQueryRunner(uri: string): QueryInfo { private initQueryRunner(uri: string): QueryInfo {
let queryRunner = this._instantiationService.createInstance(QueryRunner, uri); let queryRunner = this._instantiationService.createInstance(EditQueryRunner, uri);
let info = new QueryInfo(); let info = new QueryInfo();
queryRunner.onResultSet(e => { queryRunner.onResultSet(e => {
this._fireQueryEvent(uri, 'resultSet', e); this._fireQueryEvent(uri, 'resultSet', e);
@@ -241,14 +244,14 @@ export class QueryModelService implements IQueryModelService {
queryRunner.onBatchStart(b => { queryRunner.onBatchStart(b => {
let link = undefined; let link = undefined;
let messageText = nls.localize('runQueryBatchStartMessage', "Started executing query at "); let messageText = nls.localize('runQueryBatchStartMessage', "Started executing query at ");
if (b.selection) { if (b.range) {
if (info.selectionSnippet) { 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 // 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 // executed query text
messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet); messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet);
} else { } else {
link = { link = {
text: strings.format(nls.localize('runQueryBatchStartLine', "Line {0}"), b.selection.startLine + 1) text: strings.format(nls.localize('runQueryBatchStartLine', "Line {0}"), b.range.startLineNumber)
}; };
} }
} }
@@ -260,7 +263,7 @@ export class QueryModelService implements IQueryModelService {
link: link link: link
}; };
this._fireQueryEvent(uri, 'message', message); this._fireQueryEvent(uri, 'message', message);
info.selection!.push(this._validateSelection(b.selection)); info.range!.push(b.range);
}); });
queryRunner.onMessage(m => { queryRunner.onMessage(m => {
this._fireQueryEvent(uri, 'message', m); this._fireQueryEvent(uri, 'message', m);
@@ -274,7 +277,7 @@ export class QueryModelService implements IQueryModelService {
uri: uri, uri: uri,
queryInfo: queryInfo:
{ {
selection: info.selection!, range: info.range!,
messages: info.queryRunner!.messages messages: info.queryRunner!.messages
} }
}; };
@@ -292,7 +295,7 @@ export class QueryModelService implements IQueryModelService {
uri: uri, uri: uri,
queryInfo: queryInfo:
{ {
selection: info.selection!, range: info.range!,
messages: info.queryRunner!.messages messages: info.queryRunner!.messages
} }
}; };
@@ -308,7 +311,7 @@ export class QueryModelService implements IQueryModelService {
uri: uri, uri: uri,
queryInfo: queryInfo:
{ {
selection: info.selection!, range: info.range!,
messages: info.queryRunner!.messages messages: info.queryRunner!.messages
} }
}; };
@@ -324,7 +327,7 @@ export class QueryModelService implements IQueryModelService {
uri: planInfo.fileUri, uri: planInfo.fileUri,
queryInfo: queryInfo:
{ {
selection: info.selection!, range: info.range!,
messages: info.queryRunner!.messages messages: info.queryRunner!.messages
}, },
params: planInfo params: planInfo
@@ -338,7 +341,7 @@ export class QueryModelService implements IQueryModelService {
uri: uri, uri: uri,
queryInfo: queryInfo:
{ {
selection: info.selection!, range: info.range!,
messages: info.queryRunner!.messages messages: info.queryRunner!.messages
}, },
params: resultSetInfo params: resultSetInfo
@@ -399,12 +402,12 @@ export class QueryModelService implements IQueryModelService {
// EDIT DATA METHODS ///////////////////////////////////////////////////// // EDIT DATA METHODS /////////////////////////////////////////////////////
async initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> { async initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> {
// Reuse existing query runner if it exists // Reuse existing query runner if it exists
let queryRunner: QueryRunner; let queryRunner: EditQueryRunner;
let info: QueryInfo; let info: QueryInfo;
if (this._queryInfoMap.has(ownerUri)) { if (this._queryInfoMap.has(ownerUri)) {
info = this._getQueryInfo(ownerUri)!; info = this._getQueryInfo(ownerUri)!;
let existingRunner: QueryRunner = info.queryRunner!; let existingRunner = info.queryRunner!;
// If the initialization is already in progress // If the initialization is already in progress
if (existingRunner.isExecuting) { if (existingRunner.isExecuting) {
@@ -417,7 +420,7 @@ export class QueryModelService implements IQueryModelService {
// We do not have a query runner for this editor, so create a new one // We do not have a query runner for this editor, so create a new one
// and map it to the results uri // and map it to the results uri
queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri); queryRunner = this._instantiationService.createInstance(EditQueryRunner, ownerUri);
const resultSetEventType = 'resultSet'; const resultSetEventType = 'resultSet';
queryRunner.onResultSet(resultSet => { queryRunner.onResultSet(resultSet => {
this._fireQueryEvent(ownerUri, resultSetEventType, resultSet); this._fireQueryEvent(ownerUri, resultSetEventType, resultSet);
@@ -428,14 +431,14 @@ export class QueryModelService implements IQueryModelService {
queryRunner.onBatchStart(batch => { queryRunner.onBatchStart(batch => {
let link = undefined; let link = undefined;
let messageText = nls.localize('runQueryBatchStartMessage', "Started executing query at "); let messageText = nls.localize('runQueryBatchStartMessage', "Started executing query at ");
if (batch.selection) { if (batch.range) {
if (info.selectionSnippet) { 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 // 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 // executed query text
messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet); messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet);
} else { } else {
link = { link = {
text: strings.format(nls.localize('runQueryBatchStartLine', "Line {0}"), batch.selection.startLine + 1) text: strings.format(nls.localize('runQueryBatchStartLine', "Line {0}"), batch.range.startLineNumber)
}; };
} }
} }
@@ -459,7 +462,7 @@ export class QueryModelService implements IQueryModelService {
uri: ownerUri, uri: ownerUri,
queryInfo: queryInfo:
{ {
selection: info.selection!, range: info.range!,
messages: info.queryRunner!.messages messages: info.queryRunner!.messages
}, },
}; };
@@ -476,7 +479,7 @@ export class QueryModelService implements IQueryModelService {
uri: ownerUri, uri: ownerUri,
queryInfo: queryInfo:
{ {
selection: info.selection!, range: info.range!,
messages: info.queryRunner!.messages messages: info.queryRunner!.messages
}, },
}; };
@@ -596,8 +599,8 @@ export class QueryModelService implements IQueryModelService {
// PRIVATE METHODS ////////////////////////////////////////////////////// // PRIVATE METHODS //////////////////////////////////////////////////////
private internalGetQueryRunner(ownerUri: string): QueryRunner | undefined { private internalGetQueryRunner(ownerUri: string): EditQueryRunner | undefined {
let queryRunner: QueryRunner | undefined; let queryRunner: EditQueryRunner | undefined;
if (this._queryInfoMap.has(ownerUri)) { if (this._queryInfoMap.has(ownerUri)) {
let existingRunner = this._getQueryInfo(ownerUri)!.queryRunner!; let existingRunner = this._getQueryInfo(ownerUri)!.queryRunner!;
// If the query is not already executing then set it up // If the query is not already executing then set it up
@@ -648,17 +651,4 @@ export class QueryModelService implements IQueryModelService {
public _getQueryInfo(uri: string): QueryInfo | undefined { public _getQueryInfo(uri: string): QueryInfo | undefined {
return this._queryInfoMap.get(uri); 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;
}
} }

View File

@@ -3,15 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import { IQueryManagementService, QueryCancelResult, ExecutionPlanOptions } from 'sql/workbench/services/query/common/queryManagement';
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
import * as Utils from 'sql/platform/connection/common/utils'; import * as Utils from 'sql/platform/connection/common/utils';
import { Deferred } from 'sql/base/common/promise'; import { Deferred } from 'sql/base/common/promise';
import { IQueryPlanInfo } from 'sql/workbench/services/query/common/queryModel'; import { IQueryPlanInfo } from 'sql/workbench/services/query/common/queryModel';
import { ResultSerializer, SaveFormat } from 'sql/workbench/services/query/common/resultSerializer'; import { ResultSerializer, SaveFormat } from 'sql/workbench/services/query/common/resultSerializer';
import Severity from 'vs/base/common/severity';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import * as types from 'vs/base/common/types'; import * as types from 'vs/base/common/types';
@@ -27,20 +24,8 @@ import { IGridDataProvider, getResultsString } from 'sql/workbench/services/quer
import { getErrorMessage } from 'vs/base/common/errors'; import { getErrorMessage } from 'vs/base/common/errors';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { find } from 'vs/base/common/arrays'; import { find } from 'vs/base/common/arrays';
import { IRange, Range } from 'vs/editor/common/core/range';
export interface IEditSessionReadyEvent { import { BatchSummary, IQueryMessage, ResultSetSummary, QueryExecuteSubsetParams, CompleteBatchSummary, IResultMessage, ResultSetSubset, BatchStartSummary } from './query';
ownerUri: string;
success: boolean;
message: string;
}
export interface IQueryMessage {
batchId?: number;
isError: boolean;
time?: string;
message: string;
selection?: azdata.ISelectionData;
}
/* /*
* Query Runner class which handles running a query, reports the results to the content manager, * Query Runner class which handles running a query, reports the results to the content manager,
@@ -50,10 +35,10 @@ export default class QueryRunner extends Disposable {
// MEMBER VARIABLES //////////////////////////////////////////////////// // MEMBER VARIABLES ////////////////////////////////////////////////////
private _resultLineOffset?: number; private _resultLineOffset?: number;
private _resultColumnOffset?: number; private _resultColumnOffset?: number;
private _totalElapsedMilliseconds: number = 0; protected _totalElapsedMilliseconds: number = 0;
private _isExecuting: boolean = false; protected _isExecuting: boolean = false;
private _hasCompleted: boolean = false; private _hasCompleted: boolean = false;
private _batchSets: azdata.BatchSummary[] = []; private _batchSets: BatchSummary[] = [];
private _messages: IQueryMessage[] = []; private _messages: IQueryMessage[] = [];
private registered = false; private registered = false;
@@ -65,31 +50,28 @@ export default class QueryRunner extends Disposable {
private _onMessage = this._register(new Emitter<IQueryMessage[]>()); 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 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>()); private readonly _onResultSet = this._register(new Emitter<ResultSetSummary>());
public readonly onResultSet = this._onResultSet.event; public readonly onResultSet = this._onResultSet.event;
private _onResultSetUpdate = this._register(new Emitter<azdata.ResultSetSummary>()); private readonly _onResultSetUpdate = this._register(new Emitter<ResultSetSummary>());
public readonly onResultSetUpdate = this._onResultSetUpdate.event; public readonly onResultSetUpdate = this._onResultSetUpdate.event;
private _onQueryStart = this._register(new Emitter<void>()); protected readonly _onQueryStart = this._register(new Emitter<void>());
public readonly onQueryStart: Event<void> = this._onQueryStart.event; public readonly onQueryStart: Event<void> = this._onQueryStart.event;
private _onQueryEnd = this._register(new Emitter<string>()); private readonly _onQueryEnd = this._register(new Emitter<string>());
public get onQueryEnd(): Event<string> { return this._onQueryEnd.event; } public get onQueryEnd(): Event<string> { return this._onQueryEnd.event; }
private _onBatchStart = this._register(new Emitter<azdata.BatchSummary>()); private readonly _onBatchStart = this._register(new Emitter<BatchStartSummary>());
public readonly onBatchStart: Event<azdata.BatchSummary> = this._onBatchStart.event; public readonly onBatchStart: Event<BatchStartSummary> = this._onBatchStart.event;
private _onBatchEnd = this._register(new Emitter<azdata.BatchSummary>()); private readonly _onBatchEnd = this._register(new Emitter<CompleteBatchSummary>());
public readonly onBatchEnd: Event<azdata.BatchSummary> = this._onBatchEnd.event; public readonly onBatchEnd: Event<CompleteBatchSummary> = this._onBatchEnd.event;
private _onEditSessionReady = this._register(new Emitter<IEditSessionReadyEvent>()); private readonly _onQueryPlanAvailable = this._register(new Emitter<IQueryPlanInfo>());
public readonly onEditSessionReady = this._onEditSessionReady.event;
private _onQueryPlanAvailable = this._register(new Emitter<IQueryPlanInfo>());
public readonly onQueryPlanAvailable = this._onQueryPlanAvailable.event; public readonly onQueryPlanAvailable = this._onQueryPlanAvailable.event;
private _onVisualize = this._register(new Emitter<azdata.ResultSetSummary>()); private readonly _onVisualize = this._register(new Emitter<ResultSetSummary>());
public readonly onVisualize = this._onVisualize.event; public readonly onVisualize = this._onVisualize.event;
private _queryStartTime?: Date; private _queryStartTime?: Date;
@@ -104,12 +86,11 @@ export default class QueryRunner extends Disposable {
// CONSTRUCTOR ///////////////////////////////////////////////////////// // CONSTRUCTOR /////////////////////////////////////////////////////////
constructor( constructor(
public uri: string, public uri: string,
@IQueryManagementService private _queryManagementService: IQueryManagementService, @IQueryManagementService protected readonly queryManagementService: IQueryManagementService,
@INotificationService private _notificationService: INotificationService, @IConfigurationService private readonly configurationService: IConfigurationService,
@IConfigurationService private _configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService,
@IInstantiationService private instantiationService: IInstantiationService, @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService,
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService, @ILogService private readonly logService: ILogService
@ILogService private _logService: ILogService
) { ) {
super(); super();
} }
@@ -125,7 +106,7 @@ export default class QueryRunner extends Disposable {
/** /**
* For public use only, for private use, directly access the member * For public use only, for private use, directly access the member
*/ */
public get batchSets(): azdata.BatchSummary[] { public get batchSets(): BatchSummary[] {
return this._batchSets.slice(0); return this._batchSets.slice(0);
} }
@@ -141,22 +122,22 @@ export default class QueryRunner extends Disposable {
/** /**
* Cancels the running query, if there is one * Cancels the running query, if there is one
*/ */
public cancelQuery(): Promise<azdata.QueryCancelResult> { public cancelQuery(): Promise<QueryCancelResult> {
return this._queryManagementService.cancelQuery(this.uri); return this.queryManagementService.cancelQuery(this.uri);
} }
/** /**
* Runs the query with the provided query * Runs the query with the provided query
* @param input Query string to execute * @param input Query string to execute
*/ */
public runQuery(input: string, runOptions?: azdata.ExecutionPlanOptions): Promise<void>; public runQuery(input: string, runOptions?: ExecutionPlanOptions): Promise<void>;
/** /**
* Runs the query by pulling the query from the document using the provided selection data * Runs the query by pulling the query from the document using the provided selection data
* @param input selection data * @param input selection data
*/ */
public runQuery(input: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void>; public runQuery(input: IRange | undefined, runOptions?: ExecutionPlanOptions): Promise<void>;
public runQuery(input: string | azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void> { public runQuery(input: string | IRange | undefined, runOptions?: ExecutionPlanOptions): Promise<void> {
if (types.isString(input)) { if (types.isString(input) || types.isUndefined(input)) {
return this.doRunQuery(input, false, runOptions); return this.doRunQuery(input, false, runOptions);
} else { } else {
return this.doRunQuery(input, false, runOptions); return this.doRunQuery(input, false, runOptions);
@@ -167,7 +148,7 @@ export default class QueryRunner extends Disposable {
* Runs the current SQL statement by pulling the query from the document using the provided selection data * Runs the current SQL statement by pulling the query from the document using the provided selection data
* @param input selection data * @param input selection data
*/ */
public runQueryStatement(input: azdata.ISelectionData): Promise<void> { public runQueryStatement(input: IRange): Promise<void> {
return this.doRunQuery(input, true); return this.doRunQuery(input, true);
} }
@@ -175,9 +156,9 @@ export default class QueryRunner extends Disposable {
* Implementation that runs the query with the provided query * Implementation that runs the query with the provided query
* @param input Query string to execute * @param input Query string to execute
*/ */
private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void>; private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Promise<void>;
private doRunQuery(input: azdata.ISelectionData, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void>; private doRunQuery(input: IRange | undefined, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Promise<void>;
private doRunQuery(input: string | azdata.ISelectionData, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void> { private doRunQuery(input: string | IRange | undefined, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Promise<void> {
if (this.isExecuting) { if (this.isExecuting) {
return Promise.resolve(); return Promise.resolve();
} }
@@ -187,9 +168,9 @@ export default class QueryRunner extends Disposable {
this._queryStartTime = undefined; this._queryStartTime = undefined;
this._queryEndTime = undefined; this._queryEndTime = undefined;
this._messages = []; this._messages = [];
if (isSelectionOrUndefined(input)) { if (isRangeOrUndefined(input)) {
// Update internal state to show that we're executing the query // Update internal state to show that we're executing the query
this._resultLineOffset = input ? input.startLine : 0; this._resultLineOffset = input ? input.startLineNumber : 0;
this._resultColumnOffset = input ? input.startColumn : 0; this._resultColumnOffset = input ? input.startColumn : 0;
this._isExecuting = true; this._isExecuting = true;
this._totalElapsedMilliseconds = 0; this._totalElapsedMilliseconds = 0;
@@ -199,8 +180,8 @@ export default class QueryRunner extends Disposable {
// Send the request to execute the query // Send the request to execute the query
return runCurrentStatement return runCurrentStatement
? this._queryManagementService.runQueryStatement(this.uri, input.startLine, input.startColumn).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e)) ? this.queryManagementService.runQueryStatement(this.uri, input.startLineNumber, input.startColumn).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e))
: this._queryManagementService.runQuery(this.uri, input, runOptions).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e)); : this.queryManagementService.runQuery(this.uri, input, runOptions).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
} else { } else {
// Update internal state to show that we're executing the query // Update internal state to show that we're executing the query
this._isExecuting = true; this._isExecuting = true;
@@ -208,7 +189,7 @@ export default class QueryRunner extends Disposable {
this._onQueryStart.fire(); this._onQueryStart.fire();
return this._queryManagementService.runQueryString(this.uri, input).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e)); return this.queryManagementService.runQueryString(this.uri, input).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
} }
} }
@@ -218,45 +199,39 @@ export default class QueryRunner extends Disposable {
// The query has started, so lets fire up the result pane // The query has started, so lets fire up the result pane
if (!this.registered) { if (!this.registered) {
this.registered = true; this.registered = true;
this._queryManagementService.registerRunner(this, this.uri); this.queryManagementService.registerRunner(this, this.uri);
} }
} }
private handleFailureRunQueryResult(error: any) { private handleFailureRunQueryResult(error: any) {
// Attempting to launch the query failed, show the error message // Attempting to launch the query failed, show the error message
const eol = getEolString(this._textResourcePropertiesService, this.uri); const eol = getEolString(this.textResourcePropertiesService, this.uri);
if (error instanceof Error) { if (error instanceof Error) {
error = error.message; error = error.message;
} }
let message = nls.localize('query.ExecutionFailedError', "Execution failed due to an unexpected error: {0}\t{1}", eol, error); let message = nls.localize('query.ExecutionFailedError', "Execution failed due to an unexpected error: {0}\t{1}", eol, error);
this.handleMessage([<azdata.QueryExecuteMessageParams>{ this.handleMessage([{
ownerUri: this.uri, isError: true,
message: { message: message
isError: true,
message: message
}
}]); }]);
this.handleQueryComplete(<azdata.QueryExecuteCompleteNotificationResult>{ ownerUri: this.uri }); this.handleQueryComplete();
} }
/** /**
* Handle a QueryComplete from the service layer * Handle a QueryComplete from the service layer
*/ */
public handleQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void { public handleQueryComplete(batchSummaries?: CompleteBatchSummary[]): void {
// this also isn't exact but its the best we can do // this also isn't exact but its the best we can do
this._queryEndTime = new Date(); this._queryEndTime = new Date();
// Store the batch sets we got back as a source of "truth" // Store the batch sets we got back as a source of "truth"
this._isExecuting = false; this._isExecuting = false;
this._hasCompleted = true; this._hasCompleted = true;
this._batchSets = result.batchSummaries ? result.batchSummaries : []; this._batchSets = batchSummaries ? batchSummaries : [];
this._batchSets.map(batch => { this._batchSets.map(batch => {
if (batch.selection) { if (batch.range) {
batch.selection.startLine += this._resultLineOffset!; batch.range = new Range(batch.range.startLineNumber + this._resultLineOffset, batch.range.startColumn + this._resultColumnOffset, batch.range.endLineNumber + this._resultLineOffset, batch.range.endColumn + this._resultColumnOffset);
batch.selection.startColumn += this._resultColumnOffset!;
batch.selection.endLine += this._resultLineOffset!;
batch.selection.endColumn += this._resultColumnOffset!;
} }
}); });
@@ -277,28 +252,20 @@ export default class QueryRunner extends Disposable {
/** /**
* Handle a BatchStart from the service layer * Handle a BatchStart from the service layer
*/ */
public handleBatchStart(result: azdata.QueryExecuteBatchNotificationParams): void { public handleBatchStart(batch: BatchStartSummary): void {
let batch = result.batchSummary;
// Recalculate the start and end lines, relative to the result line offset // Recalculate the start and end lines, relative to the result line offset
if (batch.selection) { if (batch.range) {
batch.selection.startLine += this._resultLineOffset!; batch.range = new Range(batch.range.startLineNumber + this._resultLineOffset, batch.range.startColumn + this._resultColumnOffset, batch.range.endLineNumber + this._resultLineOffset, batch.range.endColumn + this._resultColumnOffset);
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 // Store the batch
this._batchSets[batch.id] = batch; this._batchSets[batch.id] = { ...batch, resultSetSummaries: [], hasError: false };
let message = { let message: IQueryMessage = {
// account for index by 1 // account for index by 1
message: nls.localize('query.message.startQuery', "Started executing query at Line {0}", batch.selection.startLine + 1), message: batch.range ? nls.localize('query.message.startQueryWithRange', "Started executing query at Line {0}", batch.range.startLineNumber) : nls.localize('query.message.startQuery', "Started executing batch {0}", batch.id),
time: batch.executionStart, time: batch.executionStart,
selection: batch.selection, range: batch.range,
isError: false isError: false
}; };
this._messages.push(message); this._messages.push(message);
@@ -309,9 +276,7 @@ export default class QueryRunner extends Disposable {
/** /**
* Handle a BatchComplete from the service layer * Handle a BatchComplete from the service layer
*/ */
public handleBatchComplete(result: azdata.QueryExecuteBatchNotificationParams): void { public handleBatchComplete(batch: CompleteBatchSummary): void {
let batch: azdata.BatchSummary = result.batchSummary;
// Store the batch again to get the rest of the data // 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); let executionTime = <number>(Utils.parseTimeString(batch.executionElapsed) || 0);
@@ -327,19 +292,18 @@ export default class QueryRunner extends Disposable {
/** /**
* Handle a ResultSetComplete from the service layer * Handle a ResultSetComplete from the service layer
*/ */
public handleResultSetAvailable(result: azdata.QueryExecuteResultSetNotificationParams): void { public handleResultSetAvailable(resultSet?: ResultSetSummary): void {
if (result && result.resultSetSummary) { if (resultSet) {
let resultSet = result.resultSetSummary; let batchSet: BatchSummary;
let batchSet: azdata.BatchSummary;
if (!resultSet.batchId) { if (!resultSet.batchId) {
// Missing the batchId or processing batchId==0. In this case, default to always using the first batch in the list // 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 // or create one in the case the DMP extension didn't obey the contract perfectly
if (this._batchSets.length > 0) { if (this._batchSets.length > 0) {
batchSet = this._batchSets[0]; batchSet = this._batchSets[0];
} else { } else {
batchSet = <azdata.BatchSummary><unknown>{ batchSet = <BatchSummary>{
id: 0, id: 0,
selection: undefined, range: undefined,
hasError: false, hasError: false,
resultSetSummaries: [] resultSetSummaries: []
}; };
@@ -350,15 +314,15 @@ export default class QueryRunner extends Disposable {
} }
// handle getting queryPlanxml if we need too // 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 // 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'); let hasShowPlan = !!find(resultSet.columnInfo, e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
if (hasShowPlan) { if (hasShowPlan && resultSet.rowCount > 0) {
this._isQueryPlan = true; this._isQueryPlan = true;
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => { this.getQueryRows(0, 1, resultSet.batchId, resultSet.id).then(e => {
if (e.resultSubset.rows) { if (e.rows) {
this._planXml.resolve(e.resultSubset.rows[0][0].displayValue); this._planXml.resolve(e.rows[0][0].displayValue);
} }
}).catch((e) => this._logService.error(e)); }).catch((e) => this.logService.error(e));
} }
// we will just ignore the set if we already have it // we will just ignore the set if we already have it
// ideally this should never happen // ideally this should never happen
@@ -370,31 +334,30 @@ export default class QueryRunner extends Disposable {
} }
} }
public handleResultSetUpdated(result: azdata.QueryExecuteResultSetNotificationParams): void { public handleResultSetUpdated(resultSet?: ResultSetSummary): void {
if (result && result.resultSetSummary) { if (resultSet) {
let resultSet = result.resultSetSummary; let batchSet: BatchSummary;
let batchSet: azdata.BatchSummary;
batchSet = this._batchSets[resultSet.batchId]; batchSet = this._batchSets[resultSet.batchId];
// handle getting queryPlanxml if we need too // 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 // 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'); let hasShowPlan = !!resultSet.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
if (hasShowPlan) { if (hasShowPlan) {
this._isQueryPlan = true; this._isQueryPlan = true;
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => { this.getQueryRows(0, 1, resultSet.batchId, resultSet.id).then(e => {
if (e.resultSubset.rows) { if (e.rows) {
let planXmlString = e.resultSubset.rows[0][0].displayValue; let planXmlString = e.rows[0][0].displayValue;
this._planXml.resolve(e.resultSubset.rows[0][0].displayValue); this._planXml.resolve(e.rows[0][0].displayValue);
// fire query plan available event if execution is completed // fire query plan available event if execution is completed
if (result.resultSetSummary.complete) { if (resultSet.complete) {
this._onQueryPlanAvailable.fire({ this._onQueryPlanAvailable.fire({
providerId: mssqlProviderName, providerId: mssqlProviderName,
fileUri: result.ownerUri, fileUri: this.uri,
planXml: planXmlString planXml: planXmlString
}); });
} }
} }
}).catch((e) => this._logService.error(e)); }).catch((e) => this.logService.error(e));
} }
if (batchSet) { if (batchSet) {
// Store the result set in the batch and emit that a result set has completed // Store the result set in the batch and emit that a result set has completed
@@ -407,8 +370,7 @@ export default class QueryRunner extends Disposable {
/** /**
* Handle a Mssage from the service layer * Handle a Mssage from the service layer
*/ */
public handleMessage(messagesObj: azdata.QueryExecuteMessageParams[]): void { public handleMessage(messages: IResultMessage[]): void {
const messages = messagesObj.map(m => m.message);
this._messages.push(...messages); this._messages.push(...messages);
// Send the message to the results pane // Send the message to the results pane
@@ -418,8 +380,8 @@ export default class QueryRunner extends Disposable {
/** /**
* Get more data rows from the current resultSets from the service layer * 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> { public getQueryRows(rowStart: number, numberOfRows: number, batchIndex: number, resultSetIndex: number): Promise<ResultSetSubset> {
let rowData: azdata.QueryExecuteSubsetParams = <azdata.QueryExecuteSubsetParams>{ let rowData: QueryExecuteSubsetParams = <QueryExecuteSubsetParams>{
ownerUri: this.uri, ownerUri: this.uri,
resultSetIndex: resultSetIndex, resultSetIndex: resultSetIndex,
rowsCount: numberOfRows, rowsCount: numberOfRows,
@@ -427,7 +389,7 @@ export default class QueryRunner extends Disposable {
batchIndex: batchIndex batchIndex: batchIndex
}; };
return this._queryManagementService.getQueryRows(rowData).then(r => r, error => { return this.queryManagementService.getQueryRows(rowData).then(r => r, error => {
// this._notificationService.notify({ // this._notificationService.notify({
// severity: Severity.Error, // severity: Severity.Error,
// message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error) // message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error)
@@ -436,104 +398,11 @@ export default class QueryRunner extends Disposable {
}); });
} }
/*
* 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 * Disposes the Query from the service client
*/ */
public async disposeQuery(): Promise<void> { public async disposeQuery(): Promise<void> {
await this._queryManagementService.disposeQuery(this.uri); await this.queryManagementService.disposeQuery(this.uri);
this.dispose(); this.dispose();
} }
@@ -561,7 +430,7 @@ export default class QueryRunner extends Disposable {
public getColumnHeaders(batchId: number, resultId: number, range: Slick.Range): string[] | undefined { public getColumnHeaders(batchId: number, resultId: number, range: Slick.Range): string[] | undefined {
let headers: string[] | undefined = undefined; let headers: string[] | undefined = undefined;
let batchSummary: azdata.BatchSummary = this._batchSets[batchId]; let batchSummary: BatchSummary = this._batchSets[batchId];
if (batchSummary !== undefined) { if (batchSummary !== undefined) {
let resultSetSummary = batchSummary.resultSetSummaries[resultId]; let resultSetSummary = batchSummary.resultSetSummaries[resultId];
headers = resultSetSummary.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => { headers = resultSetSummary.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => {
@@ -573,7 +442,7 @@ export default class QueryRunner extends Disposable {
private sendBatchTimeMessage(batchId: number, executionTime: string): void { private sendBatchTimeMessage(batchId: number, executionTime: string): void {
// get config copyRemoveNewLine option from vscode config // get config copyRemoveNewLine option from vscode config
let showBatchTime = this._configurationService.getValue<boolean>('sql.showBatchTime'); let showBatchTime = this.configurationService.getValue<boolean>('sql.showBatchTime');
if (showBatchTime) { if (showBatchTime) {
let message: IQueryMessage = { let message: IQueryMessage = {
batchId: batchId, batchId: batchId,
@@ -596,7 +465,7 @@ export default class QueryRunner extends Disposable {
} }
public notifyVisualizeRequested(batchId: number, resultSetId: number): void { public notifyVisualizeRequested(batchId: number, resultSetId: number): void {
let result: azdata.ResultSetSummary = { let result: ResultSetSummary = {
batchId: batchId, batchId: batchId,
id: resultSetId, id: resultSetId,
columnInfo: this.batchSets[batchId].resultSetSummaries[resultSetId].columnInfo, columnInfo: this.batchSets[batchId].resultSetSummaries[resultSetId].columnInfo,
@@ -620,7 +489,7 @@ export class QueryGridDataProvider implements IGridDataProvider {
) { ) {
} }
getRowData(rowStart: number, numberOfRows: number): Promise<azdata.QueryExecuteSubsetResult> { getRowData(rowStart: number, numberOfRows: number): Promise<ResultSetSubset> {
return this.queryRunner.getQueryRows(rowStart, numberOfRows, this.batchId, this.resultSetId); return this.queryRunner.getQueryRows(rowStart, numberOfRows, this.batchId, this.resultSetId);
} }
@@ -679,6 +548,6 @@ export function shouldRemoveNewLines(configurationService: IConfigurationService
return !!removeNewLines; return !!removeNewLines;
} }
function isSelectionOrUndefined(input: string | azdata.ISelectionData | undefined): input is azdata.ISelectionData | undefined { function isRangeOrUndefined(input: string | IRange | undefined): input is IRange | undefined {
return types.isObject(input) || types.isUndefinedOrNull(input); return Range.isIRange(input) || types.isUndefinedOrNull(input);
} }

View File

@@ -0,0 +1,229 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as sinon from 'sinon';
import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import { BatchSummary, ResultSetSummary, IResultMessage, ResultSetSubset, CompleteBatchSummary } from 'sql/workbench/services/query/common/query';
import { URI } from 'vs/base/common/uri';
import { workbenchInstantiationService } from 'sql/workbench/test/workbenchTestServices';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
import { Event } from 'vs/base/common/event';
import { range } from 'vs/base/common/arrays';
suite('Query Runner', () => {
test('does execute a standard selection query workflow', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQuery', runQueryStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
const queryStartPromise = Event.toPromise(runner.onQueryStart);
const rangeSelection = { endColumn: 1, endLineNumber: 1, startColumn: 1, startLineNumber: 1 };
await runner.runQuery(rangeSelection);
assert(runQueryStub.calledOnce);
assert(runQueryStub.calledWithExactly(uri, rangeSelection, undefined));
await queryStartPromise;
assert(runner.queryStartTime instanceof Date);
assert(runner.isExecuting);
assert(!runner.hasCompleted);
// start batch
const batch: BatchSummary = { id: 0, hasError: false, range: rangeSelection, resultSetSummaries: [], executionStart: '' };
const returnBatch = await trigger(batch, arg => runner.handleBatchStart(arg), runner.onBatchStart);
assert.deepEqual(returnBatch, batch);
// we expect the query runner to create a message sense we sent a selection
assert(runner.messages.length === 1);
// start result set
const result1: ResultSetSummary = { batchId: 0, id: 0, complete: false, rowCount: 0, columnInfo: [{ columnName: 'column' }] };
const returnResult = await trigger(result1, arg => runner.handleResultSetAvailable(arg), runner.onResultSet);
assert.deepEqual(returnResult, result1);
assert.deepEqual(runner.batchSets[0].resultSetSummaries[0], result1);
// update result set
const result1Update: ResultSetSummary = { batchId: 0, id: 0, complete: true, rowCount: 100, columnInfo: [{ columnName: 'column' }] };
const returnResultUpdate = await trigger(result1Update, arg => runner.handleResultSetUpdated(arg), runner.onResultSetUpdate);
assert.deepEqual(returnResultUpdate, result1Update);
assert.deepEqual(runner.batchSets[0].resultSetSummaries[0], result1Update);
// post message
const message: IResultMessage = { message: 'some message', isError: false, batchId: 0 };
const messageReturn = await trigger([message], arg => runner.handleMessage(arg), runner.onMessage);
assert.deepEqual(messageReturn[0], message);
assert.deepEqual(runner.messages[1], message);
// get query rows
const rowResults: ResultSetSubset = { rowCount: 100, rows: range(100).map(r => range(1).map(c => ({ displayValue: `${r}${c}` }))) };
const getRowStub = sinon.stub().returns(Promise.resolve(rowResults));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'getQueryRows', getRowStub);
const resultReturn = await runner.getQueryRows(0, 100, 0, 0);
assert(getRowStub.calledWithExactly({ ownerUri: uri, batchIndex: 0, resultSetIndex: 0, rowsStartIndex: 0, rowsCount: 100 }));
assert.deepStrictEqual(resultReturn, rowResults);
// batch complete
const batchComplete: CompleteBatchSummary = { ...batch, executionEnd: 'endstring', executionElapsed: 'elapsedstring' };
const batchCompleteReturn = await trigger(batchComplete, arg => runner.handleBatchComplete(arg), runner.onBatchEnd);
assert.deepStrictEqual(batchCompleteReturn, batchComplete);
// query complete
await trigger([batchComplete], arg => runner.handleQueryComplete(arg), runner.onQueryEnd);
assert(!runner.isExecuting);
assert(runner.hasCompleted);
await runner.disposeQuery();
});
test('does handle inital query failure', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStub = sinon.stub().returns(Promise.reject(new Error('some error')));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQuery', runQueryStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
const queryCompletePromise = Event.toPromise(runner.onQueryEnd);
const rangeSelection = { endColumn: 1, endLineNumber: 1, startColumn: 1, startLineNumber: 1 };
await runner.runQuery(rangeSelection);
await queryCompletePromise;
assert(runQueryStub.calledOnce);
assert(runQueryStub.calledWithExactly(uri, rangeSelection, undefined));
assert(runner.messages.length === 2);
assert(runner.messages[0].message.includes('some error'));
assert(runner.messages[0].isError);
});
test('does handle cancel query', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
assert(!runner.isExecuting);
// start query
const rangeSelection = { endColumn: 1, endLineNumber: 1, startColumn: 1, startLineNumber: 1 };
await runner.runQuery(rangeSelection);
assert(runner.isExecuting);
// cancel query
const cancelQueryStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'cancelQuery', cancelQueryStub);
await runner.cancelQuery();
assert(cancelQueryStub.calledOnce);
await trigger([], () => runner.handleQueryComplete(), runner.onQueryEnd);
assert(!runner.isExecuting);
});
test('does handle query plan in inital data set', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQuery', runQueryStub);
await runner.runQuery(undefined, { displayEstimatedQueryPlan: true });
assert(runQueryStub.calledOnce);
assert(runQueryStub.calledWithExactly(uri, undefined, { displayEstimatedQueryPlan: true }));
const xmlPlan = 'xml plan';
const getRowsStub = sinon.stub().returns(Promise.resolve({ rowCount: 1, rows: [[{ displayValue: xmlPlan }]] } as ResultSetSubset));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'getQueryRows', getRowsStub);
runner.handleBatchStart({ id: 0, executionStart: '' });
runner.handleResultSetAvailable({ id: 0, batchId: 0, complete: true, rowCount: 1, columnInfo: [{ columnName: 'Microsoft SQL Server 2005 XML Showplan' }] });
const plan = await runner.planXml;
assert(getRowsStub.calledOnce);
assert.equal(plan, xmlPlan);
assert(runner.isQueryPlan);
});
test('does handle query plan in update', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQuery', runQueryStub);
await runner.runQuery(undefined, { displayEstimatedQueryPlan: true });
assert(runQueryStub.calledOnce);
assert(runQueryStub.calledWithExactly(uri, undefined, { displayEstimatedQueryPlan: true }));
runner.handleBatchStart({ id: 0, executionStart: '' });
runner.handleResultSetAvailable({ id: 0, batchId: 0, complete: false, rowCount: 0, columnInfo: [{ columnName: 'Microsoft SQL Server 2005 XML Showplan' }] });
const xmlPlan = 'xml plan';
const getRowsStub = sinon.stub().returns(Promise.resolve({ rowCount: 1, rows: [[{ displayValue: xmlPlan }]] } as ResultSetSubset));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'getQueryRows', getRowsStub);
runner.handleResultSetUpdated({ id: 0, batchId: 0, complete: true, rowCount: 1, columnInfo: [{ columnName: 'Microsoft SQL Server 2005 XML Showplan' }] });
const plan = await runner.planXml;
assert(getRowsStub.calledOnce);
assert.equal(plan, xmlPlan);
assert(runner.isQueryPlan);
});
test('does run query string', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStringStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQueryString', runQueryStringStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
await runner.runQuery('some query');
assert(runQueryStringStub.calledOnce);
assert(runQueryStringStub.calledWithExactly(uri, 'some query'));
assert(runner.isExecuting);
});
test('does handle run query string error', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStringStub = sinon.stub().returns(Promise.reject(new Error('some error')));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQueryString', runQueryStringStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
const queryCompletePromise = Event.toPromise(runner.onQueryEnd);
await runner.runQuery('some query');
await queryCompletePromise;
assert(runQueryStringStub.calledOnce);
assert(runQueryStringStub.calledWithExactly(uri, 'some query'));
assert(runner.messages.length === 2);
assert(runner.messages[0].message.includes('some error'));
assert(runner.messages[0].isError);
});
test('does run query statement', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStatementStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQueryStatement', runQueryStatementStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
const rangeSelection = { endColumn: 1, endLineNumber: 1, startColumn: 1, startLineNumber: 1 };
await runner.runQueryStatement(rangeSelection);
assert(runQueryStatementStub.calledOnce);
assert(runQueryStatementStub.calledWithExactly(uri, rangeSelection.startLineNumber, rangeSelection.startColumn));
assert(runner.isExecuting);
});
test('does handle run query statement error', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStatementStub = sinon.stub().returns(Promise.reject(new Error('some error')));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQueryStatement', runQueryStatementStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
const queryCompletePromise = Event.toPromise(runner.onQueryEnd);
const rangeSelection = { endColumn: 1, endLineNumber: 1, startColumn: 1, startLineNumber: 1 };
await runner.runQueryStatement(rangeSelection);
await queryCompletePromise;
assert(runQueryStatementStub.calledOnce);
assert(runQueryStatementStub.calledWithExactly(uri, rangeSelection.startLineNumber, rangeSelection.startColumn));
assert(runner.messages.length === 2);
assert(runner.messages[0].message.includes('some error'));
assert(runner.messages[0].isError);
});
});
function trigger<T, V = T>(arg: T, func: (arg: T) => void, event: Event<V>): Promise<V> {
const promise = Event.toPromise(event);
func(arg);
return promise;
}

View File

@@ -0,0 +1,108 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQueryManagementService, IQueryRequestHandler, ExecutionPlanOptions } from 'sql/workbench/services/query/common/queryManagement';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import * as azdata from 'azdata';
import { IRange } from 'vs/editor/common/core/range';
import { ResultSetSubset } from 'sql/workbench/services/query/common/query';
export class TestQueryManagementService implements IQueryManagementService {
_serviceBrand: undefined;
onHandlerAdded: Event<string>;
addQueryRequestHandler(queryType: string, runner: IQueryRequestHandler): IDisposable {
throw new Error('Method not implemented.');
}
isProviderRegistered(providerId: string): boolean {
throw new Error('Method not implemented.');
}
getRegisteredProviders(): string[] {
throw new Error('Method not implemented.');
}
registerRunner(runner: QueryRunner, uri: string): void {
return;
}
async cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult> {
return { messages: undefined };
}
async runQuery(ownerUri: string, range: IRange, runOptions?: ExecutionPlanOptions): Promise<void> {
return;
}
runQueryStatement(ownerUri: string, line: number, column: number): Promise<void> {
throw new Error('Method not implemented.');
}
runQueryString(ownerUri: string, queryString: string): Promise<void> {
throw new Error('Method not implemented.');
}
runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult> {
throw new Error('Method not implemented.');
}
parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult> {
throw new Error('Method not implemented.');
}
getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<ResultSetSubset> {
throw new Error('Method not implemented.');
}
async disposeQuery(ownerUri: string): Promise<void> {
return;
}
saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult> {
throw new Error('Method not implemented.');
}
setQueryExecutionOptions(uri: string, options: azdata.QueryExecutionOptions): Promise<void> {
throw new Error('Method not implemented.');
}
onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void {
throw new Error('Method not implemented.');
}
onBatchStart(batchInfo: azdata.QueryExecuteBatchNotificationParams): void {
throw new Error('Method not implemented.');
}
onBatchComplete(batchInfo: azdata.QueryExecuteBatchNotificationParams): void {
throw new Error('Method not implemented.');
}
onResultSetAvailable(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void {
throw new Error('Method not implemented.');
}
onResultSetUpdated(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void {
throw new Error('Method not implemented.');
}
onMessage(message: Map<string, azdata.QueryExecuteMessageParams[]>): void {
throw new Error('Method not implemented.');
}
onEditSessionReady(ownerUri: string, success: boolean, message: string): void {
throw new Error('Method not implemented.');
}
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> {
throw new Error('Method not implemented.');
}
disposeEdit(ownerUri: string): Promise<void> {
throw new Error('Method not implemented.');
}
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult> {
throw new Error('Method not implemented.');
}
commitEdit(ownerUri: string): Promise<void> {
throw new Error('Method not implemented.');
}
createRow(ownerUri: string): Promise<azdata.EditCreateRowResult> {
throw new Error('Method not implemented.');
}
deleteRow(ownerUri: string, rowId: number): Promise<void> {
throw new Error('Method not implemented.');
}
revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult> {
throw new Error('Method not implemented.');
}
revertRow(ownerUri: string, rowId: number): Promise<void> {
throw new Error('Method not implemented.');
}
getEditRows(rowData: azdata.EditSubsetParams): Promise<azdata.EditSubsetResult> {
throw new Error('Method not implemented.');
}
}

View File

@@ -9,6 +9,7 @@ import * as azdata from 'azdata';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService'; import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService';
import { DataService } from 'sql/workbench/services/query/common/dataService'; import { DataService } from 'sql/workbench/services/query/common/dataService';
import { IRange } from 'vs/editor/common/core/range';
export class TestQueryModelService implements IQueryModelService { export class TestQueryModelService implements IQueryModelService {
_serviceBrand: any; _serviceBrand: any;
@@ -25,10 +26,10 @@ export class TestQueryModelService implements IQueryModelService {
getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<azdata.ResultSetSubset> { getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<azdata.ResultSetSubset> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
runQuery(uri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): void { runQuery(uri: string, range: IRange, runOptions?: azdata.ExecutionPlanOptions): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
runQueryStatement(uri: string, selection: azdata.ISelectionData): void { runQueryStatement(uri: string, range: IRange): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
runQueryString(uri: string, selection: string) { runQueryString(uri: string, selection: string) {

View File

@@ -52,12 +52,12 @@ export class QueryHistoryService extends Disposable implements IQueryHistoryServ
const uri: URI = URI.parse(e.uri); const uri: URI = URI.parse(e.uri);
// VS Range is 1 based so offset values by 1. The endLine we get back from SqlToolsService is incremented // VS Range is 1 based so offset values by 1. The endLine we get back from SqlToolsService is incremented
// by 1 from the original input range sent in as well so take that into account and don't modify // by 1 from the original input range sent in as well so take that into account and don't modify
const text: string = e.queryInfo.selection && e.queryInfo.selection.length > 0 ? const text: string = e.queryInfo.range && e.queryInfo.range.length > 0 ?
_modelService.getModel(uri).getValueInRange(new Range( _modelService.getModel(uri).getValueInRange(new Range(
e.queryInfo.selection[0].startLine + 1, e.queryInfo.range[0].startLineNumber,
e.queryInfo.selection[0].startColumn + 1, e.queryInfo.range[0].startColumn,
e.queryInfo.selection[0].endLine, e.queryInfo.range[0].endLineNumber,
e.queryInfo.selection[0].endColumn + 1)) : e.queryInfo.range[0].endColumn)) :
// If no specific selection get the entire text // If no specific selection get the entire text
_modelService.getModel(uri).getValue(); _modelService.getModel(uri).getValue();

View File

@@ -12,6 +12,8 @@ import { TestObjectExplorerService } from 'sql/workbench/services/objectExplorer
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { TestQueryEditorService } from 'sql/workbench/services/queryEditor/test/common/testQueryEditorService'; import { TestQueryEditorService } from 'sql/workbench/services/queryEditor/test/common/testQueryEditorService';
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
import { TestQueryManagementService } from 'sql/workbench/services/query/test/common/testQueryManagementService';
export function workbenchInstantiationService(): ITestInstantiationService { export function workbenchInstantiationService(): ITestInstantiationService {
const instantiationService = vsworkbenchInstantiationService(); const instantiationService = vsworkbenchInstantiationService();
@@ -19,5 +21,6 @@ export function workbenchInstantiationService(): ITestInstantiationService {
instantiationService.stub(IConnectionManagementService, new TestConnectionManagementService()); instantiationService.stub(IConnectionManagementService, new TestConnectionManagementService());
instantiationService.stub(IQueryModelService, new TestQueryModelService()); instantiationService.stub(IQueryModelService, new TestQueryModelService());
instantiationService.stub(IObjectExplorerService, new TestObjectExplorerService()); instantiationService.stub(IObjectExplorerService, new TestObjectExplorerService());
instantiationService.stub(IQueryManagementService, new TestQueryManagementService());
return instantiationService; return instantiationService;
} }