Use on-demand loading for results in the query tool. Fixes #2137

With a 27420 row query, pgAdmin III runs the query in 5.873s on my laptop. pgAdmin 4 now takes ~1s.
This commit is contained in:
Harshal Dhumal
2017-06-27 09:03:04 -04:00
committed by Dave Page
parent 15cb9fc35b
commit c65158312d
28 changed files with 1953 additions and 887 deletions

View File

@@ -16,16 +16,16 @@ import clipboard from '../../../pgadmin/static/js/selection/clipboard';
import copyData from '../../../pgadmin/static/js/selection/copy_data';
import RangeSelectionHelper from 'sources/selection/range_selection_helper';
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
describe('copyData', function () {
var grid, sqlEditor, gridContainer, buttonPasteRow;
var SlickGrid;
beforeEach(function () {
SlickGrid = Slick.Grid;
var data = [[1, 'leopord', '12'],
[2, 'lion', '13'],
[3, 'puma', '9']];
var data = [{'id': 1, 'brand':'leopord', 'size':'12', '__temp_PK': '123'},
{'id': 2, 'brand':'lion', 'size':'13', '__temp_PK': '456'},
{'id': 3, 'brand':'puma', 'size':'9', '__temp_PK': '789'}],
dataView = new Slick.Data.DataView();
var columns = [
{
@@ -37,6 +37,7 @@ describe('copyData', function () {
},
{
name: 'id',
field: 'id',
pos: 0,
label: 'id<br> numeric',
cell: 'number',
@@ -44,6 +45,7 @@ describe('copyData', function () {
type: 'numeric',
}, {
name: 'brand',
field: 'brand',
pos: 1,
label: 'flavor<br> character varying',
cell: 'string',
@@ -51,24 +53,26 @@ describe('copyData', function () {
type: 'character varying',
}, {
name: 'size',
field: 'size',
pos: 2,
label: 'size<br> numeric',
cell: 'number',
can_edit: false,
type: 'numeric',
},
]
;
gridContainer = $('<div id=\'grid\'></div>');
];
gridContainer = $('<div id="grid"></div>');
$('body').append(gridContainer);
buttonPasteRow = $('<button id=\'btn-paste-row\' disabled></button>');
buttonPasteRow = $('<button id="btn-paste-row" disabled></button>');
$('body').append(buttonPasteRow);
grid = new SlickGrid('#grid', data, columns, {});
grid = new SlickGrid('#grid', dataView, columns, {});
dataView.setItems(data, '__temp_PK');
grid.setSelectionModel(new XCellSelectionModel());
sqlEditor = {slickgrid: grid};
});
afterEach(function () {
afterEach(function() {
grid.destroy();
gridContainer.remove();
buttonPasteRow.remove();
});

View File

@@ -134,17 +134,19 @@ describe('RangeBoundaryNavigator', function () {
describe('#rangesToCsv', function () {
var data, columnDefinitions, ranges;
beforeEach(function () {
data = [[1, 'leopard', '12'],
[2, 'lion', '13'],
[3, 'cougar', '9'],
[4, 'tiger', '10']];
columnDefinitions = [{name: 'id', pos: 0}, {name: 'animal', pos: 1}, {name: 'size', pos: 2}];
data = [{'id':1, 'animal':'leopard', 'size':'12'},
{'id':2, 'animal':'lion', 'size':'13'},
{'id':3, 'animal':'cougar', 'size':'9'},
{'id':4, 'animal':'tiger', 'size':'10'}];
columnDefinitions = [{name: 'id', field: 'id', pos: 0},
{name: 'animal', field: 'animal', pos: 1},
{name: 'size', field: 'size', pos: 2}];
ranges = [new Slick.Range(0, 0, 0, 2), new Slick.Range(3, 0, 3, 2)];
});
it('returns csv for the provided ranges', function () {
var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges);
expect(csvResult).toEqual('1,\'leopard\',\'12\'\n4,\'tiger\',\'10\'');
});
@@ -158,10 +160,10 @@ describe('RangeBoundaryNavigator', function () {
describe('when there is an extra column with checkboxes', function () {
beforeEach(function () {
columnDefinitions = [{name: 'not-a-data-column'}, {name: 'id', pos: 0}, {name: 'animal', pos: 1}, {
name: 'size',
pos: 2,
}];
columnDefinitions = [{name: 'not-a-data-column'},
{name: 'id', field: 'id', pos: 0},
{name: 'animal', field: 'animal', pos: 1},
{name: 'size', field: 'size',pos: 2}];
ranges = [new Slick.Range(0, 0, 0, 3), new Slick.Range(3, 0, 3, 3)];
});

View File

@@ -6,7 +6,6 @@
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import $ from 'jquery';
import Slick from 'slickgrid';
@@ -23,7 +22,7 @@ describe('RowSelector', function () {
UP: 38,
DOWN: 40,
};
var container, data, columnDefinitions, grid, cellSelectionModel;
var container, dataView, columnDefinitions, grid, cellSelectionModel;
var SlickGrid = Slick.Grid;
beforeEach(function () {
@@ -43,14 +42,15 @@ describe('RowSelector', function () {
pos: 1,
}];
dataView = new Slick.Data.DataView();
var rowSelector = new RowSelector();
data = [];
var data = [];
for (var i = 0; i < 10; i++) {
data.push(['some-value-' + i, 'second value ' + i]);
data.push({'some-column-name':'some-value-' + i, 'second column':'second value ' + i});
}
columnDefinitions = rowSelector.getColumnDefinitions(columnDefinitions);
grid = new SlickGrid(container, data, columnDefinitions);
dataView.setItems(data, 'some-column-name');
grid = new SlickGrid(container, dataView, columnDefinitions);
grid.registerPlugin(new ActiveCellCapture());
cellSelectionModel = new XCellSelectionModel();
grid.setSelectionModel(cellSelectionModel);

View File

@@ -7,236 +7,247 @@
//
//////////////////////////////////////////////////////////////
define([
'jquery',
'underscore',
'sources/selection/set_staged_rows',
], function ($, _, SetStagedRows) {
describe('set_staged_rows', function () {
var sqlEditorObj, gridSpy, deleteButton, copyButton, selectionSpy;
beforeEach(function () {
gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode', 'getColumns']);
gridSpy.getData.and.returnValue([
{0: 'one', 1: 'two', __temp_PK: '123'},
{0: 'three', 1: 'four', __temp_PK: '456'},
{0: 'five', 1: 'six', __temp_PK: '789'},
{0: 'seven', 1: 'eight', __temp_PK: '432'},
]);
gridSpy.getColumns.and.returnValue([
import $ from 'jquery';
import 'slickgrid.grid';
import Slick from 'slickgrid';
import SetStagedRows from 'sources/selection/set_staged_rows';
describe('set_staged_rows', function () {
var sqlEditorObj, gridSpy, deleteButton, copyButton, selectionSpy;
beforeEach(function () {
var data = [{'a pk column': 'one', 'some column': 'two', '__temp_PK': '123'},
{'a pk column': 'three', 'some column': 'four', '__temp_PK': '456'},
{'a pk column': 'five', 'some column': 'six', '__temp_PK': '789'},
{'a pk column': 'seven', 'some column': 'eight', '__temp_PK': '432'}],
dataView = new Slick.Data.DataView();
dataView.setItems(data, '__temp_PK');
gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode', 'getColumns']);
gridSpy.getData.and.returnValue(dataView);
gridSpy.getColumns.and.returnValue([
{
name: 'a pk column',
field: 'a pk column',
pos: 0,
selectable: true,
}, {
name: 'some column',
field: 'some column',
pos: 1,
selectable: true,
},
]);
selectionSpy = jasmine.createSpyObj('selectionSpy', ['setSelectedRows', 'getSelectedRanges']);
deleteButton = $('<button id="btn-delete-row"></button>');
copyButton = $('<button id="btn-copy-row"></button>');
sqlEditorObj = {
grid: gridSpy,
editor: {
handler: {
data_store: {
staged_rows: {'456': {}},
},
can_edit: false,
},
},
keys: null,
selection: selectionSpy,
columns: [
{
name: 'a pk column',
field: 'a pk column',
pos: 0,
selectable: true,
}, {
},
{
name: 'some column',
field: 'some column',
pos: 1,
selectable: true,
},
]);
],
client_primary_key: '__temp_PK',
};
selectionSpy = jasmine.createSpyObj('selectionSpy', ['setSelectedRows', 'getSelectedRanges']);
$('body').append(deleteButton);
$('body').append(copyButton);
deleteButton = $('<button id="btn-delete-row"></button>');
copyButton = $('<button id="btn-copy-row"></button>');
deleteButton.prop('disabled', true);
copyButton.prop('disabled', true);
sqlEditorObj = {
grid: gridSpy,
editor: {
handler: {
data_store: {
staged_rows: {'456': {}},
},
can_edit: false,
},
},
keys: null,
selection: selectionSpy,
columns: [
{
name: 'a pk column',
pos: 0,
},
{
name: 'some column',
pos: 1,
},
],
};
selectionSpy = jasmine.createSpyObj('selectionSpy', [
'setSelectedRows',
'getSelectedRanges',
]);
});
$('body').append(deleteButton);
$('body').append(copyButton);
deleteButton.prop('disabled', true);
copyButton.prop('disabled', true);
selectionSpy = jasmine.createSpyObj('selectionSpy', [
'setSelectedRows',
'getSelectedRanges',
]);
});
afterEach(function () {
copyButton.remove();
deleteButton.remove();
});
describe('when no full rows are selected', function () {
describe('when nothing is selected', function () {
beforeEach(function () {
selectionSpy.getSelectedRanges.and.returnValue([]);
sqlEditorObj.selection = selectionSpy;
SetStagedRows.call(sqlEditorObj, {}, {});
});
it('should disable the delete row button', function () {
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should disable the copy row button', function () {
expect($('#btn-copy-row').prop('disabled')).toBeTruthy();
});
it('should clear staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
});
describe('when there is a selection', function () {
beforeEach(function () {
var range = {
fromCell: 0,
toCell: 0,
fromRow: 1,
toRow: 1,
};
selectionSpy.getSelectedRanges.and.returnValue([range]);
sqlEditorObj.selection = selectionSpy;
SetStagedRows.call(sqlEditorObj, {}, {});
});
it('should disable the delete row button', function () {
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should disable the copy row button', function () {
expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
});
it('should clear staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
});
});
describe('when 2 full rows are selected', function () {
afterEach(function () {
copyButton.remove();
deleteButton.remove();
});
describe('when no full rows are selected', function () {
describe('when nothing is selected', function () {
beforeEach(function () {
var range1 = {
selectionSpy.getSelectedRanges.and.returnValue([]);
sqlEditorObj.selection = selectionSpy;
SetStagedRows.call(sqlEditorObj, {}, {});
});
it('should disable the delete row button', function () {
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should disable the copy row button', function () {
expect($('#btn-copy-row').prop('disabled')).toBeTruthy();
});
it('should clear staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
});
describe('when there is a selection', function () {
beforeEach(function () {
var range = {
fromCell: 0,
toCell: 1,
toCell: 0,
fromRow: 1,
toRow: 1,
};
var range2 = {
fromCell: 0,
toCell: 1,
fromRow: 2,
toRow: 2,
};
selectionSpy.getSelectedRanges.and.returnValue([range1, range2]);
selectionSpy.getSelectedRanges.and.returnValue([range]);
sqlEditorObj.selection = selectionSpy;
SetStagedRows.call(sqlEditorObj, {}, {});
});
describe('when table does not have primary keys', function () {
it('should enable the copy row button', function () {
it('should disable the delete row button', function () {
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should disable the copy row button', function () {
expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
});
it('should clear staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
});
});
describe('when 2 full rows are selected', function () {
beforeEach(function () {
var range1 = {
fromCell: 0,
toCell: 1,
fromRow: 1,
toRow: 1,
};
var range2 = {
fromCell: 0,
toCell: 1,
fromRow: 2,
toRow: 2,
};
selectionSpy.getSelectedRanges.and.returnValue([range1, range2]);
sqlEditorObj.selection = selectionSpy;
});
describe('when table does not have primary keys', function () {
it('should enable the copy row button', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
});
it('should not enable the delete row button', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should update staged rows with the __temp_PK value of the new Selected Rows', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({'456': {}, '789': {}});
});
describe('the user can edit', function () {
it('should enable the delete row button', function () {
sqlEditorObj.editor.handler.can_edit = true;
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
expect($('#btn-delete-row').prop('disabled')).toBeFalsy();
});
});
});
describe('when table has primary keys', function () {
beforeEach(function () {
sqlEditorObj.keys = {'a pk column': 'varchar'};
sqlEditorObj.editor.handler.data_store.staged_rows = {'456': {'a pk column': 'three'}};
});
describe('selected rows have primary key', function () {
it('should set the staged rows correctly', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual(
{'456': {'a pk column': 'three'}, '789': {'a pk column': 'five'}});
});
it('should not enable the delete row button', function () {
it('should not clear selected rows in Cell Selection Model', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should update staged rows with the __temp_PK value of the new Selected Rows', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({'456': {}, '789': {}});
});
describe('the user can edit', function () {
it('should enable the delete row button', function () {
sqlEditorObj.editor.handler.can_edit = true;
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-delete-row').prop('disabled')).toBeFalsy();
});
expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalledWith();
});
});
describe('when table has primary keys', function () {
describe('selected rows missing primary key', function () {
beforeEach(function () {
sqlEditorObj.keys = {'a pk column': 'varchar'};
sqlEditorObj.editor.handler.data_store.staged_rows = {'456': {0: 'three'}};
var data = [{'a pk column': 'one', 'some column': 'two', '__temp_PK': '123'},
{'some column': 'four', '__temp_PK': '456'},
{'some column': 'six', '__temp_PK': '789'},
{'a pk column': 'seven', 'some column': 'eight', '__temp_PK': '432'}],
dataView = new Slick.Data.DataView();
dataView.setItems(data, '__temp_PK');
gridSpy.getData.and.returnValue(dataView);
});
describe('selected rows have primary key', function () {
it('should set the staged rows correctly', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual(
{'456': {0: 'three'}, '789': {0: 'five'}});
});
it('should clear the staged rows', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
it('should not clear selected rows in Cell Selection Model', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalledWith();
it('should clear selected rows in Cell Selection Model', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.selection.setSelectedRows).toHaveBeenCalledWith([]);
});
});
describe('when the selected row is a new row', function () {
var parentDiv;
beforeEach(function () {
var childDiv = $('<div></div>');
parentDiv = $('<div class="new_row"></div>');
parentDiv.append(childDiv);
$('body').append(parentDiv);
gridSpy.getCellNode.and.returnValue(childDiv);
SetStagedRows.call(sqlEditorObj, {}, {});
});
afterEach(function () {
parentDiv.remove();
});
it('should not clear the staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({
'456': {'a pk column': 'three'},
'789': {'a pk column': 'five'},
});
});
describe('selected rows missing primary key', function () {
beforeEach(function () {
gridSpy.getData.and.returnValue([
{0: 'one', 1: 'two', __temp_PK: '123'},
{1: 'four', __temp_PK: '456'},
{1: 'six', __temp_PK: '789'},
{0: 'seven', 1: 'eight', __temp_PK: '432'},
]);
});
it('should clear the staged rows', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
it('should clear selected rows in Cell Selection Model', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.selection.setSelectedRows).toHaveBeenCalledWith([]);
});
});
describe('when the selected row is a new row', function () {
var parentDiv;
beforeEach(function () {
var childDiv = $('<div></div>');
parentDiv = $('<div class="new_row"></div>');
parentDiv.append(childDiv);
$('body').append(parentDiv);
gridSpy.getCellNode.and.returnValue(childDiv);
SetStagedRows.call(sqlEditorObj, {}, {});
});
afterEach(function () {
parentDiv.remove();
});
it('should not clear the staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({
'456': {0: 'three'},
'789': {0: 'five'},
});
});
it('should not clear selected rows in Cell Selection Model', function () {
expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalled();
});
it('should not clear selected rows in Cell Selection Model', function () {
expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalled();
});
});
});
});
});

View File

@@ -33,14 +33,17 @@ describe('XCellSelectionModel', function () {
}, {
id: '1',
name: 'some-column-name',
field: 'some-column-name',
pos: 0,
}, {
id: 'second-column-id',
name: 'second column',
field: 'second column',
pos: 1,
}, {
id: 'third-column-id',
name: 'third column',
field: 'third column',
pos: 2,
},
];
@@ -52,13 +55,15 @@ describe('XCellSelectionModel', function () {
'second column': 'second value ' + i,
'third column': 'third value ' + i,
'fourth column': 'fourth value ' + i,
'__temp_PK': '123' + i,
});
}
container = $('<div></div>');
var dataView = new Slick.Data.DataView();
container.height(9999);
container.width(9999);
grid = new SlickGrid(container, data, columns);
dataView.setItems(data, '__temp_PK');
grid = new SlickGrid(container, dataView, columns);
grid.setSelectionModel(new XCellSelectionModel());
$('body').append(container);
});

View File

@@ -28,21 +28,22 @@ describe('#handleQueryOutputKeyboardEvent', function () {
metaKey: false,
which: -1,
keyCode: -1,
preventDefault: jasmine.createSpy('preventDefault'),
preventDefault: jasmine.createSpy('preventDefault')
};
var data = [['', '0,0-cell-content', '0,1-cell-content'],
['', '1,0-cell-content', '1,1-cell-content'],
['', '2,0-cell-content', '2,1-cell-content']];
var columnDefinitions = [{name: 'checkboxColumn'}, {pos: 1, name: 'firstColumn'}, {
pos: 2,
name: 'secondColumn',
}];
grid = new SlickGrid($('<div></div>'), data, columnDefinitions);
grid.setSelectionModel(new XCellSelectionModel());
var data = [{'checkboxColumn': '', 'firstColumn': '0,0-cell-content', 'secondColumn': '0,1-cell-content', '__temp_PK': '123'},
{'checkboxColumn': '', 'firstColumn': '1,0-cell-content', 'secondColumn': '1,1-cell-content', '__temp_PK': '456'},
{'checkboxColumn': '', 'firstColumn': '2,0-cell-content', 'secondColumn': '2,1-cell-content', '__temp_PK': '789'}],
columnDefinitions = [{name: 'checkboxColumn'},
{pos: 1, name: 'firstColumn', field: 'firstColumn'},
{ pos: 2, name: 'secondColumn', field: 'secondColumn'}],
dataView = new Slick.Data.DataView();
grid = new Slick.Grid($('<div></div>'), dataView, columnDefinitions);
grid.setSelectionModel(new XCellSelectionModel());
dataView.setItems(data, '__temp_PK');
slickEvent = {
grid: grid,
grid: grid
};
spyOn(clipboard, 'copyTextToClipboard');