From bdf9f3404f3f85869bef6bc6969f196ba4183375 Mon Sep 17 00:00:00 2001 From: Akshay Joshi Date: Tue, 27 Nov 2018 11:18:47 +0000 Subject: [PATCH] Fix handling of array types as inputs to the debugger. Fixes #3354 --- docs/en_US/release_notes.rst | 1 + docs/en_US/release_notes_3_7.rst | 17 ++ web/pgadmin/static/js/backgrid.pgadmin.js | 172 ++++++++++++++++-- web/pgadmin/tools/debugger/__init__.py | 5 +- .../tools/debugger/static/js/debugger_ui.js | 118 +++++++----- .../debugger/sql/execute_plpgsql.sql | 2 +- 6 files changed, 256 insertions(+), 59 deletions(-) create mode 100644 docs/en_US/release_notes_3_7.rst diff --git a/docs/en_US/release_notes.rst b/docs/en_US/release_notes.rst index c33f383a7..6499226de 100644 --- a/docs/en_US/release_notes.rst +++ b/docs/en_US/release_notes.rst @@ -9,6 +9,7 @@ for it. .. toctree:: + release_notes_3_7 release_notes_3_6 release_notes_3_5 release_notes_3_4 diff --git a/docs/en_US/release_notes_3_7.rst b/docs/en_US/release_notes_3_7.rst new file mode 100644 index 000000000..7fdfaa269 --- /dev/null +++ b/docs/en_US/release_notes_3_7.rst @@ -0,0 +1,17 @@ +*********** +Version 3.7 +*********** + +Release date: 2019-01-10 + +This release contains a number of features and fixes reported since the release of pgAdmin4 3.6 + + +Features +******** + + +Bug fixes +********* + +| `Bug #3354 `_ - Fix handling of array types as inputs to the debugger. diff --git a/web/pgadmin/static/js/backgrid.pgadmin.js b/web/pgadmin/static/js/backgrid.pgadmin.js index 405e93af9..525da76d7 100644 --- a/web/pgadmin/static/js/backgrid.pgadmin.js +++ b/web/pgadmin/static/js/backgrid.pgadmin.js @@ -799,7 +799,7 @@ define([ var arrayCellModel = Backbone.Model.extend({ defaults: { - value: undefined, + value: null, }, }); @@ -817,11 +817,41 @@ define([ render: function() { var self = this, arrayValuesCol = this.model.get(this.column.get('name')), - gridCols = [{ + cell_type = 'string'; + + var data_type = this.model.get('type').replace('[]' ,''); + switch (data_type) { + case 'boolean': + cell_type = 'boolean'; + break; + case 'integer': + case 'smallint': + case 'bigint': + case 'serial': + case 'smallserial': + case 'bigserial': + case 'oid': + case 'cid': + case 'xid': + case 'tid': + cell_type = 'integer'; + break; + case 'real': + case 'numeric': + case 'double precision': + case 'decimal': + cell_type = 'number'; + break; + case 'date': + cell_type = 'date'; + break; + } + + var gridCols = [{ name: 'value', label: gettext('Array Values'), type: 'text', - cell: 'string', + cell: cell_type, headerCell: Backgrid.Extension.CustomHeaderIconCell, cellHeaderClasses: 'width_percent_100', }], @@ -844,6 +874,22 @@ define([ collection: arrayValuesCol, }); + this.grid.listenTo(arrayValuesCol, 'backgrid:error', + (function(obj) { + return function(ev) { + obj.model.trigger('backgrid:error', obj.model, obj.column, new Backgrid.Command(ev)); + }; + })(this) + ); + + this.grid.listenTo(arrayValuesCol, 'backgrid:edited', + (function(obj) { + return function(ev) { + obj.model.trigger('backgrid:edited', obj.model, obj.column, new Backgrid.Command(ev)); + }; + })(this) + ); + grid.render(); gridBody.append(grid.$el); @@ -893,8 +939,6 @@ define([ self.grid = null; } }, 10); - - } }, 10); return; @@ -944,8 +988,8 @@ define([ var values = []; rawData.each(function(m) { var val = m.get('value'); - if (_.isUndefined(val)) { - values.push('null'); + if (_.isUndefined(val) || _.isNull(val)) { + values.push('NULL'); } else { values.push(m.get('value')); } @@ -963,6 +1007,40 @@ define([ }, }); + /* + * This will help us transform the user input numeric array values in proper format to be + * displayed in the cell. + */ + var InputNumberArrayCellFormatter= Backgrid.Extension.InputNumberArrayCellFormatter = + function() {}; + _.extend(InputNumberArrayCellFormatter.prototype, { + /** + * Takes a raw value from a model and returns an optionally formatted + * string for display. + */ + fromRaw: function(rawData) { + var values = []; + rawData.each(function(m) { + var val = m.get('value'); + if (_.isUndefined(val) || _.isNull(val)) { + values.push('NULL'); + } else { + values.push(m.get('value')); + } + }); + return values.toString(); + }, + toRaw: function(formattedData) { + formattedData.each(function(m) { + m.set('value', parseFloat(m.get('value')), { + silent: true, + }); + }); + + return formattedData; + }, + }); + /* * InputStringArrayCell for rendering and taking input for string array type in debugger */ @@ -982,7 +1060,6 @@ define([ } this.model.set(this.column.get('name'), this.collection); - this.listenTo(this.collection, 'remove', this.render); }, }); @@ -990,13 +1067,70 @@ define([ /* * InputIntegerArrayCell for rendering and taking input for integer array type in debugger */ - Backgrid.Extension.InputIntegerArrayCell = Backgrid.Cell.extend({ + Backgrid.Extension.InputIntegerArrayCell = Backgrid.IntegerCell.extend({ className: 'width_percent_25', formatter: InputIntegerArrayCellFormatter, editor: InputArrayCellEditor, initialize: function() { - Backgrid.Cell.prototype.initialize.apply(this, arguments); + Backgrid.IntegerCell.prototype.initialize.apply(this, arguments); + // set value to empty array. + var m = arguments[0].model; + _.each(m.get('value'), function(arrVal) { + if (arrVal.value === 'NULL') { + arrVal.value = null; + } + }); + + if (_.isUndefined(this.collection)) { + this.collection = new(Backbone.Collection.extend({ + model: arrayCellModel, + }))(m.get('value')); + } + + this.model.set(this.column.get('name'), this.collection); + this.listenTo(this.collection, 'remove', this.render); + }, + }); + + /* + * InputNumberArrayCell for rendering and taking input for numeric array type in debugger + */ + Backgrid.Extension.InputNumberArrayCell = Backgrid.NumberCell.extend({ + className: 'width_percent_25', + formatter: InputNumberArrayCellFormatter, + editor: InputArrayCellEditor, + + initialize: function() { + Backgrid.NumberCell.prototype.initialize.apply(this, arguments); + // set value to empty array. + var m = arguments[0].model; + _.each(m.get('value'), function(arrVal) { + if (arrVal.value === 'NULL') { + arrVal.value = null; + } + }); + + if (_.isUndefined(this.collection)) { + this.collection = new(Backbone.Collection.extend({ + model: arrayCellModel, + }))(m.get('value')); + } + + this.model.set(this.column.get('name'), this.collection); + this.listenTo(this.collection, 'remove', this.render); + }, + }); + + /* + * InputBooleanArrayCell for rendering and taking input for boolean array type in debugger + */ + Backgrid.Extension.InputBooleanArrayCell = Backgrid.BooleanCell.extend({ + className: 'width_percent_25', + editor: InputArrayCellEditor, + + initialize: function() { + Backgrid.BooleanCell.prototype.initialize.apply(this, arguments); // set value to empty array. var m = arguments[0].model; if (_.isUndefined(this.collection)) { @@ -1005,9 +1139,7 @@ define([ }))(m.get('value')); } - this.model.set(this.column.get('name'), this.collection); - this.listenTo(this.collection, 'remove', this.render); }, }); @@ -1479,6 +1611,22 @@ define([ remove: Backgrid.Extension.DependentCell.prototype.remove, }); + Backgrid.BooleanCellFormatter = _.extend(Backgrid.CellFormatter.prototype, { + fromRaw: function (rawValue) { + if (_.isUndefined(rawValue) || _.isNull(rawValue)) { + return false; + } else if (rawValue === '1' || rawValue === 'True') { + return true; + } else if (rawValue === '0' || rawValue === 'False') { + return false; + } + return rawValue; + }, + toRaw: function (formattedData) { + return formattedData; + }, + }); + return Backgrid; }); diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index dca39b131..1fbf145f3 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -1810,7 +1810,10 @@ def set_arguments_sqlite(sid, did, scid, func_id): if data[i]['value'].__class__.__name__ in ( 'list') and data[i]['value']: for k in range(0, len(data[i]['value'])): - array_string += data[i]['value'][k]['value'] + if data[i]['value'][k]['value'] is None: + array_string += 'NULL' + else: + array_string += str(data[i]['value'][k]['value']) if k != (len(data[i]['value']) - 1): array_string += ',' elif data[i]['value'].__class__.__name__ in ( diff --git a/web/pgadmin/tools/debugger/static/js/debugger_ui.js b/web/pgadmin/tools/debugger/static/js/debugger_ui.js index 303c9ce0c..85d81427f 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger_ui.js +++ b/web/pgadmin/tools/debugger/static/js/debugger_ui.js @@ -17,42 +17,76 @@ define([ // if variable type is an array then we need to render the custom control to take the input from user. if (variable_type.indexOf('[]') != -1) { - if (variable_type.indexOf('integer') != -1) { + var data_type = variable_type.replace('[]' ,''); + + switch (data_type) { + case 'boolean': + return Backgrid.Extension.InputBooleanArrayCell; + case 'integer': + case 'smallint': + case 'bigint': + case 'serial': + case 'smallserial': + case 'bigserial': + case 'oid': + case 'cid': + case 'xid': + case 'tid': return Backgrid.Extension.InputIntegerArrayCell; + case 'real': + case 'numeric': + case 'double precision': + case 'decimal': + return Backgrid.Extension.InputNumberArrayCell; + default: + return Backgrid.Extension.InputStringArrayCell; } - return Backgrid.Extension.InputStringArrayCell; - } - - switch (variable_type) { - case 'bool': - return Backgrid.BooleanCell; - case 'integer': - // As we are getting this value as text from sqlite database so we need to type cast it. - if (model.get('value') != undefined) { - model.set({ - 'value': parseInt(model.get('value')), - }, { - silent: true, + } else { + switch (variable_type) { + case 'boolean': + return Backgrid.BooleanCell.extend({ + formatter: Backgrid.BooleanCellFormatter, }); - } + case 'integer': + case 'smallint': + case 'bigint': + case 'serial': + case 'smallserial': + case 'bigserial': + case 'oid': + case 'cid': + case 'xid': + case 'tid': + // As we are getting this value as text from sqlite database so we need to type cast it. + if (model.get('value') != undefined) { + model.set({ + 'value': parseInt(model.get('value')), + }, { + silent: true, + }); + } - return Backgrid.IntegerCell; - case 'real': - // As we are getting this value as text from sqlite database so we need to type cast it. - if (model.get('value') != undefined) { - model.set({ - 'value': parseFloat(model.get('value')), - }, { - silent: true, - }); + return Backgrid.IntegerCell; + case 'real': + case 'numeric': + case 'double precision': + case 'decimal': + // As we are getting this value as text from sqlite database so we need to type cast it. + if (model.get('value') != undefined) { + model.set({ + 'value': parseFloat(model.get('value')), + }, { + silent: true, + }); + } + return Backgrid.NumberCell; + case 'string': + return Backgrid.StringCell; + case 'date': + return Backgrid.DateCell; + default: + return Backgrid.Cell; } - return Backgrid.NumberCell; - case 'string': - return Backgrid.StringCell; - case 'date': - return Backgrid.DateCell; - default: - return Backgrid.Cell; } }; @@ -378,13 +412,6 @@ define([ values = []; if (argtype[index].indexOf('[]') != -1) { vals = func_args_data[i]['value'].split(','); - if (argtype[index].indexOf('integer') != -1) { - _.each(vals, function(val) { - values.push({ - 'value': parseInt(val), - }); - }); - } _.each(vals, function(val) { values.push({ 'value': val, @@ -485,13 +512,6 @@ define([ values = []; if (argtype[index].indexOf('[]') != -1) { vals = func_args_data[i]['value'].split(','); - if (argtype[index].indexOf('integer') != -1) { - _.each(vals, function(val) { - values.push({ - 'value': parseInt(val), - }); - }); - } _.each(vals, function(val) { values.push({ 'value': val, @@ -918,6 +938,14 @@ define([ }; })(this) ); + + this.grid.listenTo(this.debuggerInputArgsColl, 'backgrid:error', + (function(obj) { + return function() { + obj.__internal.buttons[0].element.disabled = true; + }; + })(this) + ); }, }; }); diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql index 134a17f3a..2295f9446 100644 --- a/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql @@ -11,7 +11,7 @@ {% if data %} {% for dict_item in data %} {% if 'type' in dict_item and 'value' in dict_item %} -{% if dict_item['value'] != 'NULL' %} +{% if dict_item['value'] != 'NULL' and '[]' not in dict_item['type'] %} {{ dict_item['value']|qtLiteral }}::{{ dict_item['type'] }}{% if not loop.last %}, {% endif %} {% elif dict_item['value'] == 'NULL' %} {{ dict_item['value'] }}::{{ dict_item['type'] }}{% if not loop.last %}, {% endif %}