diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index 06ff5a205..22cbd0504 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -111,10 +111,11 @@ def update_session_function_transaction(trans_id, data): @blueprint.route('/init/////', methods=['GET']) +@blueprint.route('/init//////', methods=['GET']) @login_required -def init_function(node_type, sid, did, scid, fid): +def init_function(node_type, sid, did, scid, fid, trid=None): """ - init_function(node_type, sid, did, scid, fid) + init_function(node_type, sid, did, scid, fid, trid) This method is responsible to initialize the function required for debugging. This method is also responsible for storing the all functions data to session variable. @@ -132,6 +133,8 @@ def init_function(node_type, sid, did, scid, fid): - Schema Id fid - Function Id + trid + - Trigger Function Id """ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) conn = manager.connection(did=did) @@ -149,6 +152,18 @@ def init_function(node_type, sid, did, scid, fid): # Set the template path required to read the sql files template_path = 'debugger/sql' + if node_type == 'trigger': + # Find trigger function id from trigger id + sql = render_template("/".join([template_path, 'get_trigger_function_info.sql']), table_id=fid, trigger_id=trid) + + status, tr_set = conn.execute_dict(sql) + if not status: + current_app.logger.debug("Error retrieving trigger function information from database") + return internal_server_error(errormsg=tr_set) + + fid = tr_set['rows'][0]['tgfoid'] + + sql = '' sql = render_template("/".join([template_path, 'get_function_debug_info.sql']), is_ppas_database=ppas_server, hasFeatureFunctionDefaults=True, fid=fid) status, r_set = conn.execute_dict(sql) @@ -166,9 +181,6 @@ def init_function(node_type, sid, did, scid, fid): if ":" in r_set['rows'][0]['name']: ret_status = False msg = gettext("Functions with a colon in the name cannot be debugged.") - elif node_type != 'trigger' and r_set['rows'][0]['rettype'] == 'trigger': - ret_status = False - msg = gettext("Functions with return type of 'trigger' cannot be debugged.") elif ppas_server and r_set['rows'][0]['prosrc'].lstrip().startswith('$__EDBwrapped__$'): ret_status = False msg = gettext("EDB Advanced Server wrapped functions cannot be debugged.") @@ -304,10 +316,12 @@ def direct_new(trans_id): @blueprint.route('/initialize_target/////', methods=['GET', 'POST']) +@blueprint.route('/initialize_target//////', + methods=['GET', 'POST']) @login_required -def initialize_target(debug_type, sid, did, scid, func_id): +def initialize_target(debug_type, sid, did, scid, func_id, tri_id=None): """ - initialize_target(debug_type, sid, did, scid, func_id) + initialize_target(debug_type, sid, did, scid, func_id, tri_id) This method is responsible for creating an asynchronous connection. It will also create a unique transaction id and store the information @@ -324,6 +338,8 @@ def initialize_target(debug_type, sid, did, scid, func_id): - Schema Id func_id - Function Id + tri_id + - Trigger Function Id """ # Create asynchronous connection using random connection id. @@ -339,6 +355,20 @@ def initialize_target(debug_type, sid, did, scid, func_id): if not status: return internal_server_error(errormsg=str(msg)) + # Set the template path required to read the sql files + template_path = 'debugger/sql' + + if tri_id is not None: + # Find trigger function id from trigger id + sql = render_template("/".join([template_path, 'get_trigger_function_info.sql']), table_id=func_id, trigger_id=tri_id) + + status, tr_set = conn.execute_dict(sql) + if not status: + current_app.logger.debug("Error retrieving trigger function information from database") + return internal_server_error(errormsg=tr_set) + + func_id = tr_set['rows'][0]['tgfoid'] + # Create a unique id for the transaction trans_id = str(random.randint(1, 9999999)) diff --git a/web/pgadmin/tools/debugger/templates/debugger/js/debugger.js b/web/pgadmin/tools/debugger/templates/debugger/js/debugger.js index a68009d30..962c44b66 100644 --- a/web/pgadmin/tools/debugger/templates/debugger/js/debugger.js +++ b/web/pgadmin/tools/debugger/templates/debugger/js/debugger.js @@ -46,6 +46,18 @@ define( category: 'Debugging', priority: 10, label: '{{ _('Set breakpoint') }}', data: {object: 'procedure'}, icon: 'fa fa-arrow-circle-right', enable: 'can_debug' + }, { + name: 'trigger_function_indirect_debugger', node: 'trigger_function', module: this, + applies: ['object', 'context'], callback: 'check_func_debuggable', + priority: 10, label: '{{ _('Set breakpoint') }}', category: 'Debugging', + icon: 'fa fa-arrow-circle-right', data: {object:'trigger_function'}, + enable: 'can_debug' + }, { + name: 'trigger_indirect_debugger', node: 'trigger', module: this, + applies: ['object', 'context'], callback: 'check_func_debuggable', + priority: 10, label: '{{ _('Set breakpoint') }}', category: 'Debugging', + icon: 'fa fa-arrow-circle-right', data: {object:'trigger'}, + enable: 'can_debug' }]); // Create and load the new frame required for debugger panel @@ -89,6 +101,11 @@ define( if (!(treeInfo.server.user.is_superuser || treeInfo.function.funcowner == treeInfo.server.user.name)) return false; + // For trigger node, language will be undefined - we should allow indirect debugging for trigger node + if (d_.language == undefined && d_._type == 'trigger') { + return true; + } + if (d_.language != 'plpgsql' && d_.language != 'edbspl') { return false; } @@ -178,10 +195,19 @@ define( var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "indirect/" + treeInfo.server._id + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.function._id; } - else { + else if (d._type == "procedure") { var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "indirect/" + treeInfo.server._id + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.procedure._id; } + else if (d._type == "trigger_function") { + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "indirect/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.trigger_function._id; + } + else if (d._type == "trigger") { + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "indirect/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.table._id + + "/" + treeInfo.trigger._id; + } $.ajax({ url: baseUrl, diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/get_trigger_function_info.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/get_trigger_function_info.sql new file mode 100644 index 000000000..8807ecb0d --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/get_trigger_function_info.sql @@ -0,0 +1,12 @@ +{### To fetch trigger function information ###} +SELECT t.oid, t.xmin, t.*, relname, CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, nspname, des.description, l.lanname, p.prosrc, + COALESCE(substring(pg_get_triggerdef(t.oid), 'WHEN (.*) EXECUTE PROCEDURE'), substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \$trigger')) AS whenclause + FROM pg_trigger t + JOIN pg_class cl ON cl.oid=tgrelid + JOIN pg_namespace na ON na.oid=relnamespace + LEFT OUTER JOIN pg_description des ON (des.objoid=t.oid AND des.classoid='pg_trigger'::regclass) + LEFT OUTER JOIN pg_proc p ON p.oid=t.tgfoid + LEFT OUTER JOIN pg_language l ON l.oid=p.prolang + WHERE NOT tgisinternal + AND tgrelid = {{table_id}}::oid AND t.oid = {{trigger_id}}::oid + ORDER BY tgname