From 208ee4da8c19a81b5907b436e2eb16a72591234e Mon Sep 17 00:00:00 2001 From: Harshal Dhumal Date: Wed, 29 Aug 2018 17:44:37 +0530 Subject: [PATCH] Fixed debugger execution issues: 1. Allow debugging of EPAS package procedures/functions with INOUT params. 2. Add support for indirect debugging for EPAS package procedures/functions. 3. Allow debugging with NULL param values. 4. Remove saved debug arguments. Fixes #3191 --- docs/en_US/release_notes_3_3.rst | 1 + web/migrations/versions/ca00ec32581b_.py | 34 ++++ web/pgadmin/model/__init__.py | 2 +- .../tools/debugger/static/js/debugger.js | 23 ++- .../tools/debugger/static/js/debugger_ui.js | 167 ++++++++++-------- .../debugger/sql/get_function_debug_info.sql | 5 - 6 files changed, 150 insertions(+), 82 deletions(-) create mode 100644 web/migrations/versions/ca00ec32581b_.py diff --git a/docs/en_US/release_notes_3_3.rst b/docs/en_US/release_notes_3_3.rst index 32d8b0b06..ab8ce6492 100644 --- a/docs/en_US/release_notes_3_3.rst +++ b/docs/en_US/release_notes_3_3.rst @@ -17,6 +17,7 @@ Bug fixes ********* | `Bug #3136 `_ - Stabilise feature tests for continuous running on CI systems. +| `Bug #3191 `_ - Fixed debugger execution issues. | `Bug #3313 `_ - Ensure 'select all' and 'unselect all' working properly for pgAgent schedule. | `Bug #3325 `_ - Fix sort/filter dialog issue where it incorrectly requires ASC/DESC. | `Bug #3347 `_ - Ensure backup should work with '--data-only' and '--schema-only' for any format. diff --git a/web/migrations/versions/ca00ec32581b_.py b/web/migrations/versions/ca00ec32581b_.py new file mode 100644 index 000000000..220823514 --- /dev/null +++ b/web/migrations/versions/ca00ec32581b_.py @@ -0,0 +1,34 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +""" +Deleting old debug logs + +Revision ID: ca00ec32581b +Revises: aa86fb60b73d +Create Date: 2018-08-29 15:33:57.855491 + +""" + +from pgadmin.model import db + +# revision identifiers, used by Alembic. +revision = 'ca00ec32581b' +down_revision = 'aa86fb60b73d' +branch_labels = None +depends_on = None + + +def upgrade(): + db.engine.execute( + 'DELETE FROM debugger_function_arguments' + ) + + +def downgrade(): + pass diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index f74033732..d3b0c5480 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy # ########################################################################## -SCHEMA_VERSION = 18 +SCHEMA_VERSION = 19 ########################################################################## # diff --git a/web/pgadmin/tools/debugger/static/js/debugger.js b/web/pgadmin/tools/debugger/static/js/debugger.js index 39caca716..848737ccb 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger.js +++ b/web/pgadmin/tools/debugger/static/js/debugger.js @@ -354,6 +354,24 @@ define([ 'func_id': debuggerUtils.getProcedureId(treeInfo), } ); + } else if (d._type == 'edbfunc') { + // Get the existing function parameters available from sqlite database + baseUrl = url_for('debugger.initialize_target_for_function', { + 'debug_type': 'indirect', + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.edbfunc._id, + }); + } else if (d._type == 'edbproc') { + // Get the existing function parameters available from sqlite database + baseUrl = url_for('debugger.initialize_target_for_function', { + 'debug_type': 'indirect', + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.edbproc._id, + }); } else if (d._type == 'trigger_function') { baseUrl = url_for( 'debugger.initialize_target_for_function', { @@ -449,7 +467,8 @@ define([ i = item || t.selected(), d = i && i.length == 1 ? t.itemData(i) : undefined, node = d && pgBrowser.Nodes[d._type], - self = this; + self = this, + is_edb_proc = d._type == 'edbproc'; if (!d) return; @@ -465,7 +484,7 @@ define([ // Open Alertify the dialog to take the input arguments from user if function having input arguments if (res.data[0]['require_input']) { - get_function_arguments(res.data[0], 0); + get_function_arguments(res.data[0], 0, is_edb_proc); } else { // Initialize the target and create asynchronous connection and unique transaction ID // If there is no arguments to the functions then we should not ask for for function arguments and diff --git a/web/pgadmin/tools/debugger/static/js/debugger_ui.js b/web/pgadmin/tools/debugger/static/js/debugger_ui.js index 6f6fad5b6..38f68694f 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger_ui.js +++ b/web/pgadmin/tools/debugger/static/js/debugger_ui.js @@ -120,14 +120,17 @@ define([ } }; - var res = function(args, restart_debug) { + var res = function(debug_info, restart_debug, is_edb_proc) { if (!Alertify.debuggerInputArgsDialog) { Alertify.dialog('debuggerInputArgsDialog', function factory() { return { - main: function(title, data, restart_debug) { + main: function(title, debug_info, restart_debug, is_edb_proc) { this.set('title', title); - this.data = data; - this.restart_debug = restart_debug; + + // setting value in alertify settings allows us to access it from + // other functions other than main function. + this.set('debug_info', debug_info); + this.set('restart_debug', restart_debug); // Variables to store the data sent from sqlite database var func_args_data = this.func_args_data = []; @@ -182,10 +185,10 @@ define([ } else { // Get the existing function parameters available from sqlite database _Url = url_for('debugger.get_arguments', { - 'sid': this.data.server_id, - 'did': this.data.database_id, - 'scid': this.data.schema_id, - 'func_id': this.data.function_id, + 'sid': debug_info.server_id, + 'did': debug_info.database_id, + 'scid': debug_info.schema_id, + 'func_id': debug_info.function_id, }); } $.ajax({ @@ -279,60 +282,63 @@ define([ // Below will calculate the input argument id required to store in sqlite database var input_arg_id = this.input_arg_id = [], k; - if (this.data['proargmodes'] != null) { - var argmode_1 = this.data['proargmodes'].split(','); + if (debug_info['proargmodes'] != null) { + var argmode_1 = debug_info['proargmodes'].split(','); for (k = 0; k < argmode_1.length; k++) { - if (argmode_1[k] == 'i' || argmode_1[k] == 'b') { + if (argmode_1[k] == 'i' || argmode_1[k] == 'b' || + (is_edb_proc && argmode_1[k] == 'o')) { input_arg_id.push(k); } } } else { - var argtype_1 = this.data['proargtypenames'].split(','); + var argtype_1 = debug_info['proargtypenames'].split(','); for (k = 0; k < argtype_1.length; k++) { input_arg_id.push(k); } } - argtype = this.data['proargtypenames'].split(','); + argtype = debug_info['proargtypenames'].split(','); - if (this.data['proargmodes'] != null) { - argmode = this.data['proargmodes'].split(','); + if (debug_info['proargmodes'] != null) { + argmode = debug_info['proargmodes'].split(','); } - if (this.data['pronargdefaults']) { - default_args_count = this.data['pronargdefaults']; - default_args = this.data['proargdefaults'].split(','); + if (debug_info['pronargdefaults']) { + default_args_count = debug_info['pronargdefaults']; + default_args = debug_info['proargdefaults'].split(','); arg_cnt = default_args_count; } var vals, values, index, use_def_value, j; - if (this.data['proargnames'] != null) { - argname = this.data['proargnames'].split(','); + if (debug_info['proargnames'] != null) { + argname = debug_info['proargnames'].split(','); // It will assign default values to "Default value" column for (j = (argname.length - 1); j >= 0; j--) { - if (this.data['proargmodes'] != null) { - if (arg_cnt && (argmode[j] == 'i' || argmode[j] == 'b')) { - arg_cnt = arg_cnt - 1; - def_val_list[j] = default_args[arg_cnt]; - } else { - def_val_list[j] = ''; + if (debug_info['proargmodes'] != null) { + if (argmode[j] == 'i' || argmode[j] == 'b' || + (is_edb_proc && argmode[j] == 'o')) { + if (arg_cnt) { + arg_cnt = arg_cnt - 1; + def_val_list[j] = default_args[arg_cnt]; + } else { + def_val_list[j] = ''; + } } + } else if (arg_cnt) { + arg_cnt = arg_cnt - 1; + def_val_list[j] = default_args[arg_cnt]; } else { - if (arg_cnt) { - arg_cnt = arg_cnt - 1; - def_val_list[j] = default_args[arg_cnt]; - } else { - def_val_list[j] = ''; - } + def_val_list[j] = ''; } } if (argtype.length != 0) { for (i = 0; i < argtype.length; i++) { - if (this.data['proargmodes'] != null) { - if (argmode[i] == 'i' || argmode[i] == 'b') { + if (debug_info['proargmodes'] != null) { + if (argmode[i] == 'i' || argmode[i] == 'b' || + (is_edb_proc && argmode[i] == 'o')) { use_def_value = false; if (def_val_list[i] != '') { use_def_value = true; @@ -356,14 +362,17 @@ define([ 'default_value': def_val_list[i], }); } - - } } // Need to update the func_obj variable from sqlite database if available if (func_args_data.length != 0) { for (i = 0; i < func_args_data.length; i++) { + if (debug_info['proargmodes'] != null && + (argmode[i] == 'o' && !is_edb_proc)) { + continue; + } + index = func_args_data[i]['arg_id']; values = []; if (argtype[index].indexOf('[]') != -1) { @@ -407,7 +416,7 @@ define([ } // If there is no default arguments - if (!this.data['pronargdefaults']) { + if (!debug_info['pronargdefaults']) { for (i = 0; i < argtype.length; i++) { my_obj.push({ 'name': myargname[i], @@ -421,7 +430,7 @@ define([ // If there is default arguments //Below logic will assign default values to "Default value" column for (j = (myargname.length - 1); j >= 0; j--) { - if (this.data['proargmodes'] == null) { + if (debug_info['proargmodes'] == null) { if (arg_cnt) { arg_cnt = arg_cnt - 1; def_val_list[j] = default_args[arg_cnt]; @@ -429,7 +438,7 @@ define([ def_val_list[j] = ''; } } else { - if (arg_cnt && (argmode[j] == 'i' || argmode[j] == 'b')) { + if (arg_cnt) { arg_cnt = arg_cnt - 1; def_val_list[j] = default_args[arg_cnt]; } else { @@ -439,7 +448,7 @@ define([ } for (i = 0; i < argtype.length; i++) { - if (this.data['proargmodes'] == null) { + if (debug_info['proargmodes'] == null) { use_def_value = false; if (def_val_list[i] != '') { use_def_value = true; @@ -451,7 +460,8 @@ define([ 'default_value': def_val_list[i], }); } else { - if (argmode[i] == 'i' || argmode[i] == 'b') { + if (argmode[i] == 'i' || argmode[i] == 'b' || + (is_edb_proc && argmode[i] == 'o')) { use_def_value = false; if (def_val_list[i] != '') { use_def_value = true; @@ -536,6 +546,10 @@ define([ } }, + settings: { + debug_info: undefined, + restart_debug: undefined, + }, setup: function() { return { buttons: [{ @@ -573,7 +587,7 @@ define([ // If the debugging is started again then treeInfo is already // stored in this.data so we can use the same. - if (self.restart_debug == 0) { + if (self.setting('restart_debug') == 0) { var t = pgBrowser.tree, i = t.selected(), d = i && i.length == 1 ? t.itemData(i) : undefined, @@ -615,7 +629,7 @@ define([ } } - if (self.restart_debug == 0) { + if (self.setting('restart_debug') == 0) { var f_id; if (d._type == 'function') { f_id = treeInfo.function._id; @@ -642,10 +656,10 @@ define([ } else { // Below will format the data to be stored in sqlite database sqlite_func_args_list.push({ - 'server_id': self.data.server_id, - 'database_id': self.data.database_id, - 'schema_id': self.data.schema_id, - 'function_id': self.data.function_id, + 'server_id': self.setting('debug_info').server_id, + 'database_id': self.setting('debug_info').database_id, + 'schema_id': self.setting('debug_info').schema_id, + 'function_id': self.setting('debug_info').function_id, 'arg_id': self.input_arg_id[int_count], 'is_null': m.get('is_null') ? 1 : 0, 'is_expression': m.get('expr') ? 1 : 0, @@ -660,7 +674,7 @@ define([ var baseUrl; // If debugging is not started again then we should initialize the target otherwise not - if (self.restart_debug == 0) { + if (self.setting('restart_debug') == 0) { if (d._type == 'function') { baseUrl = url_for('debugger.initialize_target_for_function', { 'debug_type': 'direct', @@ -797,7 +811,7 @@ define([ // If the debugging is started again then we should only set the // arguments and start the listener again baseUrl = url_for('debugger.start_listener', { - 'trans_id': self.data.trans_id, + 'trans_id': self.setting('debug_info').trans_id, }); $.ajax({ @@ -817,10 +831,10 @@ define([ // Set the new input arguments given by the user during debugging var _Url = url_for('debugger.set_arguments', { - 'sid': self.data.server_id, - 'did': self.data.database_id, - 'scid': self.data.schema_id, - 'func_id': self.data.function_id, + 'sid': self.setting('debug_info').server_id, + 'did': self.setting('debug_info').database_id, + 'scid': self.setting('debug_info').schema_id, + 'func_id': self.setting('debug_info').function_id, }); $.ajax({ url: _Url, @@ -871,33 +885,38 @@ define([ debug button. */ this.grid.listenTo(this.debuggerInputArgsColl, 'backgrid:edited', - (function(obj) { + (function(obj) { - return function() { + return function() { - var enable_btn = false; + var enable_btn = false; - for (var i = 0; i < this.collection.length; i++) { + for (var i = 0; i < this.collection.length; i++) { - // TODO: Need to check the "NULL" and "Expression" column value to - // enable/disable the "Debug" button - if (this.collection.models[i].get('value') == '' || - this.collection.models[i].get('value') == null || - this.collection.models[i].get('value') == undefined) { - enable_btn = true; - - if (this.collection.models[i].get('use_default')) { + if (this.collection.models[i].get('is_null')) { obj.__internal.buttons[0].element.disabled = false; - } else { - obj.__internal.buttons[0].element.disabled = true; - break; + enable_btn = true; + continue; + } + // TODO: Need to check the "Expression" column value to + // enable/disable the "Debug" button + if (this.collection.models[i].get('value') == '' || + this.collection.models[i].get('value') == null || + this.collection.models[i].get('value') == undefined) { + enable_btn = true; + + if (this.collection.models[i].get('use_default')) { + obj.__internal.buttons[0].element.disabled = false; + } else { + obj.__internal.buttons[0].element.disabled = true; + break; + } } } - } - if (!enable_btn) - obj.__internal.buttons[0].element.disabled = false; - }; - })(this) + if (!enable_btn) + obj.__internal.buttons[0].element.disabled = false; + }; + })(this) ); }, }; @@ -905,7 +924,7 @@ define([ } Alertify.debuggerInputArgsDialog( - gettext('Debugger'), args, restart_debug + gettext('Debugger'), debug_info, restart_debug, is_edb_proc ).resizeTo('60%', '60%'); }; diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/get_function_debug_info.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/get_function_debug_info.sql index 64195b5ef..72626792f 100644 --- a/web/pgadmin/tools/debugger/templates/debugger/sql/get_function_debug_info.sql +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/get_function_debug_info.sql @@ -26,12 +26,7 @@ SELECT pg_catalog.generate_series(0, pg_catalog.array_upper(proargtypes, 1)) s(i)), ',') END AS proargtypes, pg_catalog.array_to_string(p.proargnames, ',') AS proargnames, - {% if is_ppas_database %} - pg_catalog.array_to_string(proargdeclaredmodes, ',') AS proargmodes, - {% else %} pg_catalog.array_to_string(proargmodes, ',') AS proargmodes, - {% endif %} - {% if is_ppas_database %} CASE WHEN n.nspparent <> 0 THEN n.oid ELSE 0 END AS pkg, CASE WHEN n.nspparent <> 0 THEN n.nspname ELSE '' END AS pkgname,