Result Streaming (#3319)

* handle releasing data when the grid is unrendered

* update sqlops

* add complete to sqlops

* update protocol

* update protocol

* formatting

* update sqlops.d.ts

* stash

* better handling of results streaming

* formatting

* improvments to result streaming

* bump slickgrid and address a performance bottleneck

* remove unnecessary code

* formatting

* update locks

* optimize large values in the grid

* formatting

* formatting

* update yarn

* bump packages

* yarn

* bump

* yarn lock

* locking

* yarn

* fix duplicate result sets

* fix event stream versions

* yarn
This commit is contained in:
Anthony Dresser
2018-11-29 15:16:53 -08:00
committed by GitHub
parent a04a9eb5ad
commit afb1ebebd5
22 changed files with 419 additions and 191 deletions

View File

@@ -9,36 +9,27 @@ export interface IObservableCollection<T> {
getLength(): number;
at(index: number): T;
getRange(start: number, end: number): T[];
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void;
setCollectionChangedCallback(callback: (startIndex: number, count: number) => void): void;
setLength(number): void;
dispose(): void;
}
export interface IGridDataRow {
row?: number;
values: any[];
}
export enum CollectionChange {
ItemsReplaced
}
class LoadCancellationToken {
isCancelled: boolean;
}
class DataWindow<TData> {
private _data: TData[];
class DataWindow<T> {
private _data: T[];
private _length: number = 0;
private _offsetFromDataSource: number = -1;
private lastLoadCancellationToken: LoadCancellationToken;
constructor(
private loadFunction: (offset: number, count: number) => Thenable<TData[]>,
private placeholderItemGenerator: (index: number) => TData,
private loadFunction: (offset: number, count: number) => Thenable<T[]>,
private placeholderItemGenerator: (index: number) => T,
private loadCompleteCallback: (start: number, end: number) => void
) {
}
) { }
dispose() {
this._data = undefined;
@@ -50,26 +41,26 @@ class DataWindow<TData> {
}
}
getStartIndex(): number {
public getStartIndex(): number {
return this._offsetFromDataSource;
}
getEndIndex(): number {
public getEndIndex(): number {
return this._offsetFromDataSource + this._length;
}
contains(dataSourceIndex: number): boolean {
public contains(dataSourceIndex: number): boolean {
return dataSourceIndex >= this.getStartIndex() && dataSourceIndex < this.getEndIndex();
}
getItem(index: number): TData {
public getItem(index: number): T {
if (!this._data) {
return this.placeholderItemGenerator(index);
}
return this._data[index - this._offsetFromDataSource];
}
positionWindow(offset: number, length: number): void {
public positionWindow(offset: number, length: number): void {
this._offsetFromDataSource = offset;
this._length = length;
this._data = undefined;
@@ -92,34 +83,28 @@ class DataWindow<TData> {
}
}
export class VirtualizedCollection<TData> implements IObservableCollection<TData> {
export class VirtualizedCollection<T extends Slick.SlickData> implements IObservableCollection<T> {
private _bufferWindowBefore: DataWindow<T>;
private _window: DataWindow<T>;
private _bufferWindowAfter: DataWindow<T>;
private _length: number;
private _windowSize: number;
private _bufferWindowBefore: DataWindow<TData>;
private _window: DataWindow<TData>;
private _bufferWindowAfter: DataWindow<TData>;
private collectionChangedCallback: (change: CollectionChange, startIndex: number, count: number) => void;
private collectionChangedCallback: (startIndex: number, count: number) => void;
constructor(
windowSize: number,
length: number,
loadFn: (offset: number, count: number) => Thenable<TData[]>,
private _placeHolderGenerator: (index: number) => TData
private readonly windowSize: number,
private placeHolderGenerator: (index: number) => T,
private length: number,
loadFn: (offset: number, count: number) => Thenable<T[]>
) {
this._windowSize = windowSize;
this._length = length;
let loadCompleteCallback = (start: number, end: number) => {
if (this.collectionChangedCallback) {
this.collectionChangedCallback(CollectionChange.ItemsReplaced, start, end - start);
this.collectionChangedCallback(start, end - start);
}
};
this._bufferWindowBefore = new DataWindow(loadFn, _placeHolderGenerator, loadCompleteCallback);
this._window = new DataWindow(loadFn, _placeHolderGenerator, loadCompleteCallback);
this._bufferWindowAfter = new DataWindow(loadFn, _placeHolderGenerator, loadCompleteCallback);
this._bufferWindowBefore = new DataWindow(loadFn, placeHolderGenerator, loadCompleteCallback);
this._window = new DataWindow(loadFn, placeHolderGenerator, loadCompleteCallback);
this._bufferWindowAfter = new DataWindow(loadFn, placeHolderGenerator, loadCompleteCallback);
}
dispose() {
@@ -128,19 +113,23 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
this._window.dispose();
}
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void {
public setCollectionChangedCallback(callback: (startIndex: number, count: number) => void): void {
this.collectionChangedCallback = callback;
}
getLength(): number {
return this._length;
public getLength(): number {
return this.length;
}
at(index: number): TData {
setLength(number: any): void {
this.length = number;
}
public at(index: number): T {
return this.getRange(index, index + 1)[0];
}
getRange(start: number, end: number): TData[] {
public getRange(start: number, end: number): T[] {
// current data may contain placeholders
let currentData = this.getRangeFromCurrent(start, end);
@@ -155,7 +144,7 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
this._bufferWindowAfter = this._window;
this._window = this._bufferWindowBefore;
this._bufferWindowBefore = windowToRecycle;
let newWindowOffset = Math.max(0, this._window.getStartIndex() - this._windowSize);
let newWindowOffset = Math.max(0, this._window.getStartIndex() - this.windowSize);
this._bufferWindowBefore.positionWindow(newWindowOffset, this._window.getStartIndex() - newWindowOffset);
} else if (start >= this._bufferWindowAfter.getStartIndex()) {
@@ -164,8 +153,8 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
this._bufferWindowBefore = this._window;
this._window = this._bufferWindowAfter;
this._bufferWindowAfter = windowToRecycle;
let newWindowOffset = Math.min(this._window.getStartIndex() + this._windowSize, this._length);
let newWindowLength = Math.min(this._length - newWindowOffset, this._windowSize);
let newWindowOffset = Math.min(this._window.getStartIndex() + this.windowSize, this.length);
let newWindowLength = Math.min(this.length - newWindowOffset, this.windowSize);
this._bufferWindowAfter.positionWindow(newWindowOffset, newWindowLength);
}
@@ -173,7 +162,7 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
return currentData;
}
private getRangeFromCurrent(start: number, end: number): TData[] {
private getRangeFromCurrent(start: number, end: number): T[] {
let currentData = [];
for (let i = 0; i < end - start; i++) {
currentData.push(this.getDataFromCurrent(start + i));
@@ -182,7 +171,7 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
return currentData;
}
private getDataFromCurrent(index: number): TData {
private getDataFromCurrent(index: number): T {
if (this._bufferWindowBefore.contains(index)) {
return this._bufferWindowBefore.getItem(index);
} else if (this._bufferWindowAfter.contains(index)) {
@@ -191,39 +180,47 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
return this._window.getItem(index);
}
return this._placeHolderGenerator(index);
return this.placeHolderGenerator(index);
}
private resetWindowsAroundIndex(index: number): void {
let bufferWindowBeforeStart = Math.max(0, index - this._windowSize * 1.5);
let bufferWindowBeforeEnd = Math.max(0, index - this._windowSize / 2);
let bufferWindowBeforeStart = Math.max(0, index - this.windowSize * 1.5);
let bufferWindowBeforeEnd = Math.max(0, index - this.windowSize / 2);
this._bufferWindowBefore.positionWindow(bufferWindowBeforeStart, bufferWindowBeforeEnd - bufferWindowBeforeStart);
let mainWindowStart = bufferWindowBeforeEnd;
let mainWindowEnd = Math.min(mainWindowStart + this._windowSize, this._length);
let mainWindowEnd = Math.min(mainWindowStart + this.windowSize, this.length);
this._window.positionWindow(mainWindowStart, mainWindowEnd - mainWindowStart);
let bufferWindowAfterStart = mainWindowEnd;
let bufferWindowAfterEnd = Math.min(bufferWindowAfterStart + this._windowSize, this._length);
let bufferWindowAfterEnd = Math.min(bufferWindowAfterStart + this.windowSize, this.length);
this._bufferWindowAfter.positionWindow(bufferWindowAfterStart, bufferWindowAfterEnd - bufferWindowAfterStart);
}
}
export class AsyncDataProvider<TData extends IGridDataRow> implements IDisposableDataProvider<TData> {
export class AsyncDataProvider<T extends Slick.SlickData> implements IDisposableDataProvider<T> {
constructor(private dataRows: IObservableCollection<TData>) { }
constructor(public dataRows: IObservableCollection<T>) { }
public getLength(): number {
return this.dataRows ? this.dataRows.getLength() : 0;
return this.dataRows.getLength();
}
public getItem(index: number): TData {
return !this.dataRows ? undefined : this.dataRows.at(index);
public getItem(index: number): T {
return this.dataRows.at(index);
}
public getRange(start: number, end: number): TData[] {
return !this.dataRows ? undefined : this.dataRows.getRange(start, end);
public getRange(start: number, end: number): T[] {
return this.dataRows.getRange(start, end);
}
public set length(length: number) {
this.dataRows.setLength(length);
}
public get length(): number {
return this.dataRows.getLength();
}
dispose() {

View File

@@ -5,8 +5,6 @@
'use strict';
import { range } from 'vs/base/common/arrays';
export interface IRowNumberColumnOptions {
numberOfRows: number;
cssClass?: string;
@@ -17,7 +15,7 @@ const sizePerDigit = 15;
export class RowNumberColumn<T> implements Slick.Plugin<T> {
private handler = new Slick.EventHandler();
private grid: Slick.Grid<T>;
private currentColumnWidth: number;
constructor(private options: IRowNumberColumnOptions) {
}
@@ -52,19 +50,25 @@ export class RowNumberColumn<T> implements Slick.Plugin<T> {
}
}
public updateRowCount(rowNum: number) {
this.options.numberOfRows = rowNum;
let columnWidth = Math.max(this.options.numberOfRows.toString().length * sizePerDigit, 22);
if (columnWidth !== this.currentColumnWidth) {
this.grid.setColumnWidths([this.getColumnDefinition()]);
}
}
public getColumnDefinition(): Slick.Column<T> {
// that smallest we can make it is 22 due to padding and margins in the cells
let columnWidth = Math.max(this.options.numberOfRows.toString().length * sizePerDigit, 22);
this.currentColumnWidth = Math.max(this.options.numberOfRows.toString().length * sizePerDigit, 22);
return {
id: 'rowNumber',
name: '',
field: 'rowNumber',
width: columnWidth,
minWidth: columnWidth,
maxWidth: columnWidth,
width: this.currentColumnWidth,
resizable: false,
cssClass: this.options.cssClass,
focusable: false,
focusable: true,
selectable: false,
formatter: (r, c, v, cd, dc) => this.formatter(r, c, v, cd, dc)
};

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { $ } from 'vs/base/browser/dom';
import { escape } from 'sql/base/common/strings';
export class DBCellValue {
@@ -41,20 +40,24 @@ export function hyperLinkFormatter(row: number, cell: any, value: any, columnDef
*/
export function textFormatter(row: number, cell: any, value: any, columnDef: any, dataContext: any): string {
let cellClasses = 'grid-cell-value-container';
let valueToDisplay: string = '';
let valueToDisplay = '';
let titleValue = '';
if (DBCellValue.isDBCellValue(value)) {
valueToDisplay = 'NULL';
if (!value.isNull) {
valueToDisplay = escape(value.displayValue.replace(/(\r\n|\n|\r)/g, ' '));
valueToDisplay = value.displayValue.replace(/(\r\n|\n|\r)/g, ' ');
valueToDisplay = escape(valueToDisplay.length > 250 ? valueToDisplay.slice(0, 250) + '...' : valueToDisplay);
titleValue = value.displayValue;
} else {
cellClasses += ' missing-value';
}
} else if (typeof value === 'string') {
valueToDisplay = escape(value);
valueToDisplay = escape(value.length > 250 ? value.slice(0, 250) + '...' : value);
titleValue = value;
}
return `<span title="${valueToDisplay}" class="${cellClasses}">${valueToDisplay}</span>`;
return `<span title="${titleValue}" class="${cellClasses}">${valueToDisplay}</span>`;
}
/** The following code is a rewrite over the both formatter function using dom builder

View File

@@ -29,7 +29,7 @@ export interface IQueryManagementService {
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>;
parseSyntax(ownerUri:string, query: string): Thenable<sqlops.SyntaxParseResult>;
parseSyntax(ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult>;
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>;
disposeQuery(ownerUri: string): Thenable<void>;
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
@@ -38,7 +38,8 @@ export interface IQueryManagementService {
onQueryComplete(result: sqlops.QueryExecuteCompleteNotificationResult): void;
onBatchStart(batchInfo: sqlops.QueryExecuteBatchNotificationParams): void;
onBatchComplete(batchInfo: sqlops.QueryExecuteBatchNotificationParams): void;
onResultSetComplete(resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void;
onResultSetAvailable(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void;
onResultSetUpdated(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void;
onMessage(message: sqlops.QueryExecuteMessageParams): void;
// Edit Data Callbacks
@@ -65,7 +66,7 @@ export interface IQueryRequestHandler {
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>;
parseSyntax(ownerUri:string, query: string): Thenable<sqlops.SyntaxParseResult>;
parseSyntax(ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult>;
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>;
disposeQuery(ownerUri: string): Thenable<void>;
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
@@ -244,9 +245,15 @@ export class QueryManagementService implements IQueryManagementService {
});
}
public onResultSetComplete(resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void {
public onResultSetAvailable(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
runner.handleResultSetComplete(resultSetInfo);
runner.handleResultSetAvailable(resultSetInfo);
});
}
public onResultSetUpdated(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
runner.handleResultSetUpdated(resultSetInfo);
});
}

View File

@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as pretty from 'pretty-data';
import { attachTableStyler } from 'sql/common/theme/styler';
import QueryRunner from 'sql/parts/query/execution/queryRunner';
import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView';
@@ -12,7 +14,7 @@ import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scr
import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction, ShowQueryPlanAction } from 'sql/parts/query/editor/actions';
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction } from 'sql/parts/query/editor/actions';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
import { escape } from 'sql/base/common/strings';
@@ -20,9 +22,9 @@ import { hyperLinkFormatter, textFormatter } from 'sql/parts/grid/services/share
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { ITableStyles, ITableMouseEvent } from 'sql/base/browser/ui/table/interfaces';
import { warn } from 'sql/base/common/log';
import * as sqlops from 'sqlops';
import * as pretty from 'pretty-data';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -38,7 +40,7 @@ import { $ } from 'vs/base/browser/builder';
import { generateUuid } from 'vs/base/common/uuid';
import { TPromise } from 'vs/base/common/winjs.base';
import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { Dimension, getContentWidth, isInDOM } from 'vs/base/browser/dom';
import { isInDOM } from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -175,7 +177,8 @@ export class GridPanel extends ViewletPanel {
this.reset();
this.queryRunnerDisposables = [];
this.runner = runner;
this.queryRunnerDisposables.push(this.runner.onResultSet(e => this.onResultSet(e)));
this.queryRunnerDisposables.push(this.runner.onResultSet(this.onResultSet, this));
this.queryRunnerDisposables.push(this.runner.onResultSetUpdate(this.updateResultSet, this));
this.queryRunnerDisposables.push(this.runner.onQueryStart(() => {
if (this.state) {
this.state.tableStates = [];
@@ -200,6 +203,33 @@ export class GridPanel extends ViewletPanel {
}
}
private updateResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
let resultsToUpdate: sqlops.ResultSetSummary[];
if (!Array.isArray(resultSet)) {
resultsToUpdate = [resultSet];
} else {
resultsToUpdate = resultSet;
}
for (let set of resultsToUpdate) {
let table = this.tables.find(t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id);
if (table) {
table.updateResult(set);
} else {
warn('Got result set update request for non-existant table');
}
}
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
if (this.state && this.state.scrollPosition) {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
}
private addResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
let resultsToAdd: sqlops.ResultSetSummary[];
if (!Array.isArray(resultSet)) {
@@ -323,9 +353,12 @@ class GridTable<T> extends Disposable implements IView {
private selectionModel = new CellSelectionModel();
private styles: ITableStyles;
private currentHeight: number;
private dataProvider: AsyncDataProvider<T>;
private columns: Slick.Column<T>[];
private rowNumberColumn: RowNumberColumn<T>;
private _onDidChange = new Emitter<number>();
public readonly onDidChange: Event<number> = this._onDidChange.event;
@@ -335,13 +368,20 @@ class GridTable<T> extends Disposable implements IView {
private _state: GridTableState;
private scrolled = false;
private visible = false;
public get resultSet(): sqlops.ResultSetSummary {
return this._resultSet;
}
// this handles if the row count is small, like 4-5 rows
private readonly maxSize = ((this.resultSet.rowCount) * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
private get maxSize(): number {
return ((this.resultSet.rowCount) * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
}
constructor(
private runner: QueryRunner,
public readonly resultSet: sqlops.ResultSetSummary,
private _resultSet: sqlops.ResultSetSummary,
@IContextMenuService private contextMenuService: IContextMenuService,
@IInstantiationService private instantiationService: IInstantiationService,
@IEditorService private editorService: IEditorService,
@@ -367,7 +407,31 @@ class GridTable<T> extends Disposable implements IView {
});
}
public onAdd() {
this.visible = true;
let collection = new VirtualizedCollection(
50,
index => this.placeholdGenerator(index),
this.resultSet.rowCount,
(offset, count) => this.loadData(offset, count)
);
collection.setCollectionChangedCallback((startIndex, count) => {
this.renderGridDataRowsRange(startIndex, count);
});
this.dataProvider.dataRows = collection;
this.table.updateRowCount();
}
public onRemove() {
this.visible = false;
let collection = new VirtualizedCollection(
50,
index => this.placeholdGenerator(index),
0,
() => TPromise.as([])
);
this.dataProvider.dataRows = collection;
this.table.updateRowCount();
// when we are removed slickgrid acts badly so we need to account for that
this.scrolled = false;
}
@@ -379,34 +443,38 @@ class GridTable<T> extends Disposable implements IView {
private build(): void {
let tableContainer = document.createElement('div');
tableContainer.style.display = 'inline-block';
tableContainer.style.width = `calc(100% - ${ACTIONBAR_WIDTH}px)`;
this.container.appendChild(tableContainer);
let collection = new VirtualizedCollection(50, this.resultSet.rowCount,
(offset, count) => this.loadData(offset, count),
index => this.placeholdGenerator(index)
let collection = new VirtualizedCollection(
50,
index => this.placeholdGenerator(index),
0,
() => TPromise.as([])
);
collection.setCollectionChangedCallback((change, startIndex, count) => {
collection.setCollectionChangedCallback((startIndex, count) => {
this.renderGridDataRowsRange(startIndex, count);
});
let numberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount });
this.rowNumberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount });
let copyHandler = new CopyKeybind();
copyHandler.onCopy(e => {
new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false).run(this.generateContext());
});
this.columns.unshift(numberColumn.getColumnDefinition());
this.columns.unshift(this.rowNumberColumn.getColumnDefinition());
let tableOptions: Slick.GridOptions<T> = {
rowHeight: ROW_HEIGHT,
showRowNumber: true,
forceFitColumns: false,
defaultColumnWidth: 120
};
this.table = this._register(new Table(tableContainer, { dataProvider: new AsyncDataProvider(collection), columns: this.columns }, tableOptions));
this.dataProvider = new AsyncDataProvider(collection);
this.table = this._register(new Table(tableContainer, { dataProvider: this.dataProvider, columns: this.columns }, tableOptions));
this.table.setSelectionModel(this.selectionModel);
this.table.registerPlugin(new MouseWheelSupport());
this.table.registerPlugin(new AutoColumnSize());
this.table.registerPlugin(copyHandler);
this.table.registerPlugin(numberColumn);
this.table.registerPlugin(this.rowNumberColumn);
this.table.registerPlugin(new AdditionalKeyBindings());
this._register(this.table.onContextMenu(this.contextMenu, this));
this._register(this.table.onClick(this.onTableClick, this));
@@ -507,7 +575,7 @@ class GridTable<T> extends Disposable implements IView {
} catch (e) {
// If Xml fails to parse, fall back on original Xml content
}
} else {
} else {
let jsonContent: string = undefined;
try {
jsonContent = JSON.parse(content);
@@ -526,6 +594,15 @@ class GridTable<T> extends Disposable implements IView {
}
}
public updateResult(resultSet: sqlops.ResultSetSummary) {
this._resultSet = resultSet;
if (this.table && this.visible) {
this.dataProvider.length = resultSet.rowCount;
this.table.updateRowCount();
}
this.rowNumberColumn.updateRowCount(resultSet.rowCount);
}
private generateContext(cell?: Slick.Cell): IGridActionContext {
const selection = this.selectionModel.getSelectedRanges();
return <IGridActionContext>{
@@ -577,12 +654,7 @@ class GridTable<T> extends Disposable implements IView {
} else {
this.currentHeight = size;
}
this.table.layout(
new Dimension(
getContentWidth(this.container) - ACTIONBAR_WIDTH,
size
)
);
this.table.layout(size, Orientation.VERTICAL);
}
public get minimumSize(): number {
@@ -596,6 +668,9 @@ class GridTable<T> extends Disposable implements IView {
private loadData(offset: number, count: number): Thenable<T[]> {
return this.runner.getQueryRows(offset, count, this.resultSet.batchId, this.resultSet.id).then(response => {
if (!response.resultSubset) {
return [];
}
return response.resultSubset.rows.map(r => {
let dataWithSchema = {};
// skip the first column since its a number column

View File

@@ -100,6 +100,18 @@ export default class QueryRunner extends Disposable {
private _echoedResultSet = echo(this._debouncedResultSet.event);
public readonly onResultSet = this._echoedResultSet.event;
private _onResultSetUpdate = this._register(new Emitter<sqlops.ResultSetSummary>());
private _debouncedResultSetUpdate = debounceEvent<sqlops.ResultSetSummary, sqlops.ResultSetSummary[]>(this._onResultSetUpdate.event, (l, e) => {
// on first run
if (types.isUndefinedOrNull(l)) {
return [e];
} else {
return l.concat(e);
}
});
private _echoedResultSetUpdate = echo(this._debouncedResultSetUpdate.event);
public readonly onResultSetUpdate = this._echoedResultSetUpdate.event;
private _onQueryStart = this._register(new Emitter<void>());
public readonly onQueryStart: Event<void> = this._onQueryStart.event;
@@ -336,7 +348,7 @@ export default class QueryRunner extends Disposable {
/**
* Handle a ResultSetComplete from the service layer
*/
public handleResultSetComplete(result: sqlops.QueryExecuteResultSetCompleteNotificationParams): void {
public handleResultSetAvailable(result: sqlops.QueryExecuteResultSetNotificationParams): void {
if (result && result.resultSetSummary) {
let resultSet = result.resultSetSummary;
let batchSet: sqlops.BatchSummary;
@@ -365,7 +377,9 @@ export default class QueryRunner extends Disposable {
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue));
}
}
if (batchSet) {
// we will just ignore the set if we already have it
// ideally this should never happen
if (batchSet && !batchSet.resultSetSummaries[resultSet.id]) {
// Store the result set in the batch and emit that a result set has completed
batchSet.resultSetSummaries[resultSet.id] = resultSet;
this._eventEmitter.emit(EventType.RESULT_SET, resultSet);
@@ -374,6 +388,27 @@ export default class QueryRunner extends Disposable {
}
}
public handleResultSetUpdated(result: sqlops.QueryExecuteResultSetNotificationParams): void {
if (result && result.resultSetSummary) {
let resultSet = result.resultSetSummary;
let batchSet: sqlops.BatchSummary;
batchSet = this.batchSets[resultSet.batchId];
// handle getting queryPlanxml if we need too
if (this.isQueryPlan) {
// check if this result has show plan, this needs work, it won't work for any other provider
let hasShowPlan = !!result.resultSetSummary.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
if (hasShowPlan) {
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue));
}
}
if (batchSet) {
// Store the result set in the batch and emit that a result set has completed
batchSet.resultSetSummaries[resultSet.id] = resultSet;
this._onResultSetUpdate.fire(resultSet);
}
}
}
/**
* Handle a Mssage from the service layer
*/

View File

@@ -249,9 +249,9 @@ export class QueryEditorService implements IQueryEditorService {
QueryEditorService.editorService.openEditor(newEditorInput, options, group).then((editor) => {
resolve(QueryEditorService._onEditorOpened(editor, uri.toString(), undefined, options.pinned));
},
(error) => {
reject(error);
});
(error) => {
reject(error);
});
});
});
}

6
src/sql/sqlops.d.ts vendored
View File

@@ -694,7 +694,8 @@ declare module 'sqlops' {
registerOnQueryComplete(handler: (result: QueryExecuteCompleteNotificationResult) => any): void;
registerOnBatchStart(handler: (batchInfo: QueryExecuteBatchNotificationParams) => any): void;
registerOnBatchComplete(handler: (batchInfo: QueryExecuteBatchNotificationParams) => any): void;
registerOnResultSetComplete(handler: (resultSetInfo: QueryExecuteResultSetCompleteNotificationParams) => any): void;
registerOnResultSetAvailable(handler: (resultSetInfo: QueryExecuteResultSetNotificationParams) => any): void;
registerOnResultSetUpdated(handler: (resultSetInfo: QueryExecuteResultSetNotificationParams) => any): void;
registerOnMessage(handler: (message: QueryExecuteMessageParams) => any): void;
// Edit Data Requests
@@ -769,6 +770,7 @@ declare module 'sqlops' {
batchId: number;
rowCount: number;
columnInfo: IDbColumn[];
complete: boolean;
}
export interface BatchSummary {
@@ -837,7 +839,7 @@ declare module 'sqlops' {
}
export interface QueryExecuteResultSetCompleteNotificationParams {
export interface QueryExecuteResultSetNotificationParams {
resultSetSummary: ResultSetSummary;
ownerUri: string;
}

View File

@@ -265,8 +265,11 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
$onBatchComplete(handle: number, batchInfo: sqlops.QueryExecuteBatchNotificationParams): void {
this._proxy.$onBatchComplete(handle, batchInfo);
}
$onResultSetComplete(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void {
this._proxy.$onResultSetComplete(handle, resultSetInfo);
$onResultSetAvailable(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
this._proxy.$onResultSetAvailable(handle, resultSetInfo);
}
$onResultSetUpdated(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
this._proxy.$onResultSetUpdated(handle, resultSetInfo);
}
$onQueryMessage(handle: number, message: sqlops.QueryExecuteMessageParams): void {
this._proxy.$onQueryMessage(handle, message);

View File

@@ -444,8 +444,11 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
public $onBatchComplete(handle: number, batchInfo: sqlops.QueryExecuteBatchNotificationParams): void {
this._queryManagementService.onBatchComplete(batchInfo);
}
public $onResultSetComplete(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void {
this._queryManagementService.onResultSetComplete(resultSetInfo);
public $onResultSetAvailable(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
this._queryManagementService.onResultSetAvailable(resultSetInfo);
}
public $onResultSetUpdated(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
this._queryManagementService.onResultSetUpdated(resultSetInfo);
}
public $onQueryMessage(handle: number, message: sqlops.QueryExecuteMessageParams): void {
this._queryManagementService.onMessage(message);

View File

@@ -197,8 +197,12 @@ export function createApiFactory(
extHostDataProvider.$onBatchComplete(provider.handle, batchInfo);
});
provider.registerOnResultSetComplete((resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams) => {
extHostDataProvider.$onResultSetComplete(provider.handle, resultSetInfo);
provider.registerOnResultSetAvailable((resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams) => {
extHostDataProvider.$onResultSetAvailable(provider.handle, resultSetInfo);
});
provider.registerOnResultSetUpdated((resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams) => {
extHostDataProvider.$onResultSetUpdated(provider.handle, resultSetInfo);
});
provider.registerOnMessage((message: sqlops.QueryExecuteMessageParams) => {

View File

@@ -193,7 +193,11 @@ export abstract class ExtHostDataProtocolShape {
/**
* Callback when a result set has been returned from query execution and can be displayed
*/
$onResultSetComplete(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void { throw ni(); }
$onResultSetAvailable(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void { throw ni(); }
/**
* Callback when a result set has been returned from query execution and can be displayed
*/
$onResultSetUpdate(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void { throw ni(); }
/**
* Callback when a message generated during query execution is issued
*/
@@ -504,7 +508,8 @@ export interface MainThreadDataProtocolShape extends IDisposable {
$onQueryComplete(handle: number, result: sqlops.QueryExecuteCompleteNotificationResult): void;
$onBatchStart(handle: number, batchInfo: sqlops.QueryExecuteBatchNotificationParams): void;
$onBatchComplete(handle: number, batchInfo: sqlops.QueryExecuteBatchNotificationParams): void;
$onResultSetComplete(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void;
$onResultSetAvailable(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void;
$onResultSetUpdated(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void;
$onQueryMessage(handle: number, message: sqlops.QueryExecuteMessageParams): void;
$onObjectExplorerSessionCreated(handle: number, message: sqlops.ObjectExplorerSession): void;
$onObjectExplorerSessionDisconnected(handle: number, message: sqlops.ObjectExplorerSession): void;