diff --git a/src/sql/platform/connection/common/connectionManagement.ts b/src/sql/platform/connection/common/connectionManagement.ts index ad6a814ea2..7e0aaf23e7 100644 --- a/src/sql/platform/connection/common/connectionManagement.ts +++ b/src/sql/platform/connection/common/connectionManagement.ts @@ -12,28 +12,6 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo'; 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 */ @@ -324,7 +302,7 @@ export interface INewConnectionParams { connectionType: ConnectionType; input?: IConnectableInput; runQueryOnCompletion?: RunQueryOnConnectionMode; - queryRange?: IRange; + querySelection?: azdata.ISelectionData; showDashboard?: boolean; providers?: string[]; isEditConnection?: boolean; diff --git a/src/sql/workbench/common/editor/query/queryEditorInput.ts b/src/sql/workbench/common/editor/query/queryEditorInput.ts index 2352a53acd..791482d79f 100644 --- a/src/sql/workbench/common/editor/query/queryEditorInput.ts +++ b/src/sql/workbench/common/editor/query/queryEditorInput.ts @@ -14,9 +14,8 @@ import { IConnectionManagementService, IConnectableInput, INewConnectionParams, import { QueryResultsInput } from 'sql/workbench/common/editor/query/queryResultsInput'; import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel'; -import { ExecutionPlanOptions } from 'azdata'; +import { ISelectionData, ExecutionPlanOptions } from 'azdata'; import { startsWith } from 'vs/base/common/strings'; -import { IRange } from 'vs/editor/common/core/range'; const MAX_SIZE = 13; @@ -230,13 +229,13 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab } // State update funtions - public runQuery(range?: IRange, executePlanOptions?: ExecutionPlanOptions): void { - this.queryModelService.runQuery(this.uri, range, executePlanOptions); + public runQuery(selection?: ISelectionData, executePlanOptions?: ExecutionPlanOptions): void { + this.queryModelService.runQuery(this.uri, selection, executePlanOptions); this.state.executing = true; } - public runQueryStatement(range?: IRange): void { - this.queryModelService.runQueryStatement(this.uri, range); + public runQueryStatement(selection?: ISelectionData): void { + this.queryModelService.runQueryStatement(this.uri, selection); this.state.executing = true; } @@ -270,15 +269,15 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab let isRunningQuery = this.queryModelService.isRunningQuery(this.uri); if (!isRunningQuery && params && params.runQueryOnCompletion) { - let range: IRange | undefined = params ? params.queryRange : undefined; + let selection: ISelectionData | undefined = params ? params.querySelection : undefined; if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeCurrentQuery) { - this.runQueryStatement(range); + this.runQueryStatement(selection); } else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeQuery) { - this.runQuery(range); + this.runQuery(selection); } else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.estimatedQueryPlan) { - this.runQuery(range, { displayEstimatedQueryPlan: true }); + this.runQuery(selection, { displayEstimatedQueryPlan: true }); } else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.actualQueryPlan) { - this.runQuery(range, { displayActualQueryPlan: true }); + this.runQuery(selection, { displayActualQueryPlan: true }); } } this._onDidChangeLabel.fire(); diff --git a/src/sql/workbench/contrib/charts/browser/chartView.ts b/src/sql/workbench/contrib/charts/browser/chartView.ts index 5ccca7d4c8..4fa8e78b62 100644 --- a/src/sql/workbench/contrib/charts/browser/chartView.ts +++ b/src/sql/workbench/contrib/charts/browser/chartView.ts @@ -8,7 +8,6 @@ import 'vs/css!./media/chartView'; import { IPanelView } from 'sql/base/browser/ui/panel/panel'; import { Insight } from './insight'; 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 { Extensions, IInsightRegistry, IInsightData } from 'sql/platform/dashboard/browser/insightRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -29,6 +28,7 @@ import { ChartState } from 'sql/workbench/common/editor/query/chartState'; import * as nls from 'vs/nls'; import { find } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { DbCellValue } from 'azdata'; import { Event, Emitter } from 'vs/base/common/event'; const insightRegistry = Registry.as(Extensions.InsightContribution); @@ -213,7 +213,7 @@ export class ChartView extends Disposable implements IPanelView { this.shouldGraph(); } - public setData(rows: ICellValue[][], columns: string[]): void { + public setData(rows: DbCellValue[][], columns: string[]): void { if (!rows) { this._data = { columns: [], rows: [] }; 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]; if (summary) { this._queryRunner.getQueryRows(0, summary.rowCount, this._currentData.batchId, this._currentData.resultId).then(d => { - let rows = d.rows; + let rows = d.resultSubset.rows; let columns = summary.columnInfo.map(c => c.columnName); this.setData(rows, columns); }); diff --git a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts index 552502763e..c45797740a 100644 --- a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts @@ -14,7 +14,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IDataResource } from 'sql/workbench/services/notebook/browser/sql/sqlSessionManager'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; 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 { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { attachTableStyler } from 'sql/platform/theme/common/styler'; @@ -216,7 +215,7 @@ class DataResourceTable extends GridTableBase { gridDataProvider.getRowData(0, rowCount).then(result => { let range = new Slick.Range(0, 0, rowCount - 1, columnCount - 1); let columns = gridDataProvider.getColumnHeaders(range); - this._chart.setData(result.rows, columns); + this._chart.setData(result.resultSubset.rows, columns); }); } @@ -227,9 +226,9 @@ class DataResourceTable extends GridTableBase { } class DataResourceDataProvider implements IGridDataProvider { - private rows: ICellValue[][]; + private rows: azdata.DbCellValue[][]; constructor(source: IDataResource, - private resultSet: ResultSetSummary, + private resultSet: azdata.ResultSetSummary, private documentUri: string, @INotificationService private _notificationService: INotificationService, @IClipboardService private _clipboardService: IClipboardService, @@ -257,14 +256,17 @@ class DataResourceDataProvider implements IGridDataProvider { }); } - getRowData(rowStart: number, numberOfRows: number): Thenable { + getRowData(rowStart: number, numberOfRows: number): Thenable { let rowEnd = rowStart + numberOfRows; if (rowEnd > this.rows.length) { rowEnd = this.rows.length; } - let resultSubset: ResultSetSubset = { - rowCount: rowEnd - rowStart, - rows: this.rows.slice(rowStart, rowEnd) + let resultSubset: azdata.QueryExecuteSubsetResult = { + message: undefined, + resultSubset: { + rowCount: rowEnd - rowStart, + rows: this.rows.slice(rowStart, rowEnd) + } }; return Promise.resolve(resultSubset); } @@ -324,7 +326,7 @@ class DataResourceDataProvider implements IGridDataProvider { maxRow = singleSelection.toRow + 1; columns = columns.slice(singleSelection.fromCell, singleSelection.toCell + 1); } - let getRows: ((index: number, rowCount: number) => ICellValue[][]) = (index, rowCount) => { + let getRows: ((index: number, rowCount: number) => azdata.DbCellValue[][]) = (index, rowCount) => { // Offset for selections by adding the selection startRow to the index index = index + minRow; if (rowLength === 0 || index < 0 || index >= maxRow) { diff --git a/src/sql/workbench/contrib/query/browser/gridPanel.ts b/src/sql/workbench/contrib/query/browser/gridPanel.ts index a1d566f94a..3fb394b3ad 100644 --- a/src/sql/workbench/contrib/query/browser/gridPanel.ts +++ b/src/sql/workbench/contrib/query/browser/gridPanel.ts @@ -7,7 +7,6 @@ import 'vs/css!./media/gridPanel'; import { attachTableStyler } from 'sql/platform/theme/common/styler'; 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 { Table } from 'sql/base/browser/ui/table/table'; import { ScrollableSplitView, IView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview'; @@ -22,6 +21,8 @@ import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugi import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin'; import { ITableStyles, ITableMouseEvent } from 'sql/base/browser/ui/table/interfaces'; +import * as azdata from 'azdata'; + import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -122,7 +123,7 @@ export class GridPanel extends Disposable { } this.reset(); })); - this.addResultSet(this.runner.batchSets.reduce((p, e) => { + this.addResultSet(this.runner.batchSets.reduce((p, e) => { if (this.configurationService.getValue('sql.results.streaming')) { p = p.concat(e.resultSetSummaries); } else { @@ -140,8 +141,8 @@ export class GridPanel extends Disposable { this.splitView.setScrollPosition(this.state.scrollPosition); } - private onResultSet(resultSet: ResultSetSummary | ResultSetSummary[]) { - let resultsToAdd: ResultSetSummary[]; + private onResultSet(resultSet: azdata.ResultSetSummary | azdata.ResultSetSummary[]) { + let resultsToAdd: azdata.ResultSetSummary[]; if (!Array.isArray(resultSet)) { resultsToAdd = [resultSet]; } else { @@ -169,8 +170,8 @@ export class GridPanel extends Disposable { } } - private updateResultSet(resultSet: ResultSetSummary | ResultSetSummary[]) { - let resultsToUpdate: ResultSetSummary[]; + private updateResultSet(resultSet: azdata.ResultSetSummary | azdata.ResultSetSummary[]) { + let resultsToUpdate: azdata.ResultSetSummary[]; if (!Array.isArray(resultSet)) { resultsToUpdate = [resultSet]; } else { @@ -202,7 +203,7 @@ export class GridPanel extends Disposable { } } - private addResultSet(resultSet: ResultSetSummary[]) { + private addResultSet(resultSet: azdata.ResultSetSummary[]) { let tables: GridTable[] = []; for (let set of resultSet) { @@ -315,7 +316,7 @@ export class GridPanel extends Disposable { export interface IDataSet { rowCount: number; - columnInfo: IColumn[]; + columnInfo: azdata.IDbColumn[]; } export abstract class GridTableBase extends Disposable implements IView { @@ -362,7 +363,7 @@ export abstract class GridTableBase extends Disposable implements IView { constructor( state: GridTableState, - protected _resultSet: ResultSetSummary, + protected _resultSet: azdata.ResultSetSummary, protected contextMenuService: IContextMenuService, protected instantiationService: IInstantiationService, protected editorService: IEditorService, @@ -393,7 +394,7 @@ export abstract class GridTableBase extends Disposable implements IView { abstract get gridDataProvider(): IGridDataProvider; - public get resultSet(): ResultSetSummary { + public get resultSet(): azdata.ResultSetSummary { return this._resultSet; } @@ -586,7 +587,7 @@ export abstract class GridTableBase extends Disposable implements IView { // handle if a showplan link was clicked if (column && (column.isXml || column.isJson)) { this.gridDataProvider.getRowData(event.cell.row, 1).then(async d => { - let value = d.rows[0][event.cell.cell - 1]; + let value = d.resultSubset.rows[0][event.cell.cell - 1]; let content = value.displayValue; const input = this.untitledEditorService.create({ mode: column.isXml ? 'xml' : 'json', initialValue: content }); @@ -597,7 +598,7 @@ export abstract class GridTableBase extends Disposable implements IView { } } - public updateResult(resultSet: ResultSetSummary) { + public updateResult(resultSet: azdata.ResultSetSummary) { this._resultSet = resultSet; if (this.table && this.visible) { this.dataProvider.length = resultSet.rowCount; @@ -654,10 +655,10 @@ export abstract class GridTableBase extends Disposable implements IView { private loadData(offset: number, count: number): Thenable { return this.gridDataProvider.getRowData(offset, count).then(response => { - if (!response) { + if (!response.resultSubset) { return []; } - return response.rows.map(r => { + return response.resultSubset.rows.map(r => { let dataWithSchema = {}; // skip the first column since its a number column for (let i = 1; i < this.columns.length; i++) { @@ -755,7 +756,7 @@ class GridTable extends GridTableBase { private _gridDataProvider: IGridDataProvider; constructor( private _runner: QueryRunner, - resultSet: ResultSetSummary, + resultSet: azdata.ResultSetSummary, state: GridTableState, @IContextMenuService contextMenuService: IContextMenuService, @IInstantiationService instantiationService: IInstantiationService, diff --git a/src/sql/workbench/contrib/query/browser/messagePanel.ts b/src/sql/workbench/contrib/query/browser/messagePanel.ts index b714444eda..9a7f97ea5e 100644 --- a/src/sql/workbench/contrib/query/browser/messagePanel.ts +++ b/src/sql/workbench/contrib/query/browser/messagePanel.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/messagePanel'; -import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; -import { IQueryMessage } from 'sql/workbench/services/query/common/query'; +import QueryRunner, { IQueryMessage } from 'sql/workbench/services/query/common/queryRunner'; + +import { ISelectionData } from 'azdata'; import { ITreeRenderer, IDataSource, ITreeNode, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { generateUuid } from 'vs/base/common/uuid'; @@ -31,7 +32,6 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IDataTreeViewState } from 'vs/base/browser/ui/tree/dataTree'; -import { IRange } from 'vs/editor/common/core/range'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface IResultMessageIntern { @@ -40,7 +40,8 @@ export interface IResultMessageIntern { isError: boolean; time?: string | Date; message: string; - range?: IRange; + selection?: ISelectionData; + } export interface IMessagePanelMessage { @@ -49,7 +50,7 @@ export interface IMessagePanelMessage { } export interface IMessagePanelBatchMessage extends IMessagePanelMessage { - range: IRange; + selection: ISelectionData; time: string; } @@ -269,7 +270,7 @@ class MessagePanelDelegate extends CachedListVirtualDelegate { let editor = this.editorService.activeEditorPane as QueryEditor; const codeEditor = editor.getControl(); codeEditor.focus(); - codeEditor.setSelection(node.element.range); - codeEditor.revealRangeInCenterIfOutsideViewport(node.element.range); + codeEditor.setSelection(selection); + codeEditor.revealRangeInCenterIfOutsideViewport(selection); })); } } diff --git a/src/sql/workbench/contrib/query/browser/queryActions.ts b/src/sql/workbench/contrib/query/browser/queryActions.ts index faf8e2de18..c05cf92bbf 100644 --- a/src/sql/workbench/contrib/query/browser/queryActions.ts +++ b/src/sql/workbench/contrib/query/browser/queryActions.ts @@ -14,7 +14,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import Severity from 'vs/base/common/severity'; import { append, $ } from 'vs/base/browser/dom'; -import { QueryExecutionOptions } from 'azdata'; +import { ISelectionData, QueryExecutionOptions } from 'azdata'; import { IConnectionManagementService, IConnectionParams, @@ -43,7 +43,6 @@ import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilit import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement'; 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 @@ -102,12 +101,12 @@ export abstract class QueryTaskbarAction extends Action { * Connects the given editor to it's current URI. * Public for testing only. */ - protected connectEditor(editor: QueryEditor, runQueryOnCompletion?: RunQueryOnConnectionMode, range?: IRange): void { + protected connectEditor(editor: QueryEditor, runQueryOnCompletion?: RunQueryOnConnectionMode, selection?: ISelectionData): void { let params: INewConnectionParams = { input: editor.input, connectionType: ConnectionType.editor, runQueryOnCompletion: runQueryOnCompletion ? runQueryOnCompletion : RunQueryOnConnectionMode.none, - queryRange: range + querySelection: selection }; this.connectionManagementService.showConnectionDialog(params); } @@ -242,8 +241,8 @@ export class RunQueryAction extends QueryTaskbarAction { if (this.isConnected(editor)) { // if the selection isn't empty then execute the selection // otherwise, either run the statement or the script depending on parameter - let selection = editor.getSelection(); - if (runCurrentStatement && selection) { + let selection: ISelectionData = editor.getSelection(false); + if (runCurrentStatement && selection && this.isCursorPosition(selection)) { editor.input.runQueryStatement(selection); } else { // get the selection again this time with trimming @@ -252,6 +251,11 @@ export class RunQueryAction extends QueryTaskbarAction { } } } + + protected isCursorPosition(selection: ISelectionData) { + return selection.startLine === selection.endLine + && selection.startColumn === selection.endColumn; + } } /** diff --git a/src/sql/workbench/contrib/query/browser/queryEditor.ts b/src/sql/workbench/contrib/query/browser/queryEditor.ts index 5e2177b486..79ffca210e 100644 --- a/src/sql/workbench/contrib/query/browser/queryEditor.ts +++ b/src/sql/workbench/contrib/query/browser/queryEditor.ts @@ -23,6 +23,7 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ISelectionData } from 'azdata'; import { IActionViewItem, IAction } from 'vs/base/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; @@ -35,7 +36,6 @@ import { QueryResultsEditor } from 'sql/workbench/contrib/query/browser/queryRes import * as queryContext from 'sql/workbench/contrib/query/common/queryContext'; import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar'; 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'; @@ -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 * is no selected text. */ - public getSelection(checkIfRange: boolean = true): IRange { + public getSelection(checkIfRange: boolean = true): ISelectionData { if (this.currentTextEditor && this.currentTextEditor.getControl()) { let vscodeSelection = this.currentTextEditor.getControl().getSelection(); @@ -508,7 +508,13 @@ export class QueryEditor extends BaseEditor { !(vscodeSelection.getStartPosition().lineNumber === vscodeSelection.getEndPosition().lineNumber && vscodeSelection.getStartPosition().column === vscodeSelection.getEndPosition().column); if (!checkIfRange || isRange) { - return vscodeSelection; + let sqlToolsServiceSelection: ISelectionData = { + startLine: vscodeSelection.getStartPosition().lineNumber - 1, + startColumn: vscodeSelection.getStartPosition().column - 1, + endLine: vscodeSelection.getEndPosition().lineNumber - 1, + endColumn: vscodeSelection.getEndPosition().column - 1, + }; + return sqlToolsServiceSelection; } } @@ -516,7 +522,7 @@ export class QueryEditor extends BaseEditor { return undefined; } - public getAllSelection(): IRange { + public getAllSelection(): ISelectionData { if (this.currentTextEditor && this.currentTextEditor.getControl()) { let control = this.currentTextEditor.getControl(); let codeEditor: ICodeEditor = control; @@ -524,12 +530,13 @@ export class QueryEditor extends BaseEditor { let model = codeEditor.getModel(); let totalLines = model.getLineCount(); let endColumn = model.getLineMaxColumn(totalLines); - return { - startLineNumber: 1, - startColumn: 1, - endLineNumber: totalLines, - endColumn: endColumn, + let selection: ISelectionData = { + startLine: 0, + startColumn: 0, + endLine: totalLines - 1, + endColumn: endColumn - 1, }; + return selection; } } return undefined; diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts index a42fb2c2c2..64e199a4c5 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts @@ -5,6 +5,8 @@ import { Emitter, Event } from 'vs/base/common/event'; +import { ISelectionData } from 'azdata'; + import { IConnectionParams, INewConnectionParams, @@ -32,7 +34,6 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; -import { IRange } from 'vs/editor/common/core/range'; suite('SQL QueryAction Tests', () => { @@ -221,9 +222,9 @@ suite('SQL QueryAction Tests', () => { let countCalledShowDialog: number = 0; let countCalledRunQuery: number = 0; let showDialogConnectionParams: INewConnectionParams = undefined; - let runQuerySelection: IRange = undefined; - let selectionToReturnInGetSelection: IRange = undefined; - let predefinedSelection: IRange = { startLineNumber: 1, startColumn: 2, endLineNumber: 3, endColumn: 4 }; + let runQuerySelection: ISelectionData = undefined; + let selectionToReturnInGetSelection: ISelectionData = undefined; + let predefinedSelection: ISelectionData = { startLine: 1, startColumn: 2, endLine: 3, endColumn: 4 }; // ... Mock "getSelection" in QueryEditor const workbenchinstantiationService = workbenchInstantiationService(); @@ -233,11 +234,11 @@ suite('SQL QueryAction Tests', () => { 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.runQuery(TypeMoq.It.isAny())).callback((selection: IRange) => { + queryInput.setup(x => x.runQuery(TypeMoq.It.isAny())).callback((selection: ISelectionData) => { runQuerySelection = selection; countCalledRunQuery++; }); - queryInput.setup(x => x.runQuery(undefined)).callback((selection: IRange) => { + queryInput.setup(x => x.runQuery(undefined)).callback((selection: ISelectionData) => { runQuerySelection = selection; countCalledRunQuery++; }); @@ -276,7 +277,7 @@ suite('SQL QueryAction Tests', () => { assert.equal(countCalledShowDialog, 1, 'run should call showDialog'); assert.equal(countCalledRunQuery, 0, 'run should not call runQuery'); assert.equal(showDialogConnectionParams.connectionType, ConnectionType.editor, 'connectionType should be queryEditor'); - assert.equal(showDialogConnectionParams.queryRange, undefined, 'querySelection should be undefined'); + assert.equal(showDialogConnectionParams.querySelection, undefined, 'querySelection should be undefined'); ////// If I call run on RunQueryAction while disconnected and with a defined selection isConnected = false; @@ -287,11 +288,11 @@ suite('SQL QueryAction Tests', () => { assert.equal(countCalledShowDialog, 2, 'run should call showDialog again'); assert.equal(countCalledRunQuery, 0, 'run should not call runQuery'); assert.equal(showDialogConnectionParams.connectionType, ConnectionType.editor, 'connectionType should be queryEditor'); - assert.notEqual(showDialogConnectionParams.queryRange, undefined, 'There should not be an undefined selection in runQuery'); - assert.equal(showDialogConnectionParams.queryRange.startLineNumber, selectionToReturnInGetSelection.startLineNumber, 'startLine should match'); - assert.equal(showDialogConnectionParams.queryRange.startColumn, selectionToReturnInGetSelection.startColumn, 'startColumn should match'); - assert.equal(showDialogConnectionParams.queryRange.endLineNumber, selectionToReturnInGetSelection.endLineNumber, 'endLine should match'); - assert.equal(showDialogConnectionParams.queryRange.endColumn, selectionToReturnInGetSelection.endColumn, 'endColumn should match'); + assert.notEqual(showDialogConnectionParams.querySelection, undefined, 'There should not be an undefined selection in runQuery'); + assert.equal(showDialogConnectionParams.querySelection.startLine, selectionToReturnInGetSelection.startLine, 'startLine should match'); + assert.equal(showDialogConnectionParams.querySelection.startColumn, selectionToReturnInGetSelection.startColumn, 'startColumn should match'); + assert.equal(showDialogConnectionParams.querySelection.endLine, selectionToReturnInGetSelection.endLine, 'endLine should match'); + assert.equal(showDialogConnectionParams.querySelection.endColumn, selectionToReturnInGetSelection.endColumn, 'endColumn should match'); ////// If I call run on RunQueryAction while connected and with an undefined selection isConnected = true; @@ -312,9 +313,9 @@ suite('SQL QueryAction Tests', () => { assert.equal(countCalledShowDialog, 2, 'run should not call showDialog'); assert.equal(countCalledRunQuery, 2, 'run should call runQuery again'); assert.notEqual(runQuerySelection, undefined, 'There should not be an undefined selection in runQuery'); - assert.equal(runQuerySelection.startLineNumber, selectionToReturnInGetSelection.startLineNumber, 'startLine should match'); + assert.equal(runQuerySelection.startLine, selectionToReturnInGetSelection.startLine, 'startLine should match'); assert.equal(runQuerySelection.startColumn, selectionToReturnInGetSelection.startColumn, 'startColumn should match'); - assert.equal(runQuerySelection.endLineNumber, selectionToReturnInGetSelection.endLineNumber, 'endLine should match'); + assert.equal(runQuerySelection.endLine, selectionToReturnInGetSelection.endLine, 'endLine should match'); assert.equal(runQuerySelection.endColumn, selectionToReturnInGetSelection.endColumn, 'endColumn should match'); }); diff --git a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts index 06e646e759..b31a55ba51 100644 --- a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts +++ b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts @@ -349,7 +349,7 @@ suite('SQL ConnectionManagementService tests', () => { onConnectCanceled: undefined, uri: uri }, - queryRange: undefined, + querySelection: undefined, runQueryOnCompletion: RunQueryOnConnectionMode.none }, saveTheConnection: true, @@ -456,7 +456,7 @@ suite('SQL ConnectionManagementService tests', () => { onConnectCanceled: undefined, uri: uri1, }, - queryRange: undefined, + querySelection: undefined, runQueryOnCompletion: RunQueryOnConnectionMode.none, isEditConnection: false }, @@ -494,7 +494,7 @@ suite('SQL ConnectionManagementService tests', () => { onConnectCanceled: undefined, uri: uri1 }, - queryRange: undefined, + querySelection: undefined, runQueryOnCompletion: RunQueryOnConnectionMode.none, isEditConnection: false }, @@ -705,7 +705,7 @@ suite('SQL ConnectionManagementService tests', () => { onConnectCanceled: undefined, uri: uri }, - queryRange: undefined, + querySelection: undefined, runQueryOnCompletion: RunQueryOnConnectionMode.none }, saveTheConnection: true, diff --git a/src/sql/workbench/services/editData/common/editQueryRunner.ts b/src/sql/workbench/services/editData/common/editQueryRunner.ts deleted file mode 100644 index 805465275b..0000000000 --- a/src/sql/workbench/services/editData/common/editQueryRunner.ts +++ /dev/null @@ -1,123 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as azdata from 'azdata'; -import * as 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()); - 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 { - // 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 { - 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 { - return this.queryManagementService.updateCell(ownerUri, rowId, columnId, newValue); - } - - public commitEdit(ownerUri: string): Promise { - return this.queryManagementService.commitEdit(ownerUri); - } - - public createRow(ownerUri: string): Promise { - return this.queryManagementService.createRow(ownerUri).then(result => { - return result; - }); - } - - public deleteRow(ownerUri: string, rowId: number): Promise { - return this.queryManagementService.deleteRow(ownerUri, rowId); - } - - public revertCell(ownerUri: string, rowId: number, columnId: number): Promise { - return this.queryManagementService.revertCell(ownerUri, rowId, columnId); - } - - public revertRow(ownerUri: string, rowId: number): Promise { - return this.queryManagementService.revertRow(ownerUri, rowId); - } - - public disposeEdit(ownerUri: string): Promise { - return this.queryManagementService.disposeEdit(ownerUri); - } - -} diff --git a/src/sql/workbench/services/insights/browser/insightsDialogController.ts b/src/sql/workbench/services/insights/browser/insightsDialogController.ts index 40079bc5ac..1c1dddad35 100644 --- a/src/sql/workbench/services/insights/browser/insightsDialogController.ts +++ b/src/sql/workbench/services/insights/browser/insightsDialogController.ts @@ -6,11 +6,12 @@ import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; 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 { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; import { resolveQueryFilePath } from '../common/insightsUtils'; +import { DbCellValue, IDbColumn, QueryExecuteSubsetResult } from 'azdata'; + import Severity from 'vs/base/common/severity'; import * as types from 'vs/base/common/types'; import * as nls from 'vs/nls'; @@ -26,8 +27,8 @@ export class InsightsDialogController { private _queryRunner: QueryRunner; private _connectionProfile: IConnectionProfile; private _connectionUri: string; - private _columns: IColumn[]; - private _rows: ICellValue[][]; + private _columns: IDbColumn[]; + private _rows: DbCellValue[][]; constructor( private readonly _model: IInsightsDialogModel, @@ -159,13 +160,13 @@ export class InsightsDialogController { ) { let resultset = batch.resultSetSummaries[0]; this._columns = resultset.columnInfo; - let rows: ResultSetSubset; + let rows: QueryExecuteSubsetResult; try { rows = await this._queryRunner.getQueryRows(0, resultset.rowCount, batch.id, resultset.id); } catch (e) { return Promise.reject(e); } - this._rows = rows.rows; + this._rows = rows.resultSubset.rows; this.updateModel(); } } diff --git a/src/sql/workbench/services/insights/test/browser/insightsDialogController.test.ts b/src/sql/workbench/services/insights/test/browser/insightsDialogController.test.ts index 2d9741ed73..4a4e8a5782 100644 --- a/src/sql/workbench/services/insights/test/browser/insightsDialogController.test.ts +++ b/src/sql/workbench/services/insights/test/browser/insightsDialogController.test.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { InsightsDialogController } from 'sql/workbench/services/insights/browser/insightsDialogController'; -import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; -import { IQueryMessage, BatchSummary, IColumn, ResultSetSubset } from 'sql/workbench/services/query/common/query'; +import QueryRunner, { IQueryMessage } from 'sql/workbench/services/query/common/queryRunner'; import { ConnectionManagementService } from 'sql/workbench/services/connection/browser/connectionManagementService'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import * as azdata from 'azdata'; import { equal } from 'assert'; import { Mock, MockBehavior, It } from 'typemoq'; 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.onMessage).returns(x => new Emitter<[IQueryMessage]>().event); querymock.setup(x => x.batchSets).returns(x => { - return >[ + return >[ { id: 0, resultSetSummaries: [ { - columnInfo: >columns.map(c => { return { columnName: c }; }), + columnInfo: >columns.map(c => { return { columnName: c }; }), id: 0, rowCount: data.length } @@ -129,9 +129,11 @@ function getPrimedQueryRunner(data: string[][], columns: string[]): IPrimedQuery }); querymock.setup(x => x.getQueryRows(It.isAnyNumber(), It.isAnyNumber(), It.isAnyNumber(), It.isAnyNumber())) - .returns(x => Promise.resolve({ - rowCount: data.length, - rows: data.map(r => r.map(c => { return { displayValue: c }; })) + .returns(x => Promise.resolve({ + resultSubset: { + rowCount: data.length, + rows: data.map(r => r.map(c => { return { displayValue: c }; })) + } })); querymock.setup(x => x.runQuery(It.isAnyString())).returns(x => Promise.resolve()); diff --git a/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts b/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts index 1b8dea0c67..f653d6f34e 100644 --- a/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts +++ b/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts @@ -3,10 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { nb, IResultMessage } from 'azdata'; +import { nb, QueryExecuteSubsetResult, IDbColumn, BatchSummary, IResultMessage, ResultSetSummary } from 'azdata'; import { localize } from 'vs/nls'; 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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import Severity from 'vs/base/common/severity'; @@ -344,7 +343,7 @@ class SqlKernel extends Disposable implements nb.IKernel { this._register(queryRunner.onMessage(messages => { // TODO handle showing a messages output (should be updated with all messages, only changing 1 output in total) for (const message of messages) { - if (this._future && isUndefinedOrNull(message.range)) { + if (this._future && isUndefinedOrNull(message.selection)) { this._future.handleMessage(message); } } @@ -384,7 +383,7 @@ export class SQLFuture extends Disposable implements FutureInternal { private doneDeferred = new Deferred(); private configuredMaxRows: number = MAX_ROWS; private _outputAddedPromises: Promise[] = []; - private _querySubsetResultMap: Map = new Map(); + private _querySubsetResultMap: Map = new Map(); private _errorOccurred: boolean = false; private _stopOnError: boolean = true; constructor( @@ -509,11 +508,11 @@ export class SQLFuture extends Disposable implements FutureInternal { this._querySubsetResultMap.set(resultSet.id, result); deferred.resolve(); }, (err) => { - this._querySubsetResultMap.set(resultSet.id, { rowCount: 0, rows: [] }); + this._querySubsetResultMap.set(resultSet.id, { message: '', resultSubset: { rowCount: 0, rows: [] } }); deferred.reject(err); }); } else { - this._querySubsetResultMap.set(resultSet.id, { rowCount: 0, rows: [] }); + this._querySubsetResultMap.set(resultSet.id, { message: '', resultSubset: { rowCount: 0, rows: [] } }); deferred.resolve(); } return deferred; @@ -526,7 +525,7 @@ export class SQLFuture extends Disposable implements FutureInternal { } } - private sendIOPubMessage(subsetResult: ResultSetSubset, resultSet: ResultSetSummary): void { + private sendIOPubMessage(subsetResult: QueryExecuteSubsetResult, resultSet: ResultSetSummary): void { let msg: nb.IIOPubMessage = { channel: 'iopub', type: 'iopub', @@ -561,7 +560,7 @@ export class SQLFuture extends Disposable implements FutureInternal { // no-op } - private convertToDataResource(columns: IColumn[], subsetResult: ResultSetSubset): IDataResource { + private convertToDataResource(columns: IDbColumn[], subsetResult: QueryExecuteSubsetResult): IDataResource { let columnsResources: IDataResourceSchema[] = []; columns.forEach(column => { columnsResources.push({ name: escape(column.columnName) }); @@ -570,7 +569,7 @@ export class SQLFuture extends Disposable implements FutureInternal { columnsFields.fields = columnsResources; return { schema: columnsFields, - data: subsetResult.rows.map(row => { + data: subsetResult.resultSubset.rows.map(row => { let rowObject: { [key: string]: any; } = {}; row.forEach((val, index) => { rowObject[index] = val.displayValue; @@ -580,9 +579,9 @@ export class SQLFuture extends Disposable implements FutureInternal { }; } - private convertToHtmlTable(columns: IColumn[], d: ResultSetSubset): string[] { + private convertToHtmlTable(columns: IDbColumn[], d: QueryExecuteSubsetResult): string[] { // Adding 3 for , column title rows,
- let htmlStringArr: string[] = new Array(d.rowCount + 3); + let htmlStringArr: string[] = new Array(d.resultSubset.rowCount + 3); htmlStringArr[0] = ''; if (columns.length > 0) { let columnHeaders = ''; @@ -593,7 +592,7 @@ export class SQLFuture extends Disposable implements FutureInternal { htmlStringArr[1] = columnHeaders; } let i = 2; - for (const row of d.rows) { + for (const row of d.resultSubset.rows) { let rowData = ''; for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) { rowData += ``; diff --git a/src/sql/workbench/services/query/common/gridDataProvider.ts b/src/sql/workbench/services/query/common/gridDataProvider.ts index 8dda4b49a1..0532cd954b 100644 --- a/src/sql/workbench/services/query/common/gridDataProvider.ts +++ b/src/sql/workbench/services/query/common/gridDataProvider.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as azdata from 'azdata'; import * as types from 'vs/base/common/types'; import { SaveFormat } from 'sql/workbench/services/query/common/resultSerializer'; -import { ResultSetSubset } from 'sql/workbench/services/query/common/query'; export interface IGridDataProvider { @@ -14,7 +14,7 @@ export interface IGridDataProvider { * @param rowStart 0-indexed start row to retrieve data from * @param numberOfRows total number of rows of data to retrieve */ - getRowData(rowStart: number, numberOfRows: number): Thenable; + getRowData(rowStart: number, numberOfRows: number): Thenable; /** * 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 - for (let rowIndex: number = 0; rowIndex < result.rows.length; rowIndex++) { - let row = result.rows[rowIndex]; + for (let rowIndex: number = 0; rowIndex < result.resultSubset.rows.length; rowIndex++) { + let row = result.resultSubset.rows[rowIndex]; let cellObjects = row.slice(range.fromCell, (range.toCell + 1)); // Remove newlines if requested let cells = provider.shouldRemoveNewLines() diff --git a/src/sql/workbench/services/query/common/query.ts b/src/sql/workbench/services/query/common/query.ts deleted file mode 100644 index e13f2dbfff..0000000000 --- a/src/sql/workbench/services/query/common/query.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { 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; -} diff --git a/src/sql/workbench/services/query/common/queryManagement.ts b/src/sql/workbench/services/query/common/queryManagement.ts index c95953c75f..f8514829f8 100644 --- a/src/sql/workbench/services/query/common/queryManagement.ts +++ b/src/sql/workbench/services/query/common/queryManagement.ts @@ -13,24 +13,11 @@ import { Event, Emitter } from 'vs/base/common/event'; import { keys } from 'vs/base/common/map'; import { assign } from 'vs/base/common/objects'; import { IAdsTelemetryService, ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry'; -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 IQueryManagementService = createDecorator(SERVICE_ID); -export interface QueryCancelResult { - messages: string; -} - -export interface ExecutionPlanOptions { - displayEstimatedQueryPlan?: boolean; - displayActualQueryPlan?: boolean; -} - export interface IQueryManagementService { _serviceBrand: undefined; @@ -41,13 +28,13 @@ export interface IQueryManagementService { getRegisteredProviders(): string[]; registerRunner(runner: QueryRunner, uri: string): void; - cancelQuery(ownerUri: string): Promise; - runQuery(ownerUri: string, range: IRange, runOptions?: ExecutionPlanOptions): Promise; + cancelQuery(ownerUri: string): Promise; + runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise; runQueryStatement(ownerUri: string, line: number, column: number): Promise; runQueryString(ownerUri: string, queryString: string): Promise; runQueryAndReturn(ownerUri: string, queryString: string): Promise; parseSyntax(ownerUri: string, query: string): Promise; - getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise; + getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise; disposeQuery(ownerUri: string): Promise; saveResults(requestParams: azdata.SaveResultsRequestParams): Promise; setQueryExecutionOptions(uri: string, options: azdata.QueryExecutionOptions): Promise; @@ -80,7 +67,7 @@ export interface IQueryManagementService { */ export interface IQueryRequestHandler { cancelQuery(ownerUri: string): Promise; - runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: ExecutionPlanOptions): Promise; + runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise; runQueryStatement(ownerUri: string, line: number, column: number): Promise; runQueryString(ownerUri: string, queryString: string): Promise; runQueryAndReturn(ownerUri: string, queryString: string): Promise; @@ -142,7 +129,7 @@ export class QueryManagementService implements IQueryManagementService { // Handles logic to run the given handlerCallback at the appropriate time. If the given runner is // undefined, the handlerCallback is put on the _handlerCallbackQueue to be run once the runner is set // public for testing only - private enqueueOrRun(handlerCallback: (runnerParam: QueryRunner) => void, runner?: QueryRunner): void { + private enqueueOrRun(handlerCallback: (runnerParam: QueryRunner) => void, runner: QueryRunner): void { if (runner === undefined) { this._handlerCallbackQueue.push(handlerCallback); } else { @@ -150,9 +137,9 @@ export class QueryManagementService implements IQueryManagementService { } } - private _notify(ownerUri: string, sendNotification: (runner: QueryRunner | EditQueryRunner) => void): void { + private _notify(ownerUri: string, sendNotification: (runner: QueryRunner) => void): void { let runner = this._queryRunners.get(ownerUri); - this.enqueueOrRun(sendNotification, runner); + this.enqueueOrRun(sendNotification, runner!); } public addQueryRequestHandler(queryType: string, handler: IQueryRequestHandler): IDisposable { @@ -178,7 +165,7 @@ export class QueryManagementService implements IQueryManagementService { return Array.from(keys(this._requestHandlers)); } - private addTelemetry(eventName: string, ownerUri: string, runOptions?: ExecutionPlanOptions): void { + private addTelemetry(eventName: string, ownerUri: string, runOptions?: azdata.ExecutionPlanOptions): void { const providerId: string = this._connectionService.getProviderIdFromUri(ownerUri); const data: ITelemetryEventProperties = { provider: providerId, @@ -210,59 +197,51 @@ export class QueryManagementService implements IQueryManagementService { } } - public cancelQuery(ownerUri: string): Promise { + public cancelQuery(ownerUri: string): Promise { this.addTelemetry(TelemetryKeys.CancelQuery, ownerUri); return this._runAction(ownerUri, (runner) => { return runner.cancelQuery(ownerUri); }); } - - public runQuery(ownerUri: string, range?: IRange, runOptions?: ExecutionPlanOptions): Promise { + public runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise { this.addTelemetry(TelemetryKeys.RunQuery, ownerUri, runOptions); return this._runAction(ownerUri, (runner) => { - return runner.runQuery(ownerUri, rangeToSelectionData(range), runOptions); + return runner.runQuery(ownerUri, selection, runOptions); }); } - public runQueryStatement(ownerUri: string, line: number, column: number): Promise { this.addTelemetry(TelemetryKeys.RunQueryStatement, ownerUri); return this._runAction(ownerUri, (runner) => { - 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 + return runner.runQueryStatement(ownerUri, line, column); }); } - public runQueryString(ownerUri: string, queryString: string): Promise { this.addTelemetry(TelemetryKeys.RunQueryString, ownerUri); return this._runAction(ownerUri, (runner) => { return runner.runQueryString(ownerUri, queryString); }); } - public runQueryAndReturn(ownerUri: string, queryString: string): Promise { return this._runAction(ownerUri, (runner) => { return runner.runQueryAndReturn(ownerUri, queryString); }); } - public parseSyntax(ownerUri: string, query: string): Promise { return this._runAction(ownerUri, (runner) => { return runner.parseSyntax(ownerUri, query); }); } - - public async getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise { + public getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise { return this._runAction(rowData.ownerUri, (runner) => { - return runner.getQueryRows(rowData).then(r => r.resultSubset); + return runner.getQueryRows(rowData); }); } - public disposeQuery(ownerUri: string): Promise { this._queryRunners.delete(ownerUri); return this._runAction(ownerUri, (runner) => { return runner.disposeQuery(ownerUri); }); } - public setQueryExecutionOptions(ownerUri: string, options: azdata.QueryExecutionOptions): Promise { return this._runAction(ownerUri, (runner) => { return runner.setQueryExecutionOptions(ownerUri, options); @@ -277,38 +256,37 @@ export class QueryManagementService implements IQueryManagementService { public onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void { this._notify(result.ownerUri, (runner: QueryRunner) => { - runner.handleQueryComplete(result.batchSummaries.map(s => ({ ...s, range: selectionDataToRange(s.selection) }))); + runner.handleQueryComplete(result); }); } - public onBatchStart(batchInfo: azdata.QueryExecuteBatchNotificationParams): void { this._notify(batchInfo.ownerUri, (runner: QueryRunner) => { - runner.handleBatchStart({ ...batchInfo.batchSummary, range: selectionDataToRange(batchInfo.batchSummary.selection) }); + runner.handleBatchStart(batchInfo); }); } public onBatchComplete(batchInfo: azdata.QueryExecuteBatchNotificationParams): void { this._notify(batchInfo.ownerUri, (runner: QueryRunner) => { - runner.handleBatchComplete({ range: selectionDataToRange(batchInfo.batchSummary.selection), ...batchInfo.batchSummary }); + runner.handleBatchComplete(batchInfo); }); } public onResultSetAvailable(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void { this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => { - runner.handleResultSetAvailable(resultSetInfo.resultSetSummary); + runner.handleResultSetAvailable(resultSetInfo); }); } public onResultSetUpdated(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void { this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => { - runner.handleResultSetUpdated(resultSetInfo.resultSetSummary); + runner.handleResultSetUpdated(resultSetInfo); }); } public onMessage(messagesMap: Map): void { for (const [uri, messages] of messagesMap) { this._notify(uri, (runner: QueryRunner) => { - runner.handleMessage(messages.map(m => m.message)); + runner.handleMessage(messages); }); } } @@ -321,8 +299,8 @@ export class QueryManagementService implements IQueryManagementService { } public onEditSessionReady(ownerUri: string, success: boolean, message: string): void { - this._notify(ownerUri, runner => { - (runner as EditQueryRunner).handleEditSessionReady(ownerUri, success, message); + this._notify(ownerUri, (runner: QueryRunner) => { + runner.handleEditSessionReady(ownerUri, success, message); }); } @@ -374,11 +352,3 @@ 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 }; -} diff --git a/src/sql/workbench/services/query/common/queryModel.ts b/src/sql/workbench/services/query/common/queryModel.ts index 299cce4111..24f4ace9c3 100644 --- a/src/sql/workbench/services/query/common/queryModel.ts +++ b/src/sql/workbench/services/query/common/queryModel.ts @@ -3,12 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; -import { IQueryMessage, ResultSetSubset } from 'sql/workbench/services/query/common/query'; +import QueryRunner, { IQueryMessage } from 'sql/workbench/services/query/common/queryRunner'; import { DataService } from 'sql/workbench/services/query/common/dataService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { + ISelectionData, + ResultSetSubset, EditUpdateCellResult, EditSessionReadyParams, EditSubsetResult, @@ -18,7 +19,6 @@ import { queryeditor } from 'azdata'; import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService'; -import { IRange } from 'vs/editor/common/core/range'; export const SERVICE_ID = 'queryModelService'; @@ -31,7 +31,7 @@ export interface IQueryPlanInfo { } export interface IQueryInfo { - range: IRange[]; + selection: ISelectionData[]; messages: IQueryMessage[]; } @@ -51,8 +51,8 @@ export interface IQueryModelService { getQueryRunner(uri: string): QueryRunner | undefined; getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise; - runQuery(uri: string, range: IRange | undefined, runOptions?: ExecutionPlanOptions): void; - runQueryStatement(uri: string, range: IRange | undefined): void; + runQuery(uri: string, selection: ISelectionData | undefined, runOptions?: ExecutionPlanOptions): void; + runQueryStatement(uri: string, selection: ISelectionData | undefined): void; runQueryString(uri: string, selection: string | undefined): void; cancelQuery(input: QueryRunner | string): void; disposeQuery(uri: string): void; diff --git a/src/sql/workbench/services/query/common/queryModelService.ts b/src/sql/workbench/services/query/common/queryModelService.ts index 74f1fd0764..976bfa548b 100644 --- a/src/sql/workbench/services/query/common/queryModelService.ts +++ b/src/sql/workbench/services/query/common/queryModelService.ts @@ -5,7 +5,6 @@ import * as GridContentEvents from 'sql/workbench/services/query/common/gridContentEvents'; 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 { IQueryModelService, IQueryEvent } from 'sql/workbench/services/query/common/queryModel'; @@ -18,8 +17,6 @@ import * as strings from 'vs/base/common/strings'; import * as types from 'vs/base/common/types'; import { INotificationService } from 'vs/platform/notification/common/notification'; import Severity from 'vs/base/common/severity'; -import EditQueryRunner from 'sql/workbench/services/editData/common/editQueryRunner'; -import { IRange } from 'vs/editor/common/core/range'; const selectionSnippetMaxLen = 100; @@ -32,10 +29,10 @@ export interface QueryEvent { * Holds information about the state of a query runner */ export class QueryInfo { - public queryRunner?: EditQueryRunner; + public queryRunner?: QueryRunner; public dataService?: DataService; public queryEventQueue?: QueryEvent[]; - public range?: Array; + public selection?: Array; public selectionSnippet?: string; // Notes if the angular components have obtained the DataService. If not, all messages sent @@ -45,7 +42,7 @@ export class QueryInfo { constructor() { this.dataServiceReady = false; this.queryEventQueue = []; - this.range = []; + this.selection = []; } } @@ -131,10 +128,10 @@ export class QueryModelService implements IQueryModelService { /** * Get more data rows from the current resultSets from the service layer */ - public getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise { + public getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise { if (this._queryInfoMap.has(uri)) { return this._getQueryInfo(uri)!.queryRunner!.getQueryRows(rowStart, numberOfRows, batchId, resultId).then(results => { - return results; + return results.resultSubset; }); } else { return Promise.resolve(undefined); @@ -173,15 +170,15 @@ export class QueryModelService implements IQueryModelService { /** * Run a query for the given URI with the given text selection */ - public async runQuery(uri: string, range: IRange, runOptions?: azdata.ExecutionPlanOptions): Promise { - return this.doRunQuery(uri, range, false, runOptions); + public async runQuery(uri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise { + return this.doRunQuery(uri, selection, false, runOptions); } /** * Run the current SQL statement for the given URI */ - public async runQueryStatement(uri: string, range: IRange): Promise { - return this.doRunQuery(uri, range, true); + public async runQueryStatement(uri: string, selection: azdata.ISelectionData): Promise { + return this.doRunQuery(uri, selection, true); } /** @@ -194,7 +191,7 @@ export class QueryModelService implements IQueryModelService { /** * Run Query implementation */ - private async doRunQuery(uri: string, range: IRange | string, + private async doRunQuery(uri: string, selection: azdata.ISelectionData | string, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise { // Reuse existing query runner if it exists let queryRunner: QueryRunner | undefined; @@ -211,7 +208,7 @@ export class QueryModelService implements IQueryModelService { // If the query is not in progress, we can reuse the query runner queryRunner = existingRunner!; - info.range = []; + info.selection = []; info.selectionSnippet = undefined; } else { // We do not have a query runner for this editor, so create a new one @@ -220,23 +217,23 @@ export class QueryModelService implements IQueryModelService { queryRunner = info.queryRunner!; } - if (types.isString(range)) { + if (types.isString(selection)) { // Run the query string in this case - if (range.length < selectionSnippetMaxLen) { - info.selectionSnippet = range; + if (selection.length < selectionSnippetMaxLen) { + info.selectionSnippet = selection; } else { - info.selectionSnippet = range.substring(0, selectionSnippetMaxLen - 3) + '...'; + info.selectionSnippet = selection.substring(0, selectionSnippetMaxLen - 3) + '...'; } - return queryRunner.runQuery(range, runOptions); + return queryRunner.runQuery(selection, runOptions); } else if (runCurrentStatement) { - return queryRunner.runQueryStatement(range); + return queryRunner.runQueryStatement(selection); } else { - return queryRunner.runQuery(range, runOptions); + return queryRunner.runQuery(selection, runOptions); } } private initQueryRunner(uri: string): QueryInfo { - let queryRunner = this._instantiationService.createInstance(EditQueryRunner, uri); + let queryRunner = this._instantiationService.createInstance(QueryRunner, uri); let info = new QueryInfo(); queryRunner.onResultSet(e => { this._fireQueryEvent(uri, 'resultSet', e); @@ -244,14 +241,14 @@ export class QueryModelService implements IQueryModelService { queryRunner.onBatchStart(b => { let link = undefined; let messageText = nls.localize('runQueryBatchStartMessage', "Started executing query at "); - if (b.range) { + if (b.selection) { if (info.selectionSnippet) { // This indicates it's a query string. Do not include line information since it'll be inaccurate, but show some of the // executed query text messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet); } else { link = { - text: strings.format(nls.localize('runQueryBatchStartLine', "Line {0}"), b.range.startLineNumber) + text: strings.format(nls.localize('runQueryBatchStartLine', "Line {0}"), b.selection.startLine + 1) }; } } @@ -263,7 +260,7 @@ export class QueryModelService implements IQueryModelService { link: link }; this._fireQueryEvent(uri, 'message', message); - info.range!.push(b.range); + info.selection!.push(this._validateSelection(b.selection)); }); queryRunner.onMessage(m => { this._fireQueryEvent(uri, 'message', m); @@ -277,7 +274,7 @@ export class QueryModelService implements IQueryModelService { uri: uri, queryInfo: { - range: info.range!, + selection: info.selection!, messages: info.queryRunner!.messages } }; @@ -295,7 +292,7 @@ export class QueryModelService implements IQueryModelService { uri: uri, queryInfo: { - range: info.range!, + selection: info.selection!, messages: info.queryRunner!.messages } }; @@ -311,7 +308,7 @@ export class QueryModelService implements IQueryModelService { uri: uri, queryInfo: { - range: info.range!, + selection: info.selection!, messages: info.queryRunner!.messages } }; @@ -327,7 +324,7 @@ export class QueryModelService implements IQueryModelService { uri: planInfo.fileUri, queryInfo: { - range: info.range!, + selection: info.selection!, messages: info.queryRunner!.messages }, params: planInfo @@ -341,7 +338,7 @@ export class QueryModelService implements IQueryModelService { uri: uri, queryInfo: { - range: info.range!, + selection: info.selection!, messages: info.queryRunner!.messages }, params: resultSetInfo @@ -402,12 +399,12 @@ export class QueryModelService implements IQueryModelService { // EDIT DATA METHODS ///////////////////////////////////////////////////// async initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise { // Reuse existing query runner if it exists - let queryRunner: EditQueryRunner; + let queryRunner: QueryRunner; let info: QueryInfo; if (this._queryInfoMap.has(ownerUri)) { info = this._getQueryInfo(ownerUri)!; - let existingRunner = info.queryRunner!; + let existingRunner: QueryRunner = info.queryRunner!; // If the initialization is already in progress if (existingRunner.isExecuting) { @@ -420,7 +417,7 @@ export class QueryModelService implements IQueryModelService { // We do not have a query runner for this editor, so create a new one // and map it to the results uri - queryRunner = this._instantiationService.createInstance(EditQueryRunner, ownerUri); + queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri); const resultSetEventType = 'resultSet'; queryRunner.onResultSet(resultSet => { this._fireQueryEvent(ownerUri, resultSetEventType, resultSet); @@ -431,14 +428,14 @@ export class QueryModelService implements IQueryModelService { queryRunner.onBatchStart(batch => { let link = undefined; let messageText = nls.localize('runQueryBatchStartMessage', "Started executing query at "); - if (batch.range) { + if (batch.selection) { if (info.selectionSnippet) { // This indicates it's a query string. Do not include line information since it'll be inaccurate, but show some of the // executed query text messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet); } else { link = { - text: strings.format(nls.localize('runQueryBatchStartLine', "Line {0}"), batch.range.startLineNumber) + text: strings.format(nls.localize('runQueryBatchStartLine', "Line {0}"), batch.selection.startLine + 1) }; } } @@ -462,7 +459,7 @@ export class QueryModelService implements IQueryModelService { uri: ownerUri, queryInfo: { - range: info.range!, + selection: info.selection!, messages: info.queryRunner!.messages }, }; @@ -479,7 +476,7 @@ export class QueryModelService implements IQueryModelService { uri: ownerUri, queryInfo: { - range: info.range!, + selection: info.selection!, messages: info.queryRunner!.messages }, }; @@ -599,8 +596,8 @@ export class QueryModelService implements IQueryModelService { // PRIVATE METHODS ////////////////////////////////////////////////////// - private internalGetQueryRunner(ownerUri: string): EditQueryRunner | undefined { - let queryRunner: EditQueryRunner | undefined; + private internalGetQueryRunner(ownerUri: string): QueryRunner | undefined { + let queryRunner: QueryRunner | undefined; if (this._queryInfoMap.has(ownerUri)) { let existingRunner = this._getQueryInfo(ownerUri)!.queryRunner!; // If the query is not already executing then set it up @@ -651,4 +648,17 @@ export class QueryModelService implements IQueryModelService { public _getQueryInfo(uri: string): QueryInfo | undefined { return this._queryInfoMap.get(uri); } + + // TODO remove this funciton and its usages when #821 in vscode-mssql is fixed and + // the SqlToolsService version is updated in this repo - coquagli 4/19/2017 + private _validateSelection(selection: azdata.ISelectionData): azdata.ISelectionData { + if (!selection) { + selection = {}; + } + 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; + } } diff --git a/src/sql/workbench/services/query/common/queryRunner.ts b/src/sql/workbench/services/query/common/queryRunner.ts index 331c8bd4c9..53a54bd844 100644 --- a/src/sql/workbench/services/query/common/queryRunner.ts +++ b/src/sql/workbench/services/query/common/queryRunner.ts @@ -3,12 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IQueryManagementService, QueryCancelResult, ExecutionPlanOptions } from 'sql/workbench/services/query/common/queryManagement'; +import * as azdata from 'azdata'; + +import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement'; import * as Utils from 'sql/platform/connection/common/utils'; import { Deferred } from 'sql/base/common/promise'; import { IQueryPlanInfo } from 'sql/workbench/services/query/common/queryModel'; import { ResultSerializer, SaveFormat } from 'sql/workbench/services/query/common/resultSerializer'; +import Severity from 'vs/base/common/severity'; import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import * as types from 'vs/base/common/types'; @@ -24,8 +27,20 @@ import { IGridDataProvider, getResultsString } from 'sql/workbench/services/quer import { getErrorMessage } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; import { find } from 'vs/base/common/arrays'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { BatchSummary, IQueryMessage, ResultSetSummary, QueryExecuteSubsetParams, CompleteBatchSummary, IResultMessage, ResultSetSubset, BatchStartSummary } from './query'; + +export interface IEditSessionReadyEvent { + 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, @@ -35,10 +50,10 @@ export default class QueryRunner extends Disposable { // MEMBER VARIABLES //////////////////////////////////////////////////// private _resultLineOffset?: number; private _resultColumnOffset?: number; - protected _totalElapsedMilliseconds: number = 0; - protected _isExecuting: boolean = false; + private _totalElapsedMilliseconds: number = 0; + private _isExecuting: boolean = false; private _hasCompleted: boolean = false; - private _batchSets: BatchSummary[] = []; + private _batchSets: azdata.BatchSummary[] = []; private _messages: IQueryMessage[] = []; private registered = false; @@ -50,28 +65,31 @@ export default class QueryRunner extends Disposable { private _onMessage = this._register(new Emitter()); public get onMessage(): Event { return this._onMessage.event; } // this is the only way typemoq can moq this... needs investigation @todo anthonydresser 5/2/2019 - private readonly _onResultSet = this._register(new Emitter()); + private _onResultSet = this._register(new Emitter()); public readonly onResultSet = this._onResultSet.event; - private readonly _onResultSetUpdate = this._register(new Emitter()); + private _onResultSetUpdate = this._register(new Emitter()); public readonly onResultSetUpdate = this._onResultSetUpdate.event; - protected readonly _onQueryStart = this._register(new Emitter()); + private _onQueryStart = this._register(new Emitter()); public readonly onQueryStart: Event = this._onQueryStart.event; - private readonly _onQueryEnd = this._register(new Emitter()); + private _onQueryEnd = this._register(new Emitter()); public get onQueryEnd(): Event { return this._onQueryEnd.event; } - private readonly _onBatchStart = this._register(new Emitter()); - public readonly onBatchStart: Event = this._onBatchStart.event; + private _onBatchStart = this._register(new Emitter()); + public readonly onBatchStart: Event = this._onBatchStart.event; - private readonly _onBatchEnd = this._register(new Emitter()); - public readonly onBatchEnd: Event = this._onBatchEnd.event; + private _onBatchEnd = this._register(new Emitter()); + public readonly onBatchEnd: Event = this._onBatchEnd.event; - private readonly _onQueryPlanAvailable = this._register(new Emitter()); + private _onEditSessionReady = this._register(new Emitter()); + public readonly onEditSessionReady = this._onEditSessionReady.event; + + private _onQueryPlanAvailable = this._register(new Emitter()); public readonly onQueryPlanAvailable = this._onQueryPlanAvailable.event; - private readonly _onVisualize = this._register(new Emitter()); + private _onVisualize = this._register(new Emitter()); public readonly onVisualize = this._onVisualize.event; private _queryStartTime?: Date; @@ -86,11 +104,12 @@ export default class QueryRunner extends Disposable { // CONSTRUCTOR ///////////////////////////////////////////////////////// constructor( public uri: string, - @IQueryManagementService protected readonly queryManagementService: IQueryManagementService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, - @ILogService private readonly logService: ILogService + @IQueryManagementService private _queryManagementService: IQueryManagementService, + @INotificationService private _notificationService: INotificationService, + @IConfigurationService private _configurationService: IConfigurationService, + @IInstantiationService private instantiationService: IInstantiationService, + @ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService, + @ILogService private _logService: ILogService ) { super(); } @@ -106,7 +125,7 @@ export default class QueryRunner extends Disposable { /** * For public use only, for private use, directly access the member */ - public get batchSets(): BatchSummary[] { + public get batchSets(): azdata.BatchSummary[] { return this._batchSets.slice(0); } @@ -122,22 +141,22 @@ export default class QueryRunner extends Disposable { /** * Cancels the running query, if there is one */ - public cancelQuery(): Promise { - return this.queryManagementService.cancelQuery(this.uri); + public cancelQuery(): Promise { + return this._queryManagementService.cancelQuery(this.uri); } /** * Runs the query with the provided query * @param input Query string to execute */ - public runQuery(input: string, runOptions?: ExecutionPlanOptions): Promise; + public runQuery(input: string, runOptions?: azdata.ExecutionPlanOptions): Promise; /** * Runs the query by pulling the query from the document using the provided selection data * @param input selection data */ - public runQuery(input: IRange | undefined, runOptions?: ExecutionPlanOptions): Promise; - public runQuery(input: string | IRange | undefined, runOptions?: ExecutionPlanOptions): Promise { - if (types.isString(input) || types.isUndefined(input)) { + public runQuery(input: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise; + public runQuery(input: string | azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise { + if (types.isString(input)) { return this.doRunQuery(input, false, runOptions); } else { return this.doRunQuery(input, false, runOptions); @@ -148,7 +167,7 @@ export default class QueryRunner extends Disposable { * Runs the current SQL statement by pulling the query from the document using the provided selection data * @param input selection data */ - public runQueryStatement(input: IRange): Promise { + public runQueryStatement(input: azdata.ISelectionData): Promise { return this.doRunQuery(input, true); } @@ -156,9 +175,9 @@ export default class QueryRunner extends Disposable { * Implementation that runs the query with the provided query * @param input Query string to execute */ - private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Promise; - private doRunQuery(input: IRange | undefined, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Promise; - private doRunQuery(input: string | IRange | undefined, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Promise { + private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise; + private doRunQuery(input: azdata.ISelectionData, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise; + private doRunQuery(input: string | azdata.ISelectionData, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise { if (this.isExecuting) { return Promise.resolve(); } @@ -168,9 +187,9 @@ export default class QueryRunner extends Disposable { this._queryStartTime = undefined; this._queryEndTime = undefined; this._messages = []; - if (isRangeOrUndefined(input)) { + if (isSelectionOrUndefined(input)) { // Update internal state to show that we're executing the query - this._resultLineOffset = input ? input.startLineNumber : 0; + this._resultLineOffset = input ? input.startLine : 0; this._resultColumnOffset = input ? input.startColumn : 0; this._isExecuting = true; this._totalElapsedMilliseconds = 0; @@ -180,8 +199,8 @@ export default class QueryRunner extends Disposable { // Send the request to execute the query return runCurrentStatement - ? 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.runQueryStatement(this.uri, input.startLine, input.startColumn).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e)) + : this._queryManagementService.runQuery(this.uri, input, runOptions).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e)); } else { // Update internal state to show that we're executing the query this._isExecuting = true; @@ -189,7 +208,7 @@ export default class QueryRunner extends Disposable { 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)); } } @@ -199,39 +218,45 @@ export default class QueryRunner extends Disposable { // The query has started, so lets fire up the result pane if (!this.registered) { this.registered = true; - this.queryManagementService.registerRunner(this, this.uri); + this._queryManagementService.registerRunner(this, this.uri); } } private handleFailureRunQueryResult(error: any) { // Attempting to launch the query failed, show the error message - const eol = getEolString(this.textResourcePropertiesService, this.uri); + const eol = getEolString(this._textResourcePropertiesService, this.uri); if (error instanceof Error) { error = error.message; } let message = nls.localize('query.ExecutionFailedError', "Execution failed due to an unexpected error: {0}\t{1}", eol, error); - this.handleMessage([{ - isError: true, - message: message + this.handleMessage([{ + ownerUri: this.uri, + message: { + isError: true, + message: message + } }]); - this.handleQueryComplete(); + this.handleQueryComplete({ ownerUri: this.uri }); } /** * Handle a QueryComplete from the service layer */ - public handleQueryComplete(batchSummaries?: CompleteBatchSummary[]): void { + public handleQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void { // this also isn't exact but its the best we can do this._queryEndTime = new Date(); // Store the batch sets we got back as a source of "truth" this._isExecuting = false; this._hasCompleted = true; - this._batchSets = batchSummaries ? batchSummaries : []; + this._batchSets = result.batchSummaries ? result.batchSummaries : []; this._batchSets.map(batch => { - if (batch.range) { - batch.range = new Range(batch.range.startLineNumber + this._resultLineOffset, batch.range.startColumn + this._resultColumnOffset, batch.range.endLineNumber + this._resultLineOffset, batch.range.endColumn + this._resultColumnOffset); + if (batch.selection) { + batch.selection.startLine += this._resultLineOffset!; + batch.selection.startColumn += this._resultColumnOffset!; + batch.selection.endLine += this._resultLineOffset!; + batch.selection.endColumn += this._resultColumnOffset!; } }); @@ -252,20 +277,28 @@ export default class QueryRunner extends Disposable { /** * Handle a BatchStart from the service layer */ - public handleBatchStart(batch: BatchStartSummary): void { + public handleBatchStart(result: azdata.QueryExecuteBatchNotificationParams): void { + let batch = result.batchSummary; + // Recalculate the start and end lines, relative to the result line offset - if (batch.range) { - batch.range = new Range(batch.range.startLineNumber + this._resultLineOffset, batch.range.startColumn + this._resultColumnOffset, batch.range.endLineNumber + this._resultLineOffset, batch.range.endColumn + this._resultColumnOffset); + if (batch.selection) { + batch.selection.startLine += this._resultLineOffset!; + batch.selection.startColumn += this._resultColumnOffset!; + batch.selection.endLine += this._resultLineOffset!; + batch.selection.endColumn += this._resultColumnOffset!; } - // Store the batch - this._batchSets[batch.id] = { ...batch, resultSetSummaries: [], hasError: false }; + // Set the result sets as an empty array so that as result sets complete we can add to the list + batch.resultSetSummaries = []; - let message: IQueryMessage = { + // Store the batch + this._batchSets[batch.id] = batch; + + let message = { // account for index by 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), + message: nls.localize('query.message.startQuery', "Started executing query at Line {0}", batch.selection.startLine + 1), time: batch.executionStart, - range: batch.range, + selection: batch.selection, isError: false }; this._messages.push(message); @@ -276,7 +309,9 @@ export default class QueryRunner extends Disposable { /** * Handle a BatchComplete from the service layer */ - public handleBatchComplete(batch: CompleteBatchSummary): void { + public handleBatchComplete(result: azdata.QueryExecuteBatchNotificationParams): void { + let batch: azdata.BatchSummary = result.batchSummary; + // Store the batch again to get the rest of the data this._batchSets[batch.id] = batch; let executionTime = (Utils.parseTimeString(batch.executionElapsed) || 0); @@ -292,18 +327,19 @@ export default class QueryRunner extends Disposable { /** * Handle a ResultSetComplete from the service layer */ - public handleResultSetAvailable(resultSet?: ResultSetSummary): void { - if (resultSet) { - let batchSet: BatchSummary; + public handleResultSetAvailable(result: azdata.QueryExecuteResultSetNotificationParams): void { + if (result && result.resultSetSummary) { + let resultSet = result.resultSetSummary; + let batchSet: azdata.BatchSummary; if (!resultSet.batchId) { // Missing the batchId or processing batchId==0. In this case, default to always using the first batch in the list // or create one in the case the DMP extension didn't obey the contract perfectly if (this._batchSets.length > 0) { batchSet = this._batchSets[0]; } else { - batchSet = { + batchSet = { id: 0, - range: undefined, + selection: undefined, hasError: false, resultSetSummaries: [] }; @@ -314,15 +350,15 @@ export default class QueryRunner extends Disposable { } // handle getting queryPlanxml if we need too // check if this result has show plan, this needs work, it won't work for any other provider - let hasShowPlan = !!find(resultSet.columnInfo, e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan'); - if (hasShowPlan && resultSet.rowCount > 0) { + let hasShowPlan = !!find(result.resultSetSummary.columnInfo, e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan'); + if (hasShowPlan) { this._isQueryPlan = true; - this.getQueryRows(0, 1, resultSet.batchId, resultSet.id).then(e => { - if (e.rows) { - this._planXml.resolve(e.rows[0][0].displayValue); + this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => { + if (e.resultSubset.rows) { + this._planXml.resolve(e.resultSubset.rows[0][0].displayValue); } - }).catch((e) => this.logService.error(e)); + }).catch((e) => this._logService.error(e)); } // we will just ignore the set if we already have it // ideally this should never happen @@ -334,30 +370,31 @@ export default class QueryRunner extends Disposable { } } - public handleResultSetUpdated(resultSet?: ResultSetSummary): void { - if (resultSet) { - let batchSet: BatchSummary; + public handleResultSetUpdated(result: azdata.QueryExecuteResultSetNotificationParams): void { + if (result && result.resultSetSummary) { + let resultSet = result.resultSetSummary; + let batchSet: azdata.BatchSummary; batchSet = this._batchSets[resultSet.batchId]; // handle getting queryPlanxml if we need too // check if this result has show plan, this needs work, it won't work for any other provider - let hasShowPlan = !!resultSet.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan'); + let hasShowPlan = !!find(result.resultSetSummary.columnInfo, e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan'); if (hasShowPlan) { this._isQueryPlan = true; - this.getQueryRows(0, 1, resultSet.batchId, resultSet.id).then(e => { + this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => { - if (e.rows) { - let planXmlString = e.rows[0][0].displayValue; - this._planXml.resolve(e.rows[0][0].displayValue); + if (e.resultSubset.rows) { + let planXmlString = e.resultSubset.rows[0][0].displayValue; + this._planXml.resolve(e.resultSubset.rows[0][0].displayValue); // fire query plan available event if execution is completed - if (resultSet.complete) { + if (result.resultSetSummary.complete) { this._onQueryPlanAvailable.fire({ providerId: mssqlProviderName, - fileUri: this.uri, + fileUri: result.ownerUri, planXml: planXmlString }); } } - }).catch((e) => this.logService.error(e)); + }).catch((e) => this._logService.error(e)); } if (batchSet) { // Store the result set in the batch and emit that a result set has completed @@ -370,7 +407,8 @@ export default class QueryRunner extends Disposable { /** * Handle a Mssage from the service layer */ - public handleMessage(messages: IResultMessage[]): void { + public handleMessage(messagesObj: azdata.QueryExecuteMessageParams[]): void { + const messages = messagesObj.map(m => m.message); this._messages.push(...messages); // Send the message to the results pane @@ -380,8 +418,8 @@ export default class QueryRunner extends Disposable { /** * Get more data rows from the current resultSets from the service layer */ - public getQueryRows(rowStart: number, numberOfRows: number, batchIndex: number, resultSetIndex: number): Promise { - let rowData: QueryExecuteSubsetParams = { + public getQueryRows(rowStart: number, numberOfRows: number, batchIndex: number, resultSetIndex: number): Promise { + let rowData: azdata.QueryExecuteSubsetParams = { ownerUri: this.uri, resultSetIndex: resultSetIndex, rowsCount: numberOfRows, @@ -389,7 +427,7 @@ export default class QueryRunner extends Disposable { batchIndex: batchIndex }; - return this.queryManagementService.getQueryRows(rowData).then(r => r, error => { + return this._queryManagementService.getQueryRows(rowData).then(r => r, error => { // this._notificationService.notify({ // severity: Severity.Error, // message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error) @@ -398,11 +436,104 @@ 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 { + // 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 { + const self = this; + let rowData: azdata.EditSubsetParams = { + ownerUri: this.uri, + rowCount: numberOfRows, + rowStartIndex: rowStart + }; + + return new Promise((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 { + return this._queryManagementService.updateCell(ownerUri, rowId, columnId, newValue); + } + + public commitEdit(ownerUri: string): Promise { + return this._queryManagementService.commitEdit(ownerUri); + } + + public createRow(ownerUri: string): Promise { + return this._queryManagementService.createRow(ownerUri).then(result => { + return result; + }); + } + + public deleteRow(ownerUri: string, rowId: number): Promise { + return this._queryManagementService.deleteRow(ownerUri, rowId); + } + + public revertCell(ownerUri: string, rowId: number, columnId: number): Promise { + return this._queryManagementService.revertCell(ownerUri, rowId, columnId).then(result => { + return result; + }); + } + + public revertRow(ownerUri: string, rowId: number): Promise { + return this._queryManagementService.revertRow(ownerUri, rowId); + } + + public disposeEdit(ownerUri: string): Promise { + return this._queryManagementService.disposeEdit(ownerUri); + } + /** * Disposes the Query from the service client */ public async disposeQuery(): Promise { - await this.queryManagementService.disposeQuery(this.uri); + await this._queryManagementService.disposeQuery(this.uri); this.dispose(); } @@ -430,7 +561,7 @@ export default class QueryRunner extends Disposable { public getColumnHeaders(batchId: number, resultId: number, range: Slick.Range): string[] | undefined { let headers: string[] | undefined = undefined; - let batchSummary: BatchSummary = this._batchSets[batchId]; + let batchSummary: azdata.BatchSummary = this._batchSets[batchId]; if (batchSummary !== undefined) { let resultSetSummary = batchSummary.resultSetSummaries[resultId]; headers = resultSetSummary.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => { @@ -442,7 +573,7 @@ export default class QueryRunner extends Disposable { private sendBatchTimeMessage(batchId: number, executionTime: string): void { // get config copyRemoveNewLine option from vscode config - let showBatchTime = this.configurationService.getValue('sql.showBatchTime'); + let showBatchTime = this._configurationService.getValue('sql.showBatchTime'); if (showBatchTime) { let message: IQueryMessage = { batchId: batchId, @@ -465,7 +596,7 @@ export default class QueryRunner extends Disposable { } public notifyVisualizeRequested(batchId: number, resultSetId: number): void { - let result: ResultSetSummary = { + let result: azdata.ResultSetSummary = { batchId: batchId, id: resultSetId, columnInfo: this.batchSets[batchId].resultSetSummaries[resultSetId].columnInfo, @@ -489,7 +620,7 @@ export class QueryGridDataProvider implements IGridDataProvider { ) { } - getRowData(rowStart: number, numberOfRows: number): Promise { + getRowData(rowStart: number, numberOfRows: number): Promise { return this.queryRunner.getQueryRows(rowStart, numberOfRows, this.batchId, this.resultSetId); } @@ -548,6 +679,6 @@ export function shouldRemoveNewLines(configurationService: IConfigurationService return !!removeNewLines; } -function isRangeOrUndefined(input: string | IRange | undefined): input is IRange | undefined { - return Range.isIRange(input) || types.isUndefinedOrNull(input); +function isSelectionOrUndefined(input: string | azdata.ISelectionData | undefined): input is azdata.ISelectionData | undefined { + return types.isObject(input) || types.isUndefinedOrNull(input); } diff --git a/src/sql/workbench/services/query/test/common/queryRunner.test.ts b/src/sql/workbench/services/query/test/common/queryRunner.test.ts deleted file mode 100644 index ec4063623d..0000000000 --- a/src/sql/workbench/services/query/test/common/queryRunner.test.ts +++ /dev/null @@ -1,229 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as 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(arg: T, func: (arg: T) => void, event: Event): Promise { - const promise = Event.toPromise(event); - func(arg); - return promise; -} diff --git a/src/sql/workbench/services/query/test/common/testQueryManagementService.ts b/src/sql/workbench/services/query/test/common/testQueryManagementService.ts deleted file mode 100644 index bb11c35e03..0000000000 --- a/src/sql/workbench/services/query/test/common/testQueryManagementService.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { 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; - 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 { - return { messages: undefined }; - } - async runQuery(ownerUri: string, range: IRange, runOptions?: ExecutionPlanOptions): Promise { - return; - } - runQueryStatement(ownerUri: string, line: number, column: number): Promise { - throw new Error('Method not implemented.'); - } - runQueryString(ownerUri: string, queryString: string): Promise { - throw new Error('Method not implemented.'); - } - runQueryAndReturn(ownerUri: string, queryString: string): Promise { - throw new Error('Method not implemented.'); - } - parseSyntax(ownerUri: string, query: string): Promise { - throw new Error('Method not implemented.'); - } - getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise { - throw new Error('Method not implemented.'); - } - async disposeQuery(ownerUri: string): Promise { - return; - } - saveResults(requestParams: azdata.SaveResultsRequestParams): Promise { - throw new Error('Method not implemented.'); - } - setQueryExecutionOptions(uri: string, options: azdata.QueryExecutionOptions): Promise { - 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): 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 { - throw new Error('Method not implemented.'); - } - disposeEdit(ownerUri: string): Promise { - throw new Error('Method not implemented.'); - } - updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise { - throw new Error('Method not implemented.'); - } - commitEdit(ownerUri: string): Promise { - throw new Error('Method not implemented.'); - } - createRow(ownerUri: string): Promise { - throw new Error('Method not implemented.'); - } - deleteRow(ownerUri: string, rowId: number): Promise { - throw new Error('Method not implemented.'); - } - revertCell(ownerUri: string, rowId: number, columnId: number): Promise { - throw new Error('Method not implemented.'); - } - revertRow(ownerUri: string, rowId: number): Promise { - throw new Error('Method not implemented.'); - } - getEditRows(rowData: azdata.EditSubsetParams): Promise { - throw new Error('Method not implemented.'); - } - -} diff --git a/src/sql/workbench/services/query/test/common/testQueryModelService.ts b/src/sql/workbench/services/query/test/common/testQueryModelService.ts index 0480290887..1278bcb9bc 100644 --- a/src/sql/workbench/services/query/test/common/testQueryModelService.ts +++ b/src/sql/workbench/services/query/test/common/testQueryModelService.ts @@ -9,7 +9,6 @@ import * as azdata from 'azdata'; import { Event } from 'vs/base/common/event'; import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService'; import { DataService } from 'sql/workbench/services/query/common/dataService'; -import { IRange } from 'vs/editor/common/core/range'; export class TestQueryModelService implements IQueryModelService { _serviceBrand: any; @@ -26,10 +25,10 @@ export class TestQueryModelService implements IQueryModelService { getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise { throw new Error('Method not implemented.'); } - runQuery(uri: string, range: IRange, runOptions?: azdata.ExecutionPlanOptions): void { + runQuery(uri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): void { throw new Error('Method not implemented.'); } - runQueryStatement(uri: string, range: IRange): void { + runQueryStatement(uri: string, selection: azdata.ISelectionData): void { throw new Error('Method not implemented.'); } runQueryString(uri: string, selection: string) { diff --git a/src/sql/workbench/services/queryHistory/common/queryHistoryServiceImpl.ts b/src/sql/workbench/services/queryHistory/common/queryHistoryServiceImpl.ts index f4a13867ec..4c87555d6c 100644 --- a/src/sql/workbench/services/queryHistory/common/queryHistoryServiceImpl.ts +++ b/src/sql/workbench/services/queryHistory/common/queryHistoryServiceImpl.ts @@ -52,12 +52,12 @@ export class QueryHistoryService extends Disposable implements IQueryHistoryServ 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 // 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.range && e.queryInfo.range.length > 0 ? + const text: string = e.queryInfo.selection && e.queryInfo.selection.length > 0 ? _modelService.getModel(uri).getValueInRange(new Range( - e.queryInfo.range[0].startLineNumber, - e.queryInfo.range[0].startColumn, - e.queryInfo.range[0].endLineNumber, - e.queryInfo.range[0].endColumn)) : + e.queryInfo.selection[0].startLine + 1, + e.queryInfo.selection[0].startColumn + 1, + e.queryInfo.selection[0].endLine, + e.queryInfo.selection[0].endColumn + 1)) : // If no specific selection get the entire text _modelService.getModel(uri).getValue(); diff --git a/src/sql/workbench/test/workbenchTestServices.ts b/src/sql/workbench/test/workbenchTestServices.ts index 0d0424c8d1..da9fb4a69b 100644 --- a/src/sql/workbench/test/workbenchTestServices.ts +++ b/src/sql/workbench/test/workbenchTestServices.ts @@ -12,8 +12,6 @@ import { TestObjectExplorerService } from 'sql/workbench/services/objectExplorer import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; 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 { const instantiationService = vsworkbenchInstantiationService(); @@ -21,6 +19,5 @@ export function workbenchInstantiationService(): ITestInstantiationService { instantiationService.stub(IConnectionManagementService, new TestConnectionManagementService()); instantiationService.stub(IQueryModelService, new TestQueryModelService()); instantiationService.stub(IObjectExplorerService, new TestObjectExplorerService()); - instantiationService.stub(IQueryManagementService, new TestQueryManagementService()); return instantiationService; }
${escape(row[columnIndex].displayValue)}