diff --git a/web/pgadmin/browser/static/js/frame.js b/web/pgadmin/browser/static/js/frame.js index 92ea5bf..193a01d 100644 --- a/web/pgadmin/browser/static/js/frame.js +++ b/web/pgadmin/browser/static/js/frame.js @@ -29,7 +29,8 @@ function(_, pgAdmin) { title: that.title, isPrivate: that.isPrivate, onCreate: function(myPanel) { - $(myPanel).data('pgAdminName', that.name); + $(myPanel).data('frameInitialized', false); + $(myPanel).data('pgFrameName', that.name); myPanel.initSize(that.width, that.height); if (myPanel.showTitle == false) myPanle.title(false); @@ -41,7 +42,11 @@ function(_, pgAdmin) { var frame = new wcIFrame($frameArea, myPanel); $(myPanel).data('embededFrame', frame); - setTimeout(function() { frame.openURL(that.url); }, 500); + setTimeout(function() { + frame.openURL(that.url); + + pgBrowser.Events.trigger('pgadmin-browser:frame:urlloaded:' + that.name, frame, that.url, self); + }, 1); if (that.events && _.isObject(that.events)) { _.each(that.events, function(v, k) { diff --git a/web/pgadmin/static/css/overrides.css b/web/pgadmin/static/css/overrides.css index ca87e7a..cfddaa3 100755 --- a/web/pgadmin/static/css/overrides.css +++ b/web/pgadmin/static/css/overrides.css @@ -911,4 +911,12 @@ ul.nav.nav-tabs { .select2-cell .select2-container > .backgrid th, .backgrid td { padding: 0px; -} \ No newline at end of file +} + +.breakpoints { + width: 0.8em; +} + +.CodeMirror-activeline-background { + background: #50B0F0; +} diff --git a/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js b/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js index 9965f1b..611e54e 100644 --- a/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js +++ b/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js @@ -576,6 +576,236 @@ editor: TextareaCellEditor }); + + /** + * Custom header icon cell to add the icon in table header. + */ + var CustomHeaderIconCell = Backgrid.Extension.CustomHeaderIconCell = Backgrid.HeaderCell.extend({ + /** @property */ + className: "header-icon-cell", + events: { + "click": "addHeaderIcon" + }, + addHeaderIcon: function (e) { + self = this; + m = new (this.collection.model); + this.collection.add(m) + e.preventDefault(); + }, + render: function () { + this.$el.empty(); + //this.$el.html(""); + this.$el.html(" "); + this.delegateEvents(); + return this; + } + }); + + + var arrayCellModel = Backbone.Model.extend({ + defaults: { + value: undefined + } + }); + + /** + Custom InputArrayCellEditor for editing user input array for debugger. + */ + var InputArrayCellEditor = Backgrid.Extension.InputArrayCellEditor = + Backgrid.CellEditor.extend({ + tagName: "div", + + events: { + 'blur': 'lostFocus' + }, + + render: function () { + var self = this, + arrayValuesCol = this.model.get(this.column.get("name")), + tbl = $("
").appendTo(this.$el), + gridCols = [ + {name: 'value', label:'Array Values', type: 'text', cell:'string', headerCell: Backgrid.Extension.CustomHeaderIconCell, cellHeaderClasses: 'width_percent_100'}, + ], + gridBody = $("
"); + + this.$el.attr('tabindex', '1'); + + gridCols.unshift({ + name: "pg-backform-delete", label: "", + cell: Backgrid.Extension.DeleteCell, + //headerCell: Backgrid.Extension.CustomHeaderIconCell, + editable: false, cell_priority: -1 + }); + + this.$el.empty(); + var grid = self.grid = new Backgrid.Grid({ + columns: gridCols, + collection:arrayValuesCol + }); + + grid.render(); + + gridBody.append(grid.$el) + + this.$el.html(gridBody); + + $(self.$el).pgMakeVisible('backform-tab'); + self.delegateEvents(); + + return this; + }, + + /* + * Call back function when the grid lost the focus + */ + lostFocus: function(ev) { + + var self = this, + /* + * Function to determine whether one dom element is descendant of another + * dom element. + */ + isDescendant = function (parent, child) { + var node = child.parentNode; + while (node != null) { + if (node == parent) { + return true; + } + node = node.parentNode; + } + return false; + } + /* + * Between leaving the old element focus and entering the new element focus the + * active element is the document/body itself so add timeout to get the proper + * focused active element. + */ + setTimeout(function() { + if (self.$el[0] != document.activeElement && !isDescendant(self.$el[0], document.activeElement)){ + var m = self.model, + column = self.column; + m.trigger('backgrid:edited', m, column, new Backgrid.Command(ev)); + + setTimeout(function(){ + if (self.grid) { + self.grid.remove(); + self.grid = null; + } + }, 10); + + + }},10); + return; + } + }); + + /* + * This will help us transform the user input string array values in proper format to be + * displayed in the cell. + */ + var InputStringArrayCellFormatter = Backgrid.Extension.InputStringArrayCellFormatter = + function () {}; + _.extend(InputStringArrayCellFormatter.prototype, { + /** + * Takes a raw value from a model and returns an optionally formatted + * string for display. + */ + fromRaw: function (rawData, model) { + var values = [] + rawData.each(function(m){ + var val = m.get('value'); + if (_.isUndefined(val)) { + values.push("null"); + } else { + values.push(m.get("value")); + } + } + ); + return values.toString(); + }, + toRaw: function (formattedData, model) { + return formattedData; + } + }); + + /* + * This will help us transform the user input integer array values in proper format to be + * displayed in the cell. + */ + var InputIntegerArrayCellFormatter = Backgrid.Extension.InputIntegerArrayCellFormatter = + function () {}; + _.extend(InputIntegerArrayCellFormatter.prototype, { + /** + * Takes a raw value from a model and returns an optionally formatted + * string for display. + */ + fromRaw: function (rawData, model) { + var values = [] + rawData.each(function(m){ + var val = m.get('value'); + if (_.isUndefined(val)) { + values.push("null"); + } else { + values.push(m.get("value")); + } + } + ); + return values.toString(); + }, + toRaw: function (formattedData, model) { + formattedData.each(function(m){ + m.set("value", parseInt(m.get('value')), {silent: true}); + }); + + return formattedData; + } + }); + + /* + * InputStringArrayCell for rendering and taking input for string array type in debugger + */ + var InputStringArrayCell = Backgrid.Extension.InputStringArrayCell = Backgrid.Cell.extend({ + className: "width_percent_25", + formatter: InputStringArrayCellFormatter, + editor: InputArrayCellEditor, + + initialize: function() { + Backgrid.Cell.prototype.initialize.apply(this, arguments); + // set value to empty array. + if (_.isUndefined(this.collection)) { + this.collection = new (Backbone.Collection.extend({ + model: arrayCellModel})); + } + + this.model.set(this.column.get('name'), this.collection); + + this.listenTo(this.collection, "remove", this.render); + }, + }); + + /* + * InputIntegerArrayCell for rendering and taking input for integer array type in debugger + */ + var InputIntegerArrayCell = Backgrid.Extension.InputIntegerArrayCell = Backgrid.Cell.extend({ + className: "width_percent_25", + formatter: InputIntegerArrayCellFormatter, + editor: InputArrayCellEditor, + + initialize: function() { + Backgrid.Cell.prototype.initialize.apply(this, arguments); + // set value to empty array. + if (_.isUndefined(this.collection)) { + this.collection = new (Backbone.Collection.extend({ + model: arrayCellModel})); + } + + + this.model.set(this.column.get('name'),this.collection); + + this.listenTo(this.collection, "remove", this.render); + }, + }); + return Backgrid; })); diff --git a/web/pgadmin/static/js/codemirror/addon/selection/active-line.js b/web/pgadmin/static/js/codemirror/addon/selection/active-line.js index 22da2e0..421cef5 100644 --- a/web/pgadmin/static/js/codemirror/addon/selection/active-line.js +++ b/web/pgadmin/static/js/codemirror/addon/selection/active-line.js @@ -9,9 +9,9 @@ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); + mod(require("codemirror")); else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); + define(["codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py new file mode 100644 index 0000000..0bd9904 --- /dev/null +++ b/web/pgadmin/tools/debugger/__init__.py @@ -0,0 +1,1088 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""A blueprint module implementing the debugger""" + +MODULE_NAME = 'debugger' + +import json +import random +from flask import url_for, Response, render_template, request, make_response, jsonify, session +from pgadmin.utils import PgAdminModule +from pgadmin.utils.ajax import bad_request +from flask.ext.babel import gettext +from flask.ext.security import login_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.utils.menu import Panel + +# Constants +ASYNC_OK = 1 + + +class DebuggerModule(PgAdminModule): + """ + class DebuggerModule(PgAdminModule) + + A module class for debugger which is derived from PgAdminModule. + + Methods: + ------- + * get_own_javascripts(self) + - Method is used to load the required javascript files for debugger module + + """ + + def get_own_javascripts(self): + scripts = list() + for name, script in [ + ['pgadmin.tools.debugger.controller', 'js/debugger'], + ['pgadmin.tools.debugger.ui', 'js/debugger_ui'], + ['pgadmin.tools.debugger.direct', 'js/direct'] + ]: + scripts.append({ + 'name': name, + 'path': url_for('debugger.index') + script, + 'when': None + }) + scripts.append({ + 'name': 'codemirror.activeline', + 'path': url_for('static', filename='js/codemirror/addon/selection/active-line'), + 'deps': ['codemirror'], + 'exports': 'CodeMirror.addon.selection.active-line', + 'preloaded': True + }) + + return scripts + +blueprint = DebuggerModule(MODULE_NAME, __name__) + + +@blueprint.route("/") +@login_required +def index(): + return bad_request(errormsg=gettext("This URL can not be called directly!")) + + +@blueprint.route("/js/debugger.js") +@login_required +def script(): + """render the main debugger javascript file""" + return Response(response=render_template("debugger/js/debugger.js", _=gettext), + status=200, + mimetype="application/javascript") + + +@blueprint.route("/js/debugger_ui.js") +@login_required +def script_debugger_js(): + """render the debugger UI javascript file""" + return Response(response=render_template("debugger/js/debugger_ui.js", _=gettext), + status=200, + mimetype="application/javascript") + + +@blueprint.route("/js/direct.js") +@login_required +def script_debugger_direct_js(): + """render the javascript file required send and receive the response from server for debugging""" + return Response(response=render_template("debugger/js/direct.js", _=gettext), + status=200, + mimetype="application/javascript") + + +@blueprint.route('/init/function////', methods=['GET']) +@login_required +def init_function(sid, did, scid, fid): + """ + init_function(sid, did, scid, fid) + + 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. + This is only required for direct debugging. As Indirect debugging does not require these data because user will + provide all the arguments and other functions information through another session to invoke the target. + + Parameters: + sid + - Server Id + did + - Database Id + scid + - Schema Id + fid + - Function Id + """ + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection(did=did) + + # Get the server version, server type and user information + ver = manager.version + server_type = manager.server_type + user = manager.user_info + + # Check server type is ppas or not + ppas_server = False + if server_type == 'ppas': + ppas_server = True; + + # Set the template path required to read the sql files + template_path = 'debugger/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) + if not status: + return internal_server_error(errormsg=r_set) + + ret_status = status + + # Check the condition that function is actually debuggable or not.... + if r_set['rows'][0]: + # Function with a colon in the name cannot be debugged. + # If this is an EDB wrapped function, no debugging allowed + # Function with return type "trigger" can not be debugged. + if ":" in r_set['rows'][0]['name']: + ret_status = False + elif r_set['rows'][0]['rettype'] == 'trigger': + ret_status = False + elif ppas_server and r_set['rows'][0]['prosrc'].lstrip().startswith('$__EDBwrapped__$'): + ret_status = False + else: + # If user is super user then we should check debugger library is loaded or not + if user['is_superuser']: + status_in, rid_pre = conn.execute_scalar("SHOW shared_preload_libraries") + if not status_in: + return internal_server_error("ERROR: Couldn't fetch debugger plugin information") + + # Need to check if plugin is really loaded or not with "plugin_debugger" string + if rid_pre: + if "plugin_debugger" not in rid_pre: + ret_status = False + + status_in, rid_tar = conn.execute_scalar(" SELECT count(*) FROM pg_proc WHERE proname = 'pldbg_get_target_info' ") + if not status_in: + return internal_server_error("ERROR: Couldn't fetch debugger target information") + + if rid_tar == 0: + ret_status = False + else: + ret_status = False + + # Return the response that function can not be debug... + if not ret_status: + return internal_server_error("ERROR: Function can not be debug.") + + # Store the function information in session variable + if 'functionData' not in session: + function_data = dict() + else: + function_data = session['functionData'] + + function_data = { + 'oid': fid, + 'name': r_set['rows'][0]['name'], + 'is_func': r_set['rows'][0]['isfunc'], + 'is_callable': False, + 'schema': r_set['rows'][0]['schemaname'], + 'language': r_set['rows'][0]['lanname'], + 'return_type': r_set['rows'][0]['rettype'], + 'args_type': r_set['rows'][0]['proargtypenames'], + 'args_name': r_set['rows'][0]['proargnames'], + 'arg_mode': r_set['rows'][0]['proargmodes'], + 'args_value': '' + } + + session['functionData'] = function_data; + + data = {} + data['name'] = r_set['rows'][0]['proargnames'] + data['type'] = r_set['rows'][0]['proargtypenames'] + data['use_default'] = r_set['rows'][0]['pronargdefaults'] + data['default_value'] = r_set['rows'][0]['proargdefaults'] + data['require_input'] = True + + # Below will check do we really required for the user input arguments and show input dialog + if not r_set['rows'][0]['proargtypenames']: + data['require_input'] = False + else: + if r_set['rows'][0]['pkg'] != 0 and r_set['rows'][0]['pkgconsoid'] != 0: + data['require_input'] = True + + if r_set['rows'][0]['proargmodes']: + pro_arg_modes = r_set['rows'][0]['proargmodes'].split(","); + for pr_arg_mode in pro_arg_modes: + if pr_arg_mode == 'o' or pr_arg_mode == 't': + data['require_input'] = False + continue; + else: + data['require_input'] = True + break; + + r_set['rows'][0]['require_input'] = data['require_input'] + + return make_json_response( + data=r_set['rows'], + status=200 + ) + + +@blueprint.route('/direct/', methods=['GET']) +@login_required +def direct_new(trans_id): + debugger_data = session['debuggerData'] + # Return from the function if transaction id not found + if str(trans_id) not in debugger_data: + return make_json_response(data={'status': True}) + + obj = debugger_data[str(trans_id)] + + # if indirect debugging pass value 0 to client and for direct debugging pass it to 1 + debug_type = 0 if obj['debug_type'] == 'indirect' else 1 + + return render_template( + "debugger/direct.html", + _=gettext, + function_name='test', + uniqueId=trans_id, + debug_type=debug_type, + stylesheets=[url_for('debugger.static', filename='css/debugger.css')] + ) + + +@blueprint.route('/initialize_target/////', methods=['GET', 'POST']) +@login_required +def initialize_target(debug_type, sid, did, scid, func_id): + """ + initialize_target(debug_type, sid, did, scid, func_id) + + This method is responsible for creating an asynchronous connection. + It will also create a unique transaction id and store the information + into session variable. + + Parameters: + debug_type + - Type of debugging (Direct or Indirect) + sid + - Server Id + did + - Database Id + scid + - Schema Id + func_id + - Function Id + """ + + # Create asynchronous connection using random connection id. + conn_id = str(random.randint(1, 9999999)) + try: + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection(did=did, conn_id=conn_id) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + # Connect the Server + status, msg = conn.connect() + if not status: + return internal_server_error(errormsg=str(msg)) + + # Create a unique id for the transaction + trans_id = str(random.randint(1, 9999999)) + + # If there is no debugging information in session variable then create the store that information + if 'debuggerData' not in session: + debugger_data = dict() + else: + debugger_data = session['debuggerData'] + + status = True; + + # Find out the debugger version and store it in session variables + status, rid = conn.execute_scalar( + "SELECT COUNT(*) FROM pg_catalog.pg_proc p \ + LEFT JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid \ + WHERE n.nspname = ANY(current_schemas(false)) AND p.proname = 'pldbg_get_proxy_info';" + ) + + if not status: + return internal_server_error(errormsg=rid) + else: + if rid == 0: + debugger_version = 1 + else: + status, rid = conn.execute_scalar( + "SELECT proxyapiver FROM pldbg_get_proxy_info();" + ) + if rid == 2 or rid == 3: + debugger_version = rid; + else: + status = False; + + # We need to pass the value entered by the user in dialog for direct debugging + # Here we get the value in case of direct debugging so update the session variables accordingly + # For indirect debugging user will provide the data from another session so below condition will be be required + if request.method == 'POST': + data = json.loads(request.values['data']) + if data: + d = session['functionData'] + d['args_value'] = data + session['functionData'] = d + + # Update the debugger data session variable + # Here frame_id is required when user debug the multilevel function. When user select the frame from client we + # need to update the frame here and set the breakpoint information on that function oid + debugger_data[str(trans_id)] = { + 'conn_id': conn_id, + 'server_id': sid, + 'database_id': did, + 'function_id': func_id, + 'debug_type': debug_type, + 'debugger_version': debugger_version, + 'frame_id': 0 + } + + # Store the grid dictionary into the session variable + session['debuggerData'] = debugger_data + + return make_json_response(data={'status': status, 'debuggerTransId': trans_id}) + + +@blueprint.route('/close/', methods=["GET"]) +def close(trans_id): + """ + close(trans_id) + + This method is used to close the asynchronous connection + and remove the information of unique transaction id from + the session variable. + + Parameters: + trans_id + - unique transaction id. + """ + # As debugger data is not in session that means we have already deleted and close the target + if 'debuggerData' not in session: + return make_json_response(data={'status': True}) + + debugger_data = session['debuggerData'] + # Return from the function if transaction id not found + if str(trans_id) not in debugger_data: + return make_json_response(data={'status': True}) + + obj = debugger_data[str(trans_id)] + try: + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + conn = manager.connection(did=obj['database_id'], conn_id=obj['conn_id']) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + # find the debugger version and execute the query accordingly + dbg_version = obj['debugger_version'] + if dbg_version <= 2: + template_path = 'debugger/sql/v1' + else: + template_path = 'debugger/sql/v2' + + # Release the connection + if conn.connected(): + if obj['debug_type'] == 'indirect': + # render the SQL template and send the query to server + sql = render_template("/".join([template_path, 'abort_target.sql']), + session_id=obj['session_id']) + status, res = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + # Delete the existing debugger data in session variable + del debugger_data[str(trans_id)] + del session['debuggerData'][str(trans_id)] + + # Release the connection acquired during the debugging + manager.release(did=obj['database_id'], conn_id=obj['conn_id']) + return make_json_response(data={'status': True}) + else: + return make_json_response(data={'status': False}) + + +@blueprint.route('/start_listener/', methods=['GET']) +@login_required +def start_debugger_listener(trans_id): + """ + start_debugger_listener(trans_id) + + This method is responsible to listen and get the required information requested by user during debugging + + Parameters: + trans_id + - Transaction ID + """ + + debugger_data = session['debuggerData'] + if str(trans_id) not in debugger_data: + return make_json_response( + data={'status': False, + 'result': gettext('Not connected to server Or connection with the server has been closed.')} + ) + obj = debugger_data[str(trans_id)] + + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + conn = manager.connection(did=obj['database_id'], conn_id=obj['conn_id']) + + ver = manager.version + server_type = manager.server_type + + # find the debugger version and execute the query accordingly + dbg_version = obj['debugger_version'] + if dbg_version <= 2: + template_path = 'debugger/sql/v1' + else: + template_path = 'debugger/sql/v2' + + if conn.connected(): + + # For the direct debugging extract the function arguments values from user and pass to jinja template + # to create the query for execution. + if obj['debug_type'] == 'direct': + str_query = '' + + # Form the function name with schema name + func_name = session['functionData']['schema'] + '.' + session['functionData']['name'] + + # render the SQL template and send the query to server + sql = render_template("/".join([template_path, 'debug_plpgsql_execute_target.sql']), + function_oid=obj['function_id']) + status, res = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + if session['functionData']['arg_mode']: + # In EDBAS 90, if an SPL-function has both an OUT-parameter + # and a return value (which is not possible on PostgreSQL otherwise), + # the return value is transformed into an extra OUT-parameter + # named "_retval_" + if session['functionData']['args_name']: + arg_name = session['functionData']['args_name'].split(","); + if '_retval_' in arg_name: + arg_mode = session['functionData']['arg_mode'].split(","); + arg_mode.pop(); + else: + arg_mode = session['functionData']['arg_mode'].split(","); + else: + arg_mode = session['functionData']['arg_mode'].split(","); + else: + arg_mode = ['i'] * len(session['functionData']['args_type'].split(",")); + + if session['functionData']['args_type']: + if session['functionData']['args_name']: + arg_name = session['functionData']['args_name'].split(","); + if '_retval_' in arg_name: + arg_type = session['functionData']['args_type'].split(","); + arg_type.pop(); + else: + arg_type = session['functionData']['args_type'].split(","); + else: + arg_type = session['functionData']['args_type'].split(","); + + # Below are two different template to execute and start executer + if manager.server_type != 'pg' and manager.version < 90300: + str_query = render_template("/".join(['debugger/sql', 'execute_edbspl.sql']), + func_name=func_name, is_func=session['functionData']['is_func'], + lan_name=session['functionData']['language'], + ret_type=session['functionData']['return_type'], + data=session['functionData']['args_value'], + arg_type=arg_type, + args_mode=arg_mode + ) + else: + str_query = render_template("/".join(['debugger/sql', 'execute_plpgsql.sql']), + func_name=func_name, is_func=session['functionData']['is_func'], + ret_type=session['functionData']['return_type'], + data=session['functionData']['args_value'] + ) + + status, result = conn.execute_async(str_query) + if not status: + return internal_server_error(errormsg=result) + else: + if conn.connected(): + # For indirect debugging first create the listener and then wait for the target + sql = render_template("/".join([template_path, 'create_listener.sql'])) + + status, res = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + # Get and store the session variable which is required to fetch other information during debugging + int_session_id = res['rows'][0]['pldbg_create_listener'] + + # In EnterpriseDB versions <= 9.1 the + # pldbg_set_global_breakpoint function took five arguments, + # the 2nd argument being the package's OID, if any. Starting + # with 9.2, the package OID argument is gone, and the function + # takes four arguments like the community version has always + # done. + if server_type == 'ppas' and ver <= 90100: + sql = render_template("/".join([template_path, 'add_breakpoint_edb.sql']), + session_id=int_session_id, + function_oid=obj['function_id']) + + status, res = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + else: + sql = render_template("/".join([template_path, 'add_breakpoint_pg.sql']), + session_id=int_session_id, + function_oid=obj['function_id']) + + status, res = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + # wait for the target + sql = render_template("/".join([template_path, 'wait_for_target.sql']), + session_id=int_session_id) + + status, res = conn.execute_async(sql) + if not status: + return internal_server_error(errormsg=res) + + # Delete the existing debugger data in session variable and update with new data + del debugger_data[str(trans_id)] + del session['debuggerData'][str(trans_id)] + + if 'debuggerData' not in session: + debugger_data = dict() + else: + debugger_data = session['debuggerData'] + + debugger_data[str(trans_id)] = { + 'conn_id': obj['conn_id'], + 'server_id': obj['server_id'], + 'database_id': obj['database_id'], + 'function_id': obj['function_id'], + 'debug_type': obj['debug_type'], + 'exe_conn_id': obj['conn_id'], + 'debugger_version': obj['debugger_version'], + 'session_id': int_session_id, + 'frame_id': 0 + } + + # Store the dictionary into the session variable + session['debuggerData'] = debugger_data + return make_json_response(data={'status': status, 'result': res}) + else: + status = False + result = gettext('Not connected to server Or connection with the server has been closed.') + return make_json_response(data={'status': status, 'result': result}) + else: + status = False + result = gettext('Not connected to server Or connection with the server has been closed.') + + return make_json_response(data={'status': status, 'result': result}) + + +@blueprint.route('/execute_query//', methods=['GET']) +@login_required +def execute_debugger_query(trans_id, query_type): + """ + execute_debugger_query(trans_id, query_type) + + This method is responsible to execute the query and return value. As this method is generic so user has to pass the + query_type to get the required information for debugging. + + e.g. If user want to execute 'step_into' then query_type='step_into'. + If user want to execute 'continue' then query_type='continue' + + Parameters: + trans_id + - Transaction ID + query_type + - Type of query to execute + """ + + debugger_data = session['debuggerData'] + if str(trans_id) not in debugger_data: + return make_json_response( + data={'status': False, + 'result': gettext('Not connected to server Or connection with the server has been closed.')} + ) + obj = debugger_data[str(trans_id)] + + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + conn = manager.connection(did=obj['database_id'], conn_id=obj['exe_conn_id']) + + # find the debugger version and execute the query accordingly + dbg_version = obj['debugger_version'] + if dbg_version <= 2: + template_path = 'debugger/sql/v1' + else: + template_path = 'debugger/sql/v2' + + if conn.connected(): + sql = render_template("/".join([template_path, query_type + ".sql"]), session_id=obj['session_id']) + # As the query type is continue or step_into or step_over then we may get result after some time so poll the + # result. We need to update the frame id variable when user move the next step for debugging. + if query_type == 'continue' or query_type == 'step_into' or query_type == 'step_over': + status, result = conn.execute_async(sql) + return make_json_response(data={'status': status, 'result': result}) + elif query_type == 'abort_target': + status, result = conn.execute_dict(sql) + + manager.release(did=obj['database_id'], conn_id=obj['conn_id']) + + # Delete the existing debugger data in session variable + del debugger_data[str(trans_id)] + del session['debuggerData'][str(trans_id)] + + if not status: + return internal_server_error(errormsg=result) + else: + return make_json_response(info='Target Aborted.', data={'status': status, 'result': result}) + else: + status, result = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=result) + else: + status = False + result = gettext('Not connected to server Or connection with the server has been closed.') + + return make_json_response(data={'status': 'Success', 'result': result['rows']}) + + +@blueprint.route('/messages//', methods=["GET"]) +@login_required +def messages(trans_id): + """ + messages(trans_id) + + This method polls the messages returned by the database server. + + Parameters: + trans_id + - unique transaction id. + """ + + debugger_data = session['debuggerData'] + if str(trans_id) not in debugger_data: + return make_json_response( + data={'status': 'NotConnected', + 'result': gettext('Not connected to server Or connection with the server has been closed.')} + ) + obj = debugger_data[str(trans_id)] + + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + conn = manager.connection(did=obj['database_id'], conn_id=obj['conn_id']) + + port_number = '' + + if conn.connected(): + status, result, my_result = conn.poll() + notify = conn.messages() + if notify: + # In notice message we need to find "PLDBGBREAK" string to find the port number to attach. + offset = notify[0].find('PLDBGBREAK'); + str_len = len('PLDBGBREAK') + str_len = str_len + 1 + tmpOffset = 0 + tmpFlag = False + + while notify[0][offset+str_len+tmpOffset].isdigit(): + status = 'Success' + tmpFlag = True + port_number = port_number + notify[0][offset+str_len+tmpOffset] + tmpOffset = tmpOffset + 1 + + if tmpFlag == False: + status = 'Busy' + else: + status = 'Busy' + else: + status = 'NotConnected' + result = gettext('Not connected to server Or connection with the server has been closed.') + + return make_json_response(data={'status': status, 'result': port_number}) + + +@blueprint.route('/start_execution//', methods=['GET']) +@login_required +def start_execution(trans_id, port_num): + """ + start_execution(trans_id, port_num) + + This method is responsible for creating an asynchronous connection for execution thread. + Also store the session id into session return with attach port query for the direct debugging. + + Parameters: + trans_id + - Transaction ID + port_num + - Port number to attach + """ + + debugger_data = session['debuggerData'] + if str(trans_id) not in debugger_data: + return make_json_response( + data={'status': 'NotConnected', + 'result': gettext('Not connected to server Or connection with the server has been closed.')} + ) + obj = debugger_data[str(trans_id)] + + dbg_version = obj['debugger_version'] + + # Delete the existing debugger data in session variable + del debugger_data[str(trans_id)] + del session['debuggerData'][str(trans_id)] + + # Create asynchronous connection using random connection id. + exe_conn_id = str(random.randint(1, 9999999)) + try: + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + conn = manager.connection(did=obj['database_id'], conn_id=exe_conn_id) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + # Connect the Server + status, msg = conn.connect() + if not status: + return internal_server_error(errormsg=str(msg)) + + if 'debuggerData' not in session: + debugger_data = dict() + else: + debugger_data = session['debuggerData'] + + # find the debugger version and execute the query accordingly + dbg_version = obj['debugger_version'] + if dbg_version <= 2: + template_path = 'debugger/sql/v1' + else: + template_path = 'debugger/sql/v2' + + # connect to port and store the session ID in the session variables + sql = render_template("/".join([template_path, 'attach_to_port.sql']), port=port_num) + status_port, res_port = conn.execute_dict(sql) + if not status_port: + return internal_server_error(errormsg=res_port) + + debugger_data[str(trans_id)] = { + 'conn_id': obj['conn_id'], + 'server_id': obj['server_id'], + 'database_id': obj['database_id'], + 'function_id': obj['function_id'], + 'debug_type': obj['debug_type'], + 'exe_conn_id': exe_conn_id, + 'debugger_version': dbg_version, + 'session_id': res_port['rows'][0]['pldbg_attach_to_port'] + } + + # Store the debugger data dictionary into the session variable + session['debuggerData'] = debugger_data + + return make_json_response(data={'status': 'Success', 'result': res_port['rows'][0]['pldbg_attach_to_port']}) + + +@blueprint.route('/set_breakpoint///', methods=['GET']) +@login_required +def set_clear_breakpoint(trans_id, line_no, set_type): + """ + set_clear_breakpoint(trans_id, line_no, set_type) + + This method is responsible to set and clean the breakpoint + + Parameters: + trans_id + - Transaction ID + line_no + - Line number to set + set_type + - 0 - clear the breakpoint, 1 - set the breakpoint + """ + + debugger_data = session['debuggerData'] + if str(trans_id) not in debugger_data: + return make_json_response( + data={'status': False, + 'result': gettext('Not connected to server Or connection with the server has been closed.')} + ) + obj = debugger_data[str(trans_id)] + + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + conn = manager.connection(did=obj['database_id'], conn_id=obj['exe_conn_id']) + + # find the debugger version and execute the query accordingly + dbg_version = obj['debugger_version'] + if dbg_version <= 2: + template_path = 'debugger/sql/v1' + else: + template_path = 'debugger/sql/v2' + + query_type = '' + + # We need to find out function OID before sending the foid to set the breakpoint because it may possible that + # debugging function has multi level function for debugging so we need to save the debug level to session variable + # and pass tha appropriate foid to set the breakpoint. + sql_ = render_template("/".join([template_path, "get_stack_info.sql"]), session_id=obj['session_id']) + status, res_stack = conn.execute_dict(sql_) + if not status: + return internal_server_error(errormsg=res_stack) + + foid = res_stack['rows'][0]['func'] + + # Check the result of the stack before setting the breakpoint + if conn.connected(): + if set_type == 1: + query_type = 'set_breakpoint' + else: + query_type = 'clear_breakpoint' + + sql = render_template("/".join([template_path, query_type + ".sql"]), session_id=obj['session_id'], + foid=foid, line_number=line_no) + + status, result = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=result) + else: + status = False + result = gettext('Not connected to server Or connection with the server has been closed.') + + return make_json_response(data={'status': status, 'result': result['rows']}) + + +@blueprint.route('/clear_all_breakpoint/', methods=['POST']) +@login_required +def clear_all_breakpoint(trans_id): + """ + clear_all_breakpoint(trans_id) + + This method is responsible to clear all the breakpoint + + Parameters: + trans_id + - Transaction ID + """ + debugger_data = session['debuggerData'] + if str(trans_id) not in debugger_data: + return make_json_response( + data={'status': False, + 'result': gettext('Not connected to server Or connection with the server has been closed.')} + ) + obj = debugger_data[str(trans_id)] + + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + conn = manager.connection(did=obj['database_id'], conn_id=obj['exe_conn_id']) + + # find the debugger version and execute the query accordingly + dbg_version = obj['debugger_version'] + if dbg_version <= 2: + template_path = 'debugger/sql/v1' + else: + template_path = 'debugger/sql/v2' + + query_type = '' + + if conn.connected(): + # get the data sent through post from client + if request.form['breakpoint_list']: + line_numbers = request.form['breakpoint_list'].split(",") + for line_no in line_numbers: + sql = render_template("/".join([template_path, "clear_breakpoint.sql"]), session_id=obj['session_id'], + foid=obj['function_id'], line_number=line_no) + + status, result = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=result) + else: + return make_json_response(data={'status': False}) + else: + status = False + result = gettext('Not connected to server Or connection with the server has been closed.') + + return make_json_response(data={'status': status, 'result': result['rows']}) + + +@blueprint.route('/deposit_value/', methods=['POST']) +@login_required +def deposit_parameter_value(trans_id): + """ + deposit_parameter_value(trans_id) + + This method is responsible to change the value of variables + + Parameters: + trans_id + - Transaction ID + """ + debugger_data = session['debuggerData'] + if str(trans_id) not in debugger_data: + return make_json_response( + data={'status': False, + 'result': gettext('Not connected to server Or connection with the server has been closed.')} + ) + obj = debugger_data[str(trans_id)] + + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + conn = manager.connection(did=obj['database_id'], conn_id=obj['exe_conn_id']) + + # find the debugger version and execute the query accordingly + dbg_version = obj['debugger_version'] + if dbg_version <= 2: + template_path = 'debugger/sql/v1' + else: + template_path = 'debugger/sql/v2' + + query_type = '' + + if conn.connected(): + # get the data sent through post from client + data = json.loads(request.values['data']) + + if data: + sql = render_template("/".join([template_path, "deposit_value.sql"]), session_id=obj['session_id'], + var_name=data[0]['name'], line_number=-1, val=data[0]['value']) + + status, result = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=result) + else: + status = False + result = gettext('Not connected to server Or connection with the server has been closed.') + + return make_json_response(data={'status': status, 'result': result['rows']}) + + +@blueprint.route('/select_frame//', methods=['GET']) +@login_required +def select_frame(trans_id, frame_id): + """ + select_frame(trans_id, frame_id) + + This method is responsible to select the frame from stack info + + Parameters: + trans_id + - Transaction ID + frame_id + - Frame id selected + """ + debugger_data = session['debuggerData'] + if str(trans_id) not in debugger_data: + return make_json_response( + data={'status': False, + 'result': gettext('Not connected to server Or connection with the server has been closed.')} + ) + obj = debugger_data[str(trans_id)] + + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + conn = manager.connection(did=obj['database_id'], conn_id=obj['exe_conn_id']) + + # find the debugger version and execute the query accordingly + dbg_version = obj['debugger_version'] + if dbg_version <= 2: + template_path = 'debugger/sql/v1' + else: + template_path = 'debugger/sql/v2' + + if conn.connected(): + sql = render_template("/".join([template_path, "select_frame.sql"]), session_id=obj['session_id'], + frame_id=frame_id) + + status, result = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=result) + else: + status = False + result = gettext('Not connected to server Or connection with the server has been closed.') + + return make_json_response(data={'status': status, 'result': result['rows']}) + + +@blueprint.route('/poll_end_execution_result//', methods=["GET"]) +@login_required +def poll_end_execution_result(trans_id): + """ + poll_end_execution_result(trans_id) + + This method polls the end of execution result messages returned by the database server. + + Parameters: + trans_id + - unique transaction id. + """ + + debugger_data = session['debuggerData'] + if str(trans_id) not in debugger_data: + return make_json_response( + data={'status': 'NotConnected', + 'result': gettext('Not connected to server Or connection with the server has been closed.')} + ) + obj = debugger_data[str(trans_id)] + + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + if obj['debug_type'] == 'direct': + conn = manager.connection(did=obj['database_id'], conn_id=obj['conn_id']) + else: + conn = manager.connection(did=obj['database_id'], conn_id=obj['exe_conn_id']) + + if conn.connected(): + statusmsg = conn.status_message() + status, result, my_result = conn.poll() + if result: + status = 'Success' + data = {} + for i in result: + for k, v in i.items(): + data["name"] = k + data.setdefault("value",[]).append(v) + + return make_json_response(success=1, info=gettext("Execution Completed."), + data={'status': status, 'result': data, 'status_message': statusmsg}) + else: + status = 'Busy' + else: + status = 'NotConnected' + result = gettext('Not connected to server Or connection with the server has been closed.') + + return make_json_response(data={'status': status, 'result': result}) + + +@blueprint.route('/poll_result//', methods=["GET"]) +@login_required +def poll_result(trans_id): + """ + poll_result(trans_id) + + This method polls the result of the asynchronous query and returns the result. + + Parameters: + trans_id + - unique transaction id. + """ + + debugger_data = session['debuggerData'] + if str(trans_id) not in debugger_data: + return make_json_response( + data={'status': 'NotConnected', + 'result': gettext('Not connected to server Or connection with the server has been closed.')} + ) + obj = debugger_data[str(trans_id)] + + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + conn = manager.connection(did=obj['database_id'], conn_id=obj['exe_conn_id']) + + if conn.connected(): + status, result, my_result = conn.poll() + if status == ASYNC_OK: + status = 'Success' + else: + status = 'Busy' + else: + status = 'NotConnected' + result = gettext('Not connected to server Or connection with the server has been closed.') + + return make_json_response(data={'status': status, 'result': result}) diff --git a/web/pgadmin/tools/debugger/static/css/debugger.css b/web/pgadmin/tools/debugger/static/css/debugger.css new file mode 100644 index 0000000..b3a577a --- /dev/null +++ b/web/pgadmin/tools/debugger/static/css/debugger.css @@ -0,0 +1,64 @@ +.navbar-static-top, .navbar-fixed-top, .navbar-fixed-bottom { + background-image: linear-gradient(to bottom, #CCC 0%, #D2D2D2 100%); +} + +.btn-default { + background-color: #D2D2D2; + left: 0px; + right: 0px; + padding: 7px; +} + +.btn-toolbar { + padding-top: 3px; + padding-bottom: 3px; +} + +#container { + position: absolute; + top: 44px; + bottom: 0px; + left: 0px; + right: 0px; +} + +.full-container { + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom:0px; +} + +.full-container-pane { + width: 100%; + height: 100%; +} + +.top-container { + min-height: 300px; + height: 100%; +} + +.pg-debugger-panel { + height: 100%; + width: 100%; + position: absolute; + top: 0; bottom: 0; right: 0; left: 0; +} + +.wcLoadingIcon { + position: absolute; + font-size: 100px; + left: calc(50% - 100px); + top: calc(50% - 100px); + height: 95px; +} + +.wcLoadingLabel { + position: absolute; + width: 103%; + font-size: 30px; + top: calc(50% + 0px); + text-align: center; +} diff --git a/web/pgadmin/tools/debugger/templates/debugger/direct.html b/web/pgadmin/tools/debugger/templates/debugger/direct.html new file mode 100644 index 0000000..02d9803 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/direct.html @@ -0,0 +1,63 @@ +{% extends "base.html" %} +{% block title %}{{ _('Debugger - ') + function_name }}{% endblock %} +{% block init_script %} +try { + require( + ['pgadmin', 'pgadmin.tools.debugger.direct'], + function(pgAdmin, pgDirectDebug) { + pgDirectDebug.init({{ uniqueId }}, {{ debug_type }}); + }, + function() { + /* TODO:: Show proper error dialog */ + console.log(arguments); + }); +} catch (err) { + /* Show proper error dialog */ + console.log(err); +} +{% endblock %} +{% block body %} + + +
+{% endblock %} + + +{% block css_link %} +{% for stylesheet in stylesheets %} + +{% endfor %} +{% endblock %} diff --git a/web/pgadmin/tools/debugger/templates/debugger/js/debugger.js b/web/pgadmin/tools/debugger/templates/debugger/js/debugger.js new file mode 100644 index 0000000..fa1b6c1 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/js/debugger.js @@ -0,0 +1,305 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin', + 'pgadmin.browser', 'backbone', 'backgrid', 'codemirror', 'backform', + 'pgadmin.tools.debugger.ui', 'wcdocker', 'pgadmin.backform', + 'pgadmin.backgrid', 'pgadmin.browser.frame'], + function($, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, CodeMirror, Backform, get_function_arguments) { + + pgAdmin = pgAdmin || window.pgAdmin || {}; + + var pgTools = pgAdmin.Tools = pgAdmin.Tools || {}; + + /* Return back, this has been called more than once */ + if (pgAdmin.Tools.Debugger) + return pgAdmin.Tools.Debugger; + + pgTools.Debugger = { + init: function() { + // We do not want to initialize the module multiple times. + if (this.initialized) + return; + + this.initialized = true; + + // Initialize the context menu to display the debugging options when user open the context menu for functions + pgBrowser.add_menus([{ + name: 'direct_debugger', node: 'function', module: this, + applies: ['object', 'context'], callback: 'get_function_information', + category: 'Debugging', priority: 10, label: '{{ _('Debug') }}', + data: {object: 'function'}, icon: 'fa fa-arrow-circle-right', + enable: 'can_debug' + },{ + name: 'global_debugger', node: 'function', module: this, + applies: ['object', 'context'], callback: 'check_func_debuggable', + category: 'Debugging', priority: 10, label: '{{ _('Set breakpoint') }}', + data: {object: 'function'}, icon: 'fa fa-arrow-circle-right', + enable: 'can_debug' + }]); + + // Create and load the new frame required for debugger panel + this.frame = new pgBrowser.Frame({ + name: 'frm_debugger', + title: '{{ _('Debugger') }}', + width: 500, + isCloseable: true, + isPrivate: true, + url: 'about:blank' + }); + + this.frame.load(pgBrowser.docker); + }, + // It will check weather the function is actually debuggable or not with pre-required condition. + can_debug: function(itemData, item, data) { + var t = pgBrowser.tree, i = item, d = itemData; + // To iterate over tree to check parent node + while (i) { + if ('catalog' == d._type) { + //Check if we are not child of catalog + return false; + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // Find the function is really available in database + var tree = pgBrowser.tree, + info = tree.selected(), + d_ = info && info.length == 1 ? tree.itemData(info) : undefined, + node = d_ && pgBrowser.Nodes[d_._type]; + + if (!d_) + return false; + + var treeInfo = node.getTreeNodeHierarchy.apply(node, [info]); + + // Must be a super user or object owner to create breakpoints of any kind + if (!(treeInfo.server.user.is_superuser || treeInfo.function.funcowner == treeInfo.server.user.name)) + return false; + + if (d_.language != 'plpgsql' && d_.language != 'edbspl') { + return false; + } + + return true; + }, + /* + For the direct debugging, we need to fetch the function information to display in the dialog so "generate_url" + will dynamically generate the URL from the server_id, database_id, schema_id and function id. + */ + generate_url: function(_url, treeInfo, node) { + var url = '{BASEURL}{URL}/{OBJTYPE}{REF}', + ref = ''; + + _.each( + _.sortBy( + _.values( + _.pick(treeInfo, + function(v, k, o) { + return (k != 'server-group'); + }) + ), + function(o) { return o.priority; } + ), + function(o) { + ref = S('%s/%s').sprintf(ref, encodeURI(o._id)).value(); + }); + + var args = { + 'URL': _url, + 'BASEURL': '{{ url_for('debugger.index')}}', + 'REF': ref, + 'OBJTYPE': encodeURI(node.type) + }; + + return url.replace(/{(\w+)}/g, function(match, arg) { + return args[arg]; + }); + }, + + check_func_debuggable: function(args, item) { + var input = args || {}, + t = pgBrowser.tree, + i = item || t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined, + node = d && pgBrowser.Nodes[d._type]; + + if (!d) + return; + + var objName = d.label, + treeInfo = node.getTreeNodeHierarchy.apply(node, [i]), + _url = this.generate_url('init', treeInfo, node); + + var self = this; + $.ajax({ + url: _url, + cache: false, + success: function(res) { + self.start_global_debugger(); + }, + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + if (err.success == 0) { + msg = S('{{ _(' + err.errormsg + ')}}').value(); + Alertify.alert("{{ _('" + err.errormsg + "') }}"); + } + } catch (e) {} + } + }); + }, + + //Callback function when user start the indirect debugging ( Listen to another session to invoke the target ) + start_global_debugger: function(args, item) { + // Initialize the target and create asynchronous connection and unique transaction ID + var t = pgBrowser.tree, + i = t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined, + node = d && pgBrowser.Nodes[d._type]; + + if (!d) + return; + + var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]); + + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "indirect/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.function._id; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + var url = "{{ url_for('debugger.index') }}" + "direct/" + res.data.debuggerTransId; + + pgBrowser.Events.once( + 'pgadmin-browser:frame:urlloaded:frm_debugger', function(frame) { + frame.openURL(url); + }); + + // Create the debugger panel as per the data received from user input dialog. + var dashboardPanel = pgBrowser.docker.findPanels( + 'dashboard' + ), + panel = pgBrowser.docker.addPanel( + 'frm_debugger', wcDocker.DOCK.STACKED, dashboardPanel[0] + ); + + panel.focus(); + + // Panel Closed event + panel.on(wcDocker.EVENT.CLOSED, function() { + var closeUrl = "{{ url_for('debugger.index') }}" + "close/" + res.data.debuggerTransId; + $.ajax({ + url: closeUrl, + method: 'GET' + }); + }); + }, + error: function(e) { + Alertify.alert( + 'Debugger target Initialize Error' + ); + } + }); + }, + + /* + Get the function information for the direct debugging to display the functions arguments and other informations + in the user input dialog + */ + get_function_information: function(args, item) { + var input = args || {}, + t = pgBrowser.tree, + i = item || t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined, + node = d && pgBrowser.Nodes[d._type]; + + if (!d) + return; + + var objName = d.label, + treeInfo = node.getTreeNodeHierarchy.apply(node, [i]), + _url = this.generate_url('init', treeInfo, node); + + var self = this; + $.ajax({ + url: _url, + cache: false, + success: function(res) { + + // 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]); + } + 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 + // Directly open the panel + var t = pgBrowser.tree, + i = t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined, + node = d && pgBrowser.Nodes[d._type]; + + if (!d) + return; + + var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]); + + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "direct/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.function._id; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + + var url = "{{ url_for('debugger.index') }}" + "direct/" + res.data.debuggerTransId; + + pgBrowser.Events.once( + 'pgadmin-browser:frame:urlloaded:frm_debugger', function(frame) { + frame.openURL(url); + }); + + // Create the debugger panel as per the data received from user input dialog. + var dashboardPanel = pgBrowser.docker.findPanels( + 'dashboard' + ), + panel = pgBrowser.docker.addPanel( + 'frm_debugger', wcDocker.DOCK.STACKED, dashboardPanel[0] + ); + + panel.focus(); + + // Register Panel Closed event + panel.on(wcDocker.EVENT.CLOSED, function() { + var closeUrl = "{{ url_for('debugger.index') }}" + "close/" + res.data.debuggerTransId; + $.ajax({ + url: closeUrl, + method: 'GET' + }); + }); + }, + error: function(e) { + Alertify.alert( + 'Debugger target Initialize Error', + e.responseJSON.errormsg + ); + } + }); + } + }, + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + if (err.success == 0) { + msg = S('{{ _(' + err.errormsg + ')}}').value(); + Alertify.alert("{{ _('" + err.errormsg + "') }}"); + } + } catch (e) {} + } + }); + } + }; + + return pgAdmin.Tools.Debugger; + }); \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/js/debugger_ui.js b/web/pgadmin/tools/debugger/templates/debugger/js/debugger_ui.js new file mode 100644 index 0000000..57232b8 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/js/debugger_ui.js @@ -0,0 +1,414 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin', + 'pgadmin.browser', 'backbone', 'backgrid', 'codemirror', 'backform', + 'wcdocker', 'pgadmin.backform', 'pgadmin.backgrid', + 'pgadmin.browser.panel'], + function($, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, CodeMirror, Backform ) { + + /* + * Function used to return the respective Backgrid control based on the data type + * of function input argument. + */ + var cellFunction = function(model) { + var self = this, + variable_type = model.get("type"); + + // 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) { + return Backgrid.Extension.InputIntegerArrayCell; + } + return Backgrid.Extension.InputStringArrayCell; + } + + switch(variable_type) { + case "bool": + return Backgrid.BooleanCell; + break; + + case "integer": + return Backgrid.IntegerCell; + break; + + case "real": + return Backgrid.NumberCell; + break; + + case "string": + return Backgrid.StringCell; + break; + case "date": + return Backgrid.DateCell; + break; + default: + return Backgrid.Cell; + break; + } + } + + /* + * Function used to return the respective Backgrid string or boolean control based on the data type + * of function input argument. + */ + var cellExprControlFunction = function(model) { + var self = this, + variable_type = model.get("type"); + if (variable_type.indexOf("[]") !=-1) { + return Backgrid.StringCell; + } + return Backgrid.BooleanCell; + } + + /** + * DebuggerInputArgsModel used to represent input parameters for the function to debug + * for function objects. + **/ + var DebuggerInputArgsModel = Backbone.Model.extend({ + defaults: { + name: undefined, + type: undefined, + is_null: undefined, + expr: undefined, + value: undefined, + use_default: undefined, + default_value: undefined, + }, + validate: function() { + if (_.isUndefined(this.get('value')) || + _.isNull(this.get('value')) || + String(this.get('value')).replace(/^\s+|\s+$/g, '') == '') { + var msg = '{{ _('Please enter some value!') }}'; + this.errorModel.set('value', msg); + return msg; + } else { + this.errorModel.unset('value'); + } + return null; + } + }); + + // Collection which contains the model for function informations. + var DebuggerInputArgCollections = Backbone.Collection.extend({ + model: DebuggerInputArgsModel + }); + + // function will enable/disable the use_default column based on the value received. + var disableDefaultCell = function(d) { + if (d instanceof Backbone.Model) { + return d.get('use_default'); + } + return false; + }; + + // Enable/Disable the control based on the array data type of the function input arguments + var disableExpressionControl = function(d) { + var argType = d.get('type'); + if (d instanceof Backbone.Model) { + var argType = d.get('type'); + if (argType.indexOf("[]") !=-1) { + return false; + } + return true; + } + }; + + var res = function(args) { + if (!Alertify.debuggerInputArgsDialog) { + Alertify.dialog('debuggerInputArgsDialog', function factory() { + return { + main:function(title, data) { + this.set('title', title); + this.data = data; + + var argname, argtype, argmode, default_args_count, default_args, arg_cnt; + + var value_header = Backgrid.HeaderCell.extend({ + // Add fixed width to the "value" column + className: 'width_percent_25' + }); + + var def_val_list = [], + gridCols = [ + {name: 'name', label:'Name', type:'text', editable: false, cell:'string' }, + {name: 'type', label:'Type', type: 'text', editable: false, cell:'string' }, + {name: 'is_null', label:'Null?', type: 'boolean', cell: 'boolean' }, + {name: 'expr', label:'Expression?', type: 'boolean', cellFunction: cellExprControlFunction, editable: disableExpressionControl }, + {name: 'value', label:'Value', type: 'text', editable: true, cellFunction: cellFunction, headerCell: value_header }, + {name: 'use_default', label:'Use Default?', type: 'boolean', cell:"boolean", editable: disableDefaultCell }, + {name: 'default_value', label:'Default value', type: 'text', editable: false, cell:'string' } + ]; + + var my_obj = []; + + argtype = this.data['proargtypenames'].split(","); + + if (this.data['proargmodes'] != null) { + argmode = this.data['proargmodes'].split(","); + } + + if (this.data['pronargdefaults']) { + default_args_count = this.data['pronargdefaults']; + default_args = this.data['proargdefaults'].split(","); + arg_cnt = default_args_count; + } + + if (this.data['proargnames'] != null) { + argname = this.data['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] = ""; + } + } + else { + if (arg_cnt) { + arg_cnt = arg_cnt - 1; + def_val_list[j] = default_args[arg_cnt] + } + else { + 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') { + var use_def_value = false + if (def_val_list[i] != "") { + use_def_value = true; + } + my_obj.push({ "name": argname[i], "type": argtype[i], "use_default": use_def_value, "default_value": def_val_list[i]}); + } + } + else { + var use_def_value = false + if (def_val_list[i] != "") { + use_def_value = true; + } + my_obj.push({ "name": argname[i], "type": argtype[i], "use_default": use_def_value, "default_value": def_val_list[i]}); + } + } + } + } + else { + /* + Generate the name parameter if function do not have arguments name + like dbgparam1, dbgparam2 etc. + */ + var myargname = []; + + for (i = 0; i < argtype.length; i++) { + myargname[i] = "dbgparam" + (i+1); + } + + // If there is no default arguments + if (!this.data['pronargdefaults']) { + for (i = 0; i < argtype.length; i++) { + my_obj.push({ "name": myargname[i], "type": argtype[i], "use_default": false, "default_value": ""}); + } + } + else { + // 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 (arg_cnt) { + arg_cnt = arg_cnt - 1; + def_val_list[j] = default_args[arg_cnt] + } + else { + def_val_list[j] = ""; + } + } + else { + 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] = ""; + } + } + } + + for (i = 0; i < argtype.length; i++) { + if (this.data['proargmodes'] == null) { + var use_def_value = false + if (def_val_list[i] != "") { + use_def_value = true; + } + my_obj.push({ "name": myargname[i], "type": argtype[i], "use_default": use_def_value, "default_value": def_val_list[i]}); + } + else { + if (argmode[i] == 'i' || argmode[i] == 'b') { + var use_def_value = false + if (def_val_list[i] != "") { + use_def_value = true; + } + my_obj.push({ "name": myargname[i], "type": argtype[i], "use_default": use_def_value, "default_value": def_val_list[i]}); + } + } + } + } + } + + var debuggerInputArgsColl = this.debuggerInputArgsColl = new DebuggerInputArgCollections(my_obj); + + // Initialize a new Grid instance + var grid = this.grid = new Backgrid.Grid({ + columns: gridCols, + collection: debuggerInputArgsColl, + className: "backgrid table-bordered" + }); + + grid.render(); + $(this.elements.content).html(grid.el); + }, + setup:function() { + return { + buttons:[{ text: "Debug", key: 27, className: "btn btn-primary" }, + { text: "Cancel", key: 27, className: "btn btn-primary" }], + options: { modal: 0, resizable: true } + }; + }, + // Callback functions when click on the buttons of the Alertify dialogs + callback: function(e) { + if (e.button.text === "Debug") { + + // Initialize the target once the debug button is clicked and + // create asynchronous connection and unique transaction ID + var self = this; + var t = pgBrowser.tree, + i = t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined, + node = d && pgBrowser.Nodes[d._type]; + + if (!d) + return; + + var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]); + + var args_value_list = []; + + this.grid.collection.each(function(m) { + // Check if default value to be used or not + if (m.get('use_default')) { + args_value_list.push({ 'name': m.get('name'), + 'type': m.get('type'), + 'value': m.get('default_value')}); + } + else { + args_value_list.push({ 'name': m.get('name'), + 'type': m.get('type'), + 'value': m.get('value')}); + } + }); + + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "direct/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.function._id; + + $.ajax({ + url: baseUrl, + method: 'POST', + data:{'data':JSON.stringify(args_value_list)}, + success: function(res) { + + var url = "{{ url_for('debugger.index') }}" + "direct/" + res.data.debuggerTransId; + + pgBrowser.Events.once( + 'pgadmin-browser:frame:urlloaded:frm_debugger', function(frame) { + frame.openURL(url); + }); + + // Create the debugger panel as per the data received from user input dialog. + var dashboardPanel = pgBrowser.docker.findPanels( + 'dashboard' + ), + panel = pgBrowser.docker.addPanel( + 'frm_debugger', wcDocker.DOCK.STACKED, dashboardPanel[0] + ); + + panel.focus(); + + // Panel Closed event + panel.on(wcDocker.EVENT.CLOSED, function() { + var closeUrl = "{{ url_for('debugger.index') }}" + "close/" + res.data.debuggerTransId; + $.ajax({ + url: closeUrl, + method: 'GET' + }); + }); + }, + error: function(e) { + Alertify.alert( + 'Debugger target Initialize Error', + e.responseJSON.errormsg + ); + } + }); + + return true; + } + + if (e.button.text === "Cancel") { + //close the dialog... + return false; + } + }, + build:function() { + }, + prepare:function() { + // Once the dialog open disable the button by default + this.__internal.buttons[0].element.disabled = true; + + this.grid.listenTo(this.debuggerInputArgsColl,"backgrid:edited", + (function(obj) { + + return function() { + + var self = this; + var enable_btn = false; + + for (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')) { + 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) + ); + } + }; + }); + } + + Alertify.debuggerInputArgsDialog('Debugger',args).resizeTo('60%', '60%'); + + }; + + return res; +}); diff --git a/web/pgadmin/tools/debugger/templates/debugger/js/direct.js b/web/pgadmin/tools/debugger/templates/debugger/js/direct.js new file mode 100644 index 0000000..e4b688b --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/js/direct.js @@ -0,0 +1,1318 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin', + 'backbone', 'backgrid', 'codemirror', 'backform','pgadmin.tools.debugger.ui', + 'wcdocker', 'pgadmin.backform', 'pgadmin.backgrid', 'codemirror.activeline'], + function($, _, S, Alertify, pgAdmin, Backbone, Backgrid, CodeMirror, Backform, get_function_arguments) { + + pgAdmin = pgAdmin || window.pgAdmin || {}; + + var pgTools = pgAdmin.Tools = pgAdmin.Tools || {}; + + if (pgTools.DirectDebug) + return pgTools.DirectDebug; + + var controller = new (function() {}); + + _.extend( + controller, Backbone.Events, { + enable: function(btn, enable) { + // trigger the event and change the button view to enable/disable the buttons for debugging + this.trigger('pgDebugger:button:state:' + btn, enable); + }, + + /* + Function to set the breakpoint and send the line no. which is set to server + trans_id :- Unique Transaction ID, line_no - line no. to set the breakpoint, set_type = 0 - clear , 1 - set + */ + set_breakpoint: function(trans_id, line_no, set_type) { + var self = this; + + // Make ajax call to set/clear the break point by user + var baseUrl = "{{ url_for('debugger.index') }}" + "set_breakpoint/" + trans_id + "/" + line_no + "/" + set_type; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status) { + // Breakpoint has been set by the user + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Breakpoint set execution error' + ); + } + }); + }, + + // Function to get the latest breakpoint information and update the gutters of codemirror + UpdateBreakpoint: function(trans_id) { + var self = this; + + var br_list = self.GetBreakpointInformation(trans_id); + + // If there is no break point to clear then we should return from here. + if ((br_list.length == 1) && (br_list[0].linenumber == -1)) + return; + + var breakpoint_list = new Array(); + + for (i = 0; i < br_list.length; i++) { + if (br_list[i].linenumber != -1) { + breakpoint_list.push(br_list[i].linenumber) + } + } + + for (i = 0;i< breakpoint_list.length;i++) { + var info = pgTools.DirectDebug.editor.lineInfo((breakpoint_list[i] - 1)); + + if (info.gutterMarkers != undefined) { + pgTools.DirectDebug.editor.setGutterMarker((breakpoint_list[i] - 1), "breakpoints", null); + } + else { + pgTools.DirectDebug.editor.setGutterMarker((breakpoint_list[i] - 1), "breakpoints", function() { + var marker = document.createElement("div"); + marker.style.color = "#822"; + marker.innerHTML = "●"; + return marker; + }()); + } + } + }, + + // Function to get the breakpoint information from the server + GetBreakpointInformation: function(trans_id) { + var self = this; + var result = ''; + + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "execute_query/" + trans_id + "/" + "get_breakpoints"; + + $.ajax({ + url: baseUrl, + method: 'GET', + async: false, + success: function(res) { + if (res.data.status === 'Success') { + result = res.data.result; + } + else if (res.data.status === 'NotConnected') { + Alertify.alert( + 'Debugger: Error fetching breakpoint information' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Error fetching breakpoint information' + ); + } + }); + + return result; + }, + + // Function to start the executer and execute the user requested option for debugging + start_execution: function(trans_id, port_num) { + var self = this; + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "start_execution/" + trans_id + "/" + port_num; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status === 'Success') { + // If status is Success then find the port number to attach the executer. + self.execute_query(trans_id); + } + else if (res.data.status === 'NotConnected') { + Alertify.alert( + 'Debugger: Start execution error' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Start execution error' + ); + } + }); + }, + + // Execute the query and get the first functions debug information from the server + execute_query: function(trans_id) { + var self = this; + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "execute_query/" + trans_id + "/" + "wait_for_breakpoint"; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status === 'Success') { + // set the return code to the code editor text area + if (res.data.result[0].src != null && res.data.result[0].linenumber != null) { + pgTools.DirectDebug.editor.setValue(res.data.result[0].src); + active_line_no = self.active_line_no = (res.data.result[0].linenumber - 2); + pgTools.DirectDebug.editor.addLineClass((res.data.result[0].linenumber - 2), 'wrap', 'CodeMirror-activeline-background'); + } + + // Call function to create and update local variables .... + self.GetLocalVariables(trans_id); + self.GetStackInformation(trans_id); + } + else if (res.data.status === 'NotConnected') { + Alertify.alert( + 'Debugger: Execution error' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Execution error' + ); + } + }); + }, + + // Get the local variable information of the functions and update the grid + GetLocalVariables: function(trans_id) { + var self = this; + + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "execute_query/" + trans_id + "/" + "get_variables"; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status === 'Success') { + // Call function to create and update local variables + self.AddLocalVariables(res.data.result); + self.AddParameters(res.data.result); + } + else if (res.data.status === 'NotConnected') { + Alertify.alert( + 'Debugger: Error fetching variables information' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Error fetching variables information' + ); + } + }); + }, + + // Get the stack information of the functions and update the grid + GetStackInformation: function(trans_id) { + var self = this; + + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "execute_query/" + trans_id + "/" + "get_stack_info"; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status === 'Success') { + // Call function to create and update stack information + self.AddStackInformation(res.data.result); + } + else if (res.data.status === 'NotConnected') { + Alertify.alert( + 'Debugger: Error fetching stack information' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Error fetching stack information' + ); + } + }); + }, + + /* + poll the actual result after user has executed the "continue", "step-into", "step-over" actions and get the + other updated information from the server. + */ + poll_result: function(trans_id) { + var self = this; + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "poll_result/" + trans_id; + + + setTimeout( + function() { + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status === 'Success') { + if (res.data.result[0].src != undefined || res.data.result[0].src != null) { + pgTools.DirectDebug.docker.finishLoading(500); + pgTools.DirectDebug.editor.setValue(res.data.result[0].src); + self.UpdateBreakpoint(trans_id); + pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + pgTools.DirectDebug.editor.addLineClass((res.data.result[0].linenumber - 2), 'wrap', 'CodeMirror-activeline-background'); + self.active_line_no = (res.data.result[0].linenumber - 2); + + // Update the stack, local variables and parameters information + self.GetStackInformation(trans_id); + self.GetLocalVariables(trans_id); + } + else if (!pgTools.DirectDebug.debug_type && !pgTools.DirectDebug.first_time_indirect_debug) { + pgTools.DirectDebug.docker.finishLoading(500); + if (self.active_line_no != undefined) { + pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + } + self.clear_all_breakpoint(trans_id); + self.execute_query(trans_id); + pgTools.DirectDebug.first_time_indirect_debug = true; + } + else { + pgTools.DirectDebug.docker.finishLoading(500); + // If the source is really changed then only update the breakpoint information + if (res.data.result[0].src != pgTools.DirectDebug.editor.getValue()) { + pgTools.DirectDebug.editor.setValue(res.data.result[0].src); + self.UpdateBreakpoint(trans_id); + } + + pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + pgTools.DirectDebug.editor.addLineClass((res.data.result[0].linenumber - 2), 'wrap', 'CodeMirror-activeline-background'); + self.active_line_no = (res.data.result[0].linenumber - 2); + + // Update the stack, local variables and parameters information + self.GetStackInformation(trans_id); + self.GetLocalVariables(trans_id); + } + + // Enable all the buttons as we got the results + self.enable('stop', true); + self.enable('step_over', true); + self.enable('step_into', true); + self.enable('continue', true); + self.enable('toggle_breakpoint', true); + self.enable('clear_all_breakpoints', true); + } + else if (res.data.status === 'Busy') { + // If status is Busy then poll the result by recursive call to the poll function + if (!pgTools.DirectDebug.debug_type) { + pgTools.DirectDebug.docker.startLoading('{{ _('Waiting for another session to invoke the target...') }}'); + // As we are waiting for another session to invoke the target so disable all the buttons + self.enable('stop', false); + self.enable('step_over', false); + self.enable('step_into', false); + self.enable('continue', false); + self.enable('toggle_breakpoint', false); + self.enable('clear_all_breakpoints', false); + pgTools.DirectDebug.first_time_indirect_debug = false; + self.poll_result(trans_id); + } + } + else if (res.data.status === 'NotConnected') { + Alertify.alert( + 'Debugger Poll Result Error' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger Poll Result Error' + ); + } + }); + }, 800 ); + + }, + + /* + For the direct debugging, we need to check weather the functions execution is completed or not. After completion + of the debugging, we will stop polling the result until new execution starts. + */ + poll_end_execution_result: function(trans_id) { + var self = this; + //return; + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "poll_end_execution_result/" + trans_id; + self.execution_completed = false; + + setTimeout( + function() { + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status === 'Success') { + // Call function to create and update local variables .... + if (res.data.result.name != null) { + pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + self.AddResults(res.data.result); + pgTools.DirectDebug.paramsTabFrame.tab(3,true); + self.execution_completed = true; + + //Set the alertify message to inform the user that execution is completed. + Alertify.notify( + res.info, + 'success', + 3, + function() { } + ); + + // Update the message tab of the debugger + pgTools.DirectDebug.dbmsMessages.$elem.text(res.data.status_message); + + // Execution completed so disable the buttons other than "Continue/Start" button because user can still + // start the same execution again. + self.enable('stop', false); + self.enable('step_over', false); + self.enable('step_into', false); + self.enable('toggle_breakpoint', false); + self.enable('clear_all_breakpoints', false); + + //TODO: Continue button should be enable and user will be able to do next debugging with same function. + self.enable('continue', false); + } + } + else if (res.data.status === 'Busy') { + // If status is Busy then poll the result by recursive call to the poll function + //self.poll_end_execution_result(trans_id); + } + else if (res.data.status === 'NotConnected') { + Alertify.alert( + 'Debugger poll end execution error', + res.data.result + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger poll end execution error', + e.responseJSON.errormsg + ); + } + }); + }, 1000); + + }, + + // Continue the execution until the next breakpoint + Continue: function(trans_id) { + var self = this; + + //Check first if previous execution was completed or not + if (self.execution_completed) { + self.execution_completed = false; + get_function_arguments(); + // TODO: we need to again ask for the input dialog + //Previous execution was completed so we need to again start the execution with first step..... + } + else { + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "execute_query/" + trans_id + "/" + "continue"; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status) { + self.poll_result(trans_id); + if (pgTools.DirectDebug.debug_type) { + self.poll_end_execution_result(trans_id); + } + } + else { + Alertify.alert( + 'Debugger: Continue execution error' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Continue execution error' + ); + } + }); + } + }, + + Step_over: function(trans_id) { + var self = this; + + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "execute_query/" + trans_id + "/" + "step_over"; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status) { + self.poll_result(trans_id); + if (pgTools.DirectDebug.debug_type) { + self.poll_end_execution_result(trans_id); + } + } + else { + Alertify.alert( + 'Debugger: Step over execution error' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Step over execution error' + ); + } + }); + }, + + Step_into: function(trans_id) { + var self = this; + + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "execute_query/" + trans_id + "/" + "step_into"; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status) { + self.poll_result(trans_id); + if (pgTools.DirectDebug.debug_type) { + self.poll_end_execution_result(trans_id); + } + } + else { + Alertify.alert( + 'Debugger: Step into execution error' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Step into execution error' + ); + } + }); + }, + + Stop: function(trans_id) { + var self = this; + + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "execute_query/" + trans_id + "/" + "abort_target"; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status) { + // Call function to create and update local variables .... + pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + self.execution_completed = true; + + //Set the alertify message to inform the user that execution is completed. + Alertify.notify( + res.info, + 'success', + 3, + function() { } + ); + + //Disable the buttons other than continue button. If user wants to again then it should allow to debug again... + self.enable('stop', false); + self.enable('step_over', false); + self.enable('step_into', false); + self.enable('continue', false); + self.enable('toggle_breakpoint', false); + self.enable('clear_all_breakpoints', false); + } + else if (res.data.status === 'NotConnected') { + Alertify.alert( + 'Debugger: Stop execution error' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Stop execution error' + ); + } + }); + }, + + toggle_breakpoint: function(trans_id) { + var self = this; + + var info = pgTools.DirectDebug.editor.lineInfo(self.active_line_no); + var baseUrl = ''; + + // If gutterMarker is undefined that means there is no marker defined previously + // So we need to set the breakpoint command here... + if (info.gutterMarkers == undefined) { + baseUrl = "{{ url_for('debugger.index') }}" + "set_breakpoint/" + trans_id + "/" + self.active_line_no + "/" + "1"; + } + else { + baseUrl = "{{ url_for('debugger.index') }}" + "set_breakpoint/" + trans_id + "/" + self.active_line_no + "/" + "0"; + } + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status) { + // Call function to create and update local variables .... + var info = pgTools.DirectDebug.editor.lineInfo(self.active_line_no); + + if (info.gutterMarkers != undefined) { + pgTools.DirectDebug.editor.setGutterMarker(self.active_line_no, "breakpoints", null); + } + else { + pgTools.DirectDebug.editor.setGutterMarker(self.active_line_no, "breakpoints", function() { + var marker = document.createElement("div"); + marker.style.color = "#822"; + marker.innerHTML = "●"; + return marker; + }()); + } + } + else if (res.data.status === 'NotConnected') { + Alertify.alert( + 'Debugger: Toggle breakpoint execution error' + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Toggle breakpoint execution error' + ); + } + }); + }, + + clear_all_breakpoint: function(trans_id) { + var self = this; + + var br_list = self.GetBreakpointInformation(trans_id); + + // If there is no break point to clear then we should return from here. + if ((br_list.length == 1) && (br_list[0].linenumber == -1)) + return; + + var breakpoint_list = new Array(); + + for (i = 0; i < br_list.length; i++) { + if (br_list[i].linenumber != -1) { + breakpoint_list.push(br_list[i].linenumber) + } + } + + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "clear_all_breakpoint/" + trans_id; + + $.ajax({ + url: baseUrl, + method: 'POST', + data: { 'breakpoint_list': breakpoint_list.join() }, + success: function(res) { + if (res.data.status) { + for (i = 0;i< breakpoint_list.length;i++) { + var info = pgTools.DirectDebug.editor.lineInfo((breakpoint_list[i] - 1)); + + if (info) { + if (info.gutterMarkers != undefined) { + pgTools.DirectDebug.editor.setGutterMarker((breakpoint_list[i] - 1), "breakpoints", null); + } + } + } + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Clear all breakpoint execution error' + ); + } + }); + }, + + AddStackInformation: function(result) { + var self = this; + + // Remove the existing created grid and update the stack values + if (self.stack_grid) { + self.stack_grid.remove(); + self.stack_grid = null; + } + + pgTools.DirectDebug.stackTab.$elem.empty(); + + var DebuggerStackModel = Backbone.Model.extend({ + defaults: { + name: undefined, + value: undefined, + line_no: undefined + } + }); + + // Collection which contains the model for function informations. + var StackCollection = Backbone.Collection.extend({ + model: DebuggerStackModel + }); + + stackGridCols = [ + {name: 'name', label:'Name', type:'text', editable: false, cell:'string'}, + {name: 'value', label:'Value', type:'text', editable: false, cell:'string'}, + {name: 'line_no', label:'Line No.', type:'text', editable: false, cell:'string'} + ]; + + var my_obj = []; + if (result.length != 0) + { + for (i = 0; i < result.length; i++) { + // TODO: change the my_func_test_2 with name of the function to be executed. + my_obj.push({ "name": result[i].targetname, "value": result[i].args, "line_no": result[i].linenumber }); + } + } + + var stackColl = this.stackColl = new StackCollection(my_obj); + this.stackColl.on('backgrid:row:selected', self.select_frame, self); + + // Initialize a new Grid instance + var stack_grid = this.stack_grid = new Backgrid.Grid({ + columns: stackGridCols, + row: Backgrid.Row.extend({ + highlightColor: "#D9EDF7", + disabledColor: "#F1F1F1", + events: { + click: "rowClick" + }, + rowClick: function(e) { + //Find which row is selected and depending on that send the frame id + for (i = 0; i < this.model.collection.length; i++) { + if (this.model.collection.models[i].get('name') == this.model.get('name')) { + self.frame_id_ = i; + break; + } + } + this.model.trigger('backgrid:row:selected', this); + self.stack_grid.$el.find("td").css("background-color", this.disabledColor); + this.$el.find("td").css("background-color", this.highlightColor); + } + }), + collection: stackColl, + className: "backgrid table-bordered" + }); + + stack_grid.render(); + pgTools.DirectDebug.stackTab.$elem.append(stack_grid.el); + }, + + AddResults: function(result) { + var self = this; + + // Remove the existing created grid and update the result values + if (self.result_grid) { + self.result_grid.remove(); + self.result_grid = null; + } + + pgTools.DirectDebug.retResults.$elem.empty(); + + var DebuggerResultsModel = Backbone.Model.extend({ + defaults: { + name: undefined + } + }); + + // Collection which contains the model for function informations. + var ResultsCollection = Backbone.Collection.extend({ + model: DebuggerResultsModel + }); + + resultGridCols = [ + {name: 'value', label:result.name, type:'text', editable: false, cell:'string'} + ]; + + var my_obj = []; + if (result.value.length != 0) + { + for (i = 0; i < result.value.length; i++) { + // TODO: change the my_func_test_2 with name of the function to be executed. + my_obj.push({ "value": result.value[i]}); + } + } + + // Initialize a new Grid instance + var result_grid = this.result_grid = new Backgrid.Grid({ + columns: resultGridCols, + collection: new ResultsCollection(my_obj), + className: "backgrid table-bordered" + }); + + result_grid.render(); + pgTools.DirectDebug.retResults.$elem.append(result_grid.el); + }, + + AddLocalVariables: function(result) { + var self = this; + + // Remove the existing created grid and update the variables values + if (self.variable_grid) { + self.variable_grid.remove(); + self.variable_grid = null; + } + + pgTools.DirectDebug.localVars.$elem.empty(); + + var DebuggerVariablesModel = Backbone.Model.extend({ + defaults: { + name: undefined, + type: undefined, + value: undefined + } + }); + + // Collection which contains the model for function informations. + var VariablesCollection = Backbone.Collection.extend({ + model: DebuggerVariablesModel + }); + + gridCols = [ + {name: 'name', label:'Name', type:'text', editable: false, cell:'string'}, + {name: 'type', label:'Type', type: 'text', editable: false, cell:'string'}, + {name: 'value', label:'Value', type: 'text', cell: 'string'} + ]; + + var my_obj = []; + if (result.length != 0) + { + for (i = 0; i < result.length; i++) { + if (result[i].varclass == 'L') { + my_obj.push({ "name": result[i].name, "type": result[i].dtype, "value": result[i].value}); + } + } + } + + // Initialize a new Grid instance + var variable_grid = this.variable_grid = new Backgrid.Grid({ + columns: gridCols, + collection: new VariablesCollection(my_obj), + className: "backgrid table-bordered" + }); + + variable_grid.render(); + pgTools.DirectDebug.localVars.$elem.append(variable_grid.el); + }, + + AddParameters: function(result) { + var self = this; + + // Remove the existing created grid and update the parameter values + if (self.param_grid) { + self.param_grid.remove(); + self.param_grid = null; + } + + pgTools.DirectDebug.paramsTab.$elem.empty(); + + var DebuggerParametersModel = Backbone.Model.extend({ + defaults: { + name: undefined, + type: undefined, + value: undefined + } + }); + + // Collection which contains the model for function informations. + var ParametersCollection = self.ParametersCollection = Backbone.Collection.extend({ + model: DebuggerParametersModel + }); + + self.ParametersCollection.prototype.on('change', self.deposit_parameter_value, self); + + paramGridCols = [ + {name: 'name', label:'Name', type:'text', editable: false, cell:'string'}, + {name: 'type', label:'Type', type: 'text', editable: false, cell:'string'}, + {name: 'value', label:'Value', type: 'text', cell: 'string'} + ]; + + var param_obj = []; + if (result.length != 0) + { + for (i = 0; i < result.length; i++) { + if (result[i].varclass == 'A') { + param_obj.push({ "name": result[i].name, "type": result[i].dtype, "value": result[i].value}); + } + } + } + + // Initialize a new Grid instance + var param_grid = this.param_grid = new Backgrid.Grid({ + columns: paramGridCols, + collection: new ParametersCollection(param_obj), + className: "backgrid table-bordered" + }); + + param_grid.render(); + pgTools.DirectDebug.paramsTab.$elem.append(param_grid.el); + }, + + deposit_parameter_value: function(model) { + var self = this; + + // variable name and value list that is changed by user + var name_value_list = []; + + name_value_list.push({ 'name': model.get('name'),'type': model.get('type'), 'value': model.get('value')}); + + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "deposit_value/" + pgTools.DirectDebug.trans_id; + + $.ajax({ + url: baseUrl, + method: 'POST', + data:{'data':JSON.stringify(name_value_list)}, + success: function(res) { + if (res.data.status) { + // Get the updated variables value + self.GetLocalVariables(pgTools.DirectDebug.trans_id); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Deposit value execution error', + e.responseJSON.errormsg + ); + } + }); + }, + + select_frame: function(model, selected) { + var self = this; + + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "select_frame/" + pgTools.DirectDebug.trans_id + "/" + self.frame_id_; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status) { + pgTools.DirectDebug.editor.setValue(res.data.result[0].src); + self.UpdateBreakpoint(pgTools.DirectDebug.trans_id); + //active_line_no = self.active_line_no = (res.data.result[0].linenumber - 2); + pgTools.DirectDebug.editor.addLineClass((res.data.result[0].linenumber - 2), 'wrap', 'CodeMirror-activeline-background'); + + // Call function to create and update local variables .... + self.GetLocalVariables(pgTools.DirectDebug.trans_id); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger: Select frame execution error', + e.responseJSON.errormsg + ); + } + }); + }, + } + ) + + /* + Debugger tool var view to create the button toolbar and listen to the button click event and inform the + controller about the click and controller will take the action for the specified button click. + */ + var DebuggerToolbarView = Backbone.View.extend({ + el: '#btn-toolbar', + initialize: function() { + controller.on('pgDebugger:button:state:stop', this.enable_stop, this); + controller.on('pgDebugger:button:state:step_over', this.enable_step_over, this); + controller.on('pgDebugger:button:state:step_into', this.enable_step_into, this); + controller.on('pgDebugger:button:state:continue', this.enable_continue, this); + controller.on('pgDebugger:button:state:toggle_breakpoint', this.enable_toggle_breakpoint, this); + controller.on('pgDebugger:button:state:clear_all_breakpoints', this.enable_clear_all_breakpoints, this); + }, + events: { + 'click .btn-stop': 'on_stop', + 'click .btn-clear-breakpoint': 'on_clear_all_breakpoint', + 'click .btn-toggle-breakpoint': 'on_toggle_breakpoint', + 'click .btn-continue': 'on_continue', + 'click .btn-step-over': 'on_step_over', + 'click .btn-step-into': 'on_step_into' + }, + enable_stop: function(enable) { + var $btn = this.$el.find('.btn-stop'); + + if (enable) { + $btn.prop('disabled', false); + $btn.removeAttr('disabled'); + } else { + $btn.prop('disabled', true); + $btn.attr('disabled', 'disabled'); + } + }, + enable_step_over: function(enable) { + var $btn = this.$el.find('.btn-step-over'); + + if (enable) { + $btn.prop('disabled', false); + $btn.removeAttr('disabled'); + } else { + $btn.prop('disabled', true); + $btn.attr('disabled', 'disabled'); + } + }, + enable_step_into: function(enable) { + var $btn = this.$el.find('.btn-step-into'); + + if (enable) { + $btn.prop('disabled', false); + $btn.removeAttr('disabled'); + } else { + $btn.prop('disabled', true); + $btn.attr('disabled', 'disabled'); + } + }, + enable_continue: function(enable) { + var $btn = this.$el.find('.btn-continue'); + + if (enable) { + $btn.prop('disabled', false); + $btn.removeAttr('disabled'); + } else { + $btn.prop('disabled', true); + $btn.attr('disabled', 'disabled'); + } + }, + enable_toggle_breakpoint: function(enable) { + var $btn = this.$el.find('.btn-toggle-breakpoint'); + + if (enable) { + $btn.prop('disabled', false); + $btn.removeAttr('disabled'); + } else { + $btn.prop('disabled', true); + $btn.attr('disabled', 'disabled'); + } + }, + enable_clear_all_breakpoints: function(enable) { + var $btn = this.$el.find('.btn-clear-breakpoint'); + + if (enable) { + $btn.prop('disabled', false); + $btn.removeAttr('disabled'); + } else { + $btn.prop('disabled', true); + $btn.attr('disabled', 'disabled'); + } + }, + + on_stop: function() { + controller.Stop(pgTools.DirectDebug.trans_id); + }, + on_clear_all_breakpoint: function() { + controller.clear_all_breakpoint(pgTools.DirectDebug.trans_id); + }, + on_toggle_breakpoint: function() { + controller.toggle_breakpoint(pgTools.DirectDebug.trans_id); + }, + on_continue: function() { + controller.Continue(pgTools.DirectDebug.trans_id); + }, + on_step_over: function() { + controller.Step_over(pgTools.DirectDebug.trans_id); + }, + on_step_into: function() { + controller.Step_into(pgTools.DirectDebug.trans_id); + }, + }); + + + /* + Function is responsible to create the new wcDocker instance for debugger and initialize the debugger panel inside + the docker instance. + */ + var DirectDebug = function() {}; + + _.extend(DirectDebug.prototype, { + init: function(trans_id, debug_type) { /* We should get the transaction id from the server during initialization here */ + // We do not want to initialize the module multiple times. + + var self = this; + _.bindAll(pgTools.DirectDebug, 'messages'); + + if (this.initialized) + return; + + this.initialized = true; + this.trans_id = trans_id; + this.debug_type = debug_type; + this.first_time_indirect_debug = false; + + var docker = this.docker = new wcDocker( + '#container', { + allowContextMenu: false, + allowCollapse: false, + themePath: '{{ url_for('static', filename='css/wcDocker/Themes') }}', + theme: 'pgadmin' + }); + + this.panels = []; + + // Below code will be executed for indirect debugging + if (trans_id != undefined && !debug_type) { + // Make ajax call to execute the and start the target for execution + var baseUrl = "{{ url_for('debugger.index') }}" + "start_listener/" + trans_id; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status) { + self.intializePanels(); + controller.poll_result(trans_id); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger listener starting error', + e.responseJSON.errormsg + ); + } + }); + } + else if (trans_id != undefined && debug_type) + { + // Make ajax call to execute the and start the target for execution + var baseUrl = "{{ url_for('debugger.index') }}" + "start_listener/" + trans_id; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status) { + self.messages(trans_id); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger listener starting error', + e.responseJSON.errormsg + ); + } + }); + } + else + this.intializePanels(); + }, + + // Read the messages of the database server and get the port ID and attach the executer to that port. + messages: function(trans_id) { + var self = this; + // Make ajax call to listen the database message + var baseUrl = "{{ url_for('debugger.index') }}" + "messages/" + trans_id; + + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status === 'Success') { + self.intializePanels(); + // If status is Success then find the port number to attach the executer. + //self.start_execution(trans_id, res.data.result); + controller.start_execution(trans_id, res.data.result); + } + else if (res.data.status === 'Busy') { + // If status is Busy then poll the result by recursive call to the poll function + self.messages(trans_id); + } + else if (res.data.status === 'NotConnected') { + Alertify.alert( + 'Data Grid Poll Result Error', + res.data.result + ); + } + }, + error: function(e) { + Alertify.alert( + 'Debugger listener starting error' + ); + } + }); + + }, + + // Callback function when user click on gutters of codemirror to set/clear the breakpoint + onBreakPoint: function(cm, m) { + var self = this; + + // TODO:: + // We may want to check, if break-point is allowed at this moment or not + var info = cm.lineInfo(m); + + // If gutterMarker is undefined that means there is no marker defined previously + // So we need to set the breakpoint command here... + if (info.gutterMarkers == undefined) { + controller.set_breakpoint(self.trans_id,m+1,1); //set the breakpoint + } + else { + controller.set_breakpoint(self.trans_id,m+1,0); //clear the breakpoint + } + + cm.setGutterMarker( + m, "breakpoints", info.gutterMarkers ? null : function() { + var marker = document.createElement("div"); + + marker.style.color = "#822"; + marker.innerHTML = "●"; + + return marker; + }()); + }, + + // Create the debugger layout with splitter and display the appropriate data received from server. + intializePanels: function() { + var self = this; + this.registerPanel( + 'code', false, '100%', '100%', + function(panel) { + var container = panel.layout().scene().find('.pg-debugger-panel'); + + // Create the wcSplitter used by wcDocker to split the single panel. + var hSplitter = new wcSplitter( + container, panel, + wcDocker.ORIENTATION.VERTICAL + ); + + hSplitter.scrollable(0, false, false); + hSplitter.scrollable(1, true, true); + + // Initialize this splitter with a layout in each pane. + hSplitter.initLayouts(wcDocker.LAYOUT.SIMPLE, wcDocker.LAYOUT.SIMPLE); + + // By default, the splitter splits down the middle, we split the main panel by 80%. + hSplitter.pos(0.65); + + var $params = $('
'); + hSplitter.right().addItem($params); + + // Add Local parameters tab to display function arguments value + var paramsTabFrame = self.paramsTabFrame = new wcTabFrame($params, panel); + var paramsTab = self.paramsTab = paramsTabFrame.addTab( + '{{_('Parameters')}}', -1, wcDocker.LAYOUT.SIMPLE + ); + paramsTab.addItem($('
')); + + // Add Local variables tab + var localVars = self.localVars = paramsTabFrame.addTab( + '{{ _('Local variables') }}', -1, wcDocker.LAYOUT.SIMPLE + ); + localVars.addItem($('
')); + + // Add DBMS messages tab + var dbmsMessages = self.dbmsMessages = paramsTabFrame.addTab( + '{{ _('Messages') }}', -1, wcDocker.LAYOUT.SIMPLE + ); + dbmsMessages.addItem($('
')); + + // Add function return results tab + var retResults = self.retResults = paramsTabFrame.addTab( + '{{ _('Results') }}', -1, wcDocker.LAYOUT.SIMPLE + ); + retResults.addItem($('
')); + + // Now create a second splitter to go inside the existing one. + var $topContainer = $('
'); + hSplitter.left().addItem($topContainer); + + // Create the wcSplitter used by wcDocker to split the single panel. + var vSplitter = new wcSplitter( + $topContainer, panel, + wcDocker.ORIENTATION.HORIZONTAL + ); + + // Initialize this splitter with a layout in each pane. + vSplitter.initLayouts(wcDocker.LAYOUT.SIMPLE, wcDocker.LAYOUT.SIMPLE); + + // Now create a tab widget and put that into one of the sub splits. + var $stack = $('
'); + vSplitter.bottom().addItem($stack); + + var stackFrame = new wcTabFrame($stack, panel); + var stackTab = self.stackTab = stackFrame.addTab( + '{{ _('Stack pane') }}', -1, wcDocker.LAYOUT.SIMPLE + ); + stackTab.addItem( + $('
')); + + // By default, the splitter splits down the middle, we split the main panel by 80%. + vSplitter.pos(0.75); + + // Now create a tab widget and put that into one of the sub splits. + var editor_pane = $('
'); + var code_editor_area = $('').append(editor_pane); + vSplitter.top().addItem(code_editor_area); + + // To show the line-number and set breakpoint marker details by user. + var editor = self.editor = CodeMirror.fromTextArea( + code_editor_area.get(0), { + lineNumbers: true, + gutters: ["note-gutter", "CodeMirror-linenumbers", "breakpoints"], + mode: "text/x-sql", + readOnly: true + }); + }); + + // On loading the docker, register the callbacks + var onLoad = function() { + self.docker.finishLoading(100); + self.docker.off(wcDocker.EVENT.LOADED); + // Register the callback when user set/clear the breakpoint on gutter area. + self.editor.on("gutterClick", self.onBreakPoint.bind(self), self); + }; + + self.docker.startLoading('{{ _('Loading...') }}'); + self.docker.on(wcDocker.EVENT.LOADED, onLoad); + + self.main_panel = self.docker.addPanel( + 'code', wcDocker.DOCK.TOP, null, {h: '100%', w: '100%'} + ); + + // Create the toolbar view for debugging the function + this.toolbarView = new DebuggerToolbarView(); + }, + + // Register the panel with new debugger docker instance. + registerPanel: function(name, title, width, height, onInit) { + var self = this; + + this.docker.registerPanelType(name, { + title: title, + isPrivate: true, + onCreate: function(panel) { + self.panels[name] = panel; + panel.initSize(width, height); + if (!title) + panel.title(false); + else + panel.title(title); + panel.closeable(false); + panel.layout().addItem( + $('
', {'class': 'pg-debugger-panel'}) + ); + if (onInit) { + onInit.apply(self, [panel]); + } + } + }); + } + }); + + pgTools.DirectDebug = new DirectDebug(); + + return pgTools.DirectDebug; +}); \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/execute_edbspl.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/execute_edbspl.sql new file mode 100755 index 0000000..04c0953 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/execute_edbspl.sql @@ -0,0 +1,115 @@ +{### Create executer function for edb spl function debugging ###} +{% set inside_loop = {'value': False} %} + +{% if lan_name == 'edbspl' %} +{% set useAnonymousBlock = "true" %} +{% if not is_func %} + {% set str_statement = "\tEXEC " ~ func_name %} +{% elif ret_type == 'void' %} + {% set str_statement = "\tPERFORM " ~ func_name %} +{% else %} + {% set resultVar = "v_retVal" %} + {% set str_statement = "\t" ~ resultVar ~ " := " ~ func_name %} + {% set str_declare = str_declare ~ "\t" ~ resultVar ~ " " ~ ret_type ~ ";\n" %} + {% set str_result = "\tDBMS_OUTPUT.PUT_LINE(E'\\n\\nResult:\\n--------\\n' || " ~ resultVar ~ "::text || E'\\n\\nNOTE: This is the result generated during the function execution by the debugger.\\n');\n" %} +{% endif %} + +{% else %} +{% if ret_type == 'record' %} + {% set str_statement = "\tSELECT " ~ func_name %} +{% else %} + {% set str_statement = "\tSELECT * FROM " ~ func_name %} +{% endif %} + +{% endif %} + + +{% set firstProceesed = "false" %} +{% set input_value_index = 0 %} + +{% if arg_type|length > 0 %} +{% set str_statement = str_statement ~ "(" %} + +{% for arg_mode in args_mode %} +.... ARG_MODE: .... {{ arg_mode }}... Index.... {{ loop.index }}.... +{% if useAnonymousBlock == "true" and (arg_mode == 'o' or arg_mode == 'b') %} +{% set strParam = "p_param" ~ (loop.index - 1) %} +{% set str_declare = str_declare ~ "\t" ~ strParam ~ " " ~ arg_type[loop.index - 1] %} +{% if arg_mode == 'b' %} +{### TODO: to check for Null parameters received from client ###} +{% set str_declare = str_declare ~ " := " ~ strParam ~ " " ~ data[input_value_index]['value'] ~ "::" ~ data[input_value_index]['type'] %} +{% set input_value_index = input_value_index + 1 %} +{% endif %} +{% set str_declare = str_declare ~ ";\n" %} + +{% if firstProceesed == "true" %} +{% set str_statement = str_statement ~ ", " %} +{% endif %} +{% set firstProceesed = "true" %} +{% set str_statement = str_statement ~ strParam %} + + + + + +{% elif arg_mode != 'o' %} +{% if firstProceesed == "true" %} +{% set str_statement = str_statement ~ ", " %} +{% endif %} +{% set firstProceesed = "true" %} + +{% if arg_mode == 'v' %} +{% set str_statement = str_statement ~ "VARIADIC " %} +{% endif %} + +{### TODO: to check for Null parameters received from client ###} + +{% set str_statement = str_statement ~ data[input_value_index]['value'] ~ "::" ~ data[input_value_index]['type'] %} +{% set input_value_index = input_value_index + 1 %} + + +{% endif %} + +{% if loop.last %} +{% set str_statement = str_statement ~ ")" %} +{% set strQuery = str_statement %} +{% if useAnonymousBlock == "true" %} +{% set strQuery = "DECLARE\n" ~ str_declare ~ "BEGIN\n" ~ str_statement ~ ";\n" ~ str_result ~ "END;" %} +{% endif %} + +{{ strQuery }} +{% if inside_loop.update({'value': True}) %} {% endif %} +{% endif %} + +{% endfor %} + + + + + + + + + + + + +{% elif not is_func and lan_name == 'edbspl' %} +{% set strQuery = str_statement %} +{% if useAnonymousBlock == "true" %} +{% set strQuery = "DECLARE\n" ~ str_declare ~ "BEGIN\n" ~ str_statement ~ ";\n" ~ str_result ~ "END;" %} +{% endif %} +{% else %} +{% set strQuery = str_statement ~ "()" %} +{% if useAnonymousBlock == "true" %} +{% set strQuery = "DECLARE\n" ~ str_declare ~ "BEGIN\n" ~ str_statement ~ ";\n" ~ str_result ~ "END;" %} +{% endif %} +{% endif %} + + + + +{### Return final query formed with above condition ###} +{% if not inside_loop.value %} +{{ strQuery }} +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql new file mode 100755 index 0000000..f4201c3 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql @@ -0,0 +1,20 @@ +{### Create executer function for plpgsql function debugging ###} +{% if is_func == 'False' %} + EXEC {{ func_name }} ( +{% elif ret_type == 'record' %} + SELECT {{ func_name }} ( +{% else %} + SELECT * FROM {{ func_name }} ( +{% endif %} +{% if data %} +{% for dict_item in data %} +{% if 'type' in dict_item and 'value' in dict_item %} +{% if dict_item['type'] == 'text' %} +{{ dict_item['value']|qtLiteral }}::{{ dict_item['type'] }}{% if not loop.last %}, {% endif %} +{% else %} +{% if '[]' in dict_item['type'] %} ARRAY{{ dict_item['value'] }}::{{ dict_item['type'] }} {% else %} {{ dict_item['value'] }}::{{ dict_item['type'] }} {% endif %} {% if not loop.last %}, {% endif %} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} +) \ No newline at end of file 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 new file mode 100755 index 0000000..30e47e5 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/get_function_debug_info.sql @@ -0,0 +1,66 @@ +{### To fetch debug function information ###} +SELECT + p.proname AS name, p.prosrc, l.lanname, p.proretset, p.prorettype, y.typname AS rettype, + CASE WHEN proallargtypes IS NOT NULL THEN + pg_catalog.array_to_string(ARRAY( + SELECT + pg_catalog.format_type(p.proallargtypes[s.i], NULL) + FROM + pg_catalog.generate_series(0, pg_catalog.array_upper( + p.proallargtypes, 1)) AS s(i)), ',') + ELSE + pg_catalog.array_to_string(ARRAY( + SELECT + pg_catalog.format_type(p.proargtypes[s.i], NULL) + FROM + pg_catalog.generate_series(0, pg_catalog.array_upper( + p.proargtypes, 1)) AS s(i)), ',') + END AS proargtypenames, + CASE WHEN proallargtypes IS NOT NULL THEN + pg_catalog.array_to_string(ARRAY( + SELECT proallargtypes[s.i] FROM + pg_catalog.generate_series(0, pg_catalog.array_upper(proallargtypes, 1)) s(i)), ',') + ELSE + pg_catalog.array_to_string(ARRAY( + SELECT proargtypes[s.i] FROM + 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, + pg_catalog.array_to_string(proargmodes, ',') AS proargmodes, + + {% 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, + CASE WHEN n.nspparent <> 0 THEN (SELECT oid FROM pg_proc WHERE pronamespace=n.oid AND proname='cons') ELSE 0 END AS pkgconsoid, + CASE WHEN n.nspparent <> 0 THEN g.oid ELSE n.oid END AS schema, + CASE WHEN n.nspparent <> 0 THEN g.nspname ELSE n.nspname END AS schemaname, + NOT (l.lanname = 'edbspl' AND protype = '1') AS isfunc, + {%else%} + 0 AS pkg, + '' AS pkgname, + 0 AS pkgconsoid, + n.oid AS schema, + n.nspname AS schemaname, + true AS isfunc, + {%endif%} + pg_catalog.pg_get_function_identity_arguments(p.oid) AS signature, + + {% if hasFeatureFunctionDefaults %} + pg_catalog.pg_get_expr(p.proargdefaults, 'pg_catalog.pg_class'::regclass, false) AS proargdefaults, + p.pronargdefaults + {%else%} + '' AS proargdefaults, 0 AS pronargdefaults + {%endif%} + FROM + pg_catalog.pg_proc p + LEFT JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + LEFT JOIN pg_catalog.pg_language l ON p.prolang = l.oid + LEFT JOIN pg_catalog.pg_type y ON p.prorettype = y.oid + + {% if is_ppas_database %} + LEFT JOIN pg_catalog.pg_namespace g ON n.nspparent = g.oid + {% endif %} + + {% if fid %} + WHERE p.oid = {{fid}}::int; + {% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/Backend_running.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/Backend_running.sql new file mode 100755 index 0000000..c5770d0 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/Backend_running.sql @@ -0,0 +1,4 @@ +{### Check backend target is running or not for debugging ###} +{% if backend_pid %} + SELECT COUNT(*) FROM (SELECT pg_catalog.pg_stat_get_backend_idset() AS bid) AS s WHERE pg_catalog.pg_stat_get_backend_pid(s.bid) = {{ conn|qtIdent(backend_pid) }}::integer; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/abort_target.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/abort_target.sql new file mode 100755 index 0000000..8f89ae3 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/abort_target.sql @@ -0,0 +1,4 @@ +{### Abort the target for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_abort_target({{session_id}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/add_breakpoint_edb.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/add_breakpoint_edb.sql new file mode 100755 index 0000000..0129462 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/add_breakpoint_edb.sql @@ -0,0 +1,4 @@ +{### Add EDB breakpoints for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_set_global_breakpoint({{session_id}}::int, {{package_oid}}::int, {{function_oid}}::OID, -1, {{target_oid}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/add_breakpoint_pg.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/add_breakpoint_pg.sql new file mode 100755 index 0000000..f83e1cf --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/add_breakpoint_pg.sql @@ -0,0 +1,4 @@ +{### Add PG breakpoint for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_set_global_breakpoint({{session_id}}, {{function_oid}}, -1, NULL) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/attach_to_port.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/attach_to_port.sql new file mode 100755 index 0000000..bb74477 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/attach_to_port.sql @@ -0,0 +1,4 @@ +{### Attach the target to port for debugging ###} +{% if port %} + SELECT * FROM pldbg_attach_to_port({{port}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/clear_breakpoint.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/clear_breakpoint.sql new file mode 100755 index 0000000..eecc674 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/clear_breakpoint.sql @@ -0,0 +1,4 @@ +{### Clear breakpoints for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_drop_breakpoint({{session_id}}::int, {{poid}}::OID, {{foid}}::OID, {{line_number}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/continue.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/continue.sql new file mode 100755 index 0000000..071c53e --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/continue.sql @@ -0,0 +1,11 @@ +{### Continue for debugging ###} +{% if session_id %} + SELECT + p.pkg AS pkg, p.func AS func, p.targetName AS targetName, + p.linenumber AS linenumber, pldbg_get_source({{session_id}}::INTEGER, p.pkg, p.func) AS src, + (SELECT + s.args + FROM pldbg_get_stack({{session_id}}::INTEGER) s + WHERE s.func = p.func AND s.pkg = p.pkg) AS args + FROM pldbg_continue({{session_id}}::INTEGER) p +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/create_listener.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/create_listener.sql new file mode 100755 index 0000000..1d47739 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/create_listener.sql @@ -0,0 +1,2 @@ +{### Create listener for debugging ###} + SELECT * from pldbg_create_listener() diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_plpgsql_execute_target.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_plpgsql_execute_target.sql new file mode 100755 index 0000000..5be078d --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_plpgsql_execute_target.sql @@ -0,0 +1,4 @@ +{### Debug execute target for plpgsql function ###} +{% if function_oid %} + SELECT plpgsql_oid_debug({{packge_oid}}::OID, {{function_oid}}::OID) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_plpgsql_init.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_plpgsql_init.sql new file mode 100755 index 0000000..14ad3c5 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_plpgsql_init.sql @@ -0,0 +1,4 @@ +{### Debug Initialization for plpgsql function ###} +{% if packge_init_oid %} + SELECT plpgsql_oid_debug({{packge_oid}}::OID, {{packge_init_oid}}::OID) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_spl_execute_target.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_spl_execute_target.sql new file mode 100755 index 0000000..84afb34 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_spl_execute_target.sql @@ -0,0 +1,4 @@ +{### Debug execute target for EDB spl function ###} +{% if function_oid %} + SELECT edb_oid_debug({{packge_oid}}::OID, {{function_oid}}::OID) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_spl_init.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_spl_init.sql new file mode 100755 index 0000000..aa5b324 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/debug_spl_init.sql @@ -0,0 +1,4 @@ +{### Debug Initialization for EDB spl function ###} +{% if packge_init_oid %} + SELECT edb_oid_debug({{packge_oid}}::OID, {{packge_init_oid}}::OID) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/deposit_value.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/deposit_value.sql new file mode 100755 index 0000000..86e9f4a --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/deposit_value.sql @@ -0,0 +1,4 @@ +{### Change the variable value and submit during debugging ###} +{% if session_id %} + SELECT * FROM pldbg_deposit_value({{session_id}}::int, {{var_name|qtLiteral}}, {{line_number}}, {{val|qtLiteral}}) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_breakpoints.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_breakpoints.sql new file mode 100755 index 0000000..19da5db --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_breakpoints.sql @@ -0,0 +1,4 @@ +{### Get the breakpoint information for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_get_breakpoints({{session_id}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_function_info.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_function_info.sql new file mode 100755 index 0000000..7989da7 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_function_info.sql @@ -0,0 +1,66 @@ +{### To fetch debug function information ###} +SELECT + p.proname AS name, l.lanname, p.proretset, p.prorettype, y.typname AS rettype, + CASE WHEN proallargtypes IS NOT NULL THEN + pg_catalog.array_to_string(ARRAY( + SELECT + pg_catalog.format_type(p.proallargtypes[s.i], NULL) + FROM + pg_catalog.generate_series(0, pg_catalog.array_upper( + p.proallargtypes, 1)) AS s(i)), ',') + ELSE + pg_catalog.array_to_string(ARRAY( + SELECT + pg_catalog.format_type(p.proargtypes[s.i], NULL) + FROM + pg_catalog.generate_series(0, pg_catalog.array_upper( + p.proargtypes, 1)) AS s(i)), ',') + END AS proargtypenames, + CASE WHEN proallargtypes IS NOT NULL THEN + pg_catalog.array_to_string(ARRAY( + SELECT proallargtypes[s.i] FROM + pg_catalog.generate_series(0, pg_catalog.array_upper(proallargtypes, 1)) s(i)), ',') + ELSE + pg_catalog.array_to_string(ARRAY( + SELECT proargtypes[s.i] FROM + 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, + pg_catalog.array_to_string(proargmodes, ',') AS proargmodes, + + {% 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, + CASE WHEN n.nspparent <> 0 THEN (SELECT oid FROM pg_proc WHERE pronamespace=n.oid AND proname='cons') ELSE 0 END AS pkgconsoid, + CASE WHEN n.nspparent <> 0 THEN g.oid ELSE n.oid END AS schema, + CASE WHEN n.nspparent <> 0 THEN g.nspname ELSE n.nspname END AS schemaname, + NOT (l.lanname = 'edbspl' AND protype = '1') AS isfunc, + {%else%} + 0 AS pkg, + '' AS pkgname, + 0 AS pkgconsoid, + n.oid AS schema, + n.nspname AS schemaname, + true AS isfunc, + {%endif%} + pg_catalog.pg_get_function_identity_arguments(p.oid) AS signature, + + {% if hasFeatureFunctionDefaults %} + pg_catalog.pg_get_expr(p.proargdefaults, 'pg_catalog.pg_class'::regclass, false) AS proargdefaults, + p.pronargdefaults + {%else%} + '' AS proargdefaults, 0 AS pronargdefaults + {%endif%} + FROM + pg_catalog.pg_proc p + LEFT JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + LEFT JOIN pg_catalog.pg_language l ON p.prolang = l.oid + LEFT JOIN pg_catalog.pg_type y ON p.prorettype = y.oid + + {% if is_ppas_database %} + LEFT JOIN pg_catalog.pg_namespace g ON n.nspparent = g.oid + {% endif %} + + {% if fid %} + WHERE p.oid = {{fid}}::int; + {% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_stack_info.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_stack_info.sql new file mode 100755 index 0000000..05034c9 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_stack_info.sql @@ -0,0 +1,4 @@ +{### Get the stack information for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_get_stack({{session_id}}::int) ORDER BY level +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_variables.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_variables.sql new file mode 100755 index 0000000..47ebed5 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/get_variables.sql @@ -0,0 +1,8 @@ +{### Get the variables information for debugging ###} +{% if session_id %} + SELECT + name, varClass, value, + pg_catalog.format_type(dtype, NULL) as dtype, isconst + FROM pldbg_get_variables({{session_id}}::int) + ORDER BY varClass +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/select_frame.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/select_frame.sql new file mode 100755 index 0000000..e31e634 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/select_frame.sql @@ -0,0 +1,12 @@ +{### select the frame to debug the function ###} +{% if session_id and frame_id %} + SELECT + p.pkg AS pkg, p.func AS func, p.targetName AS targetName, + p.linenumber AS linenumber, + CASE WHEN p.func <> 0 THEN pldbg_get_source({{session_id}}::INTEGER, p.func, p.pkg) ELSE '' END AS src, + (SELECT + s.args + FROM pldbg_get_stack({{session_id}}::INTEGER) s + WHERE s.func = p.func AND s.pkg = p.pkg) AS args + FROM pldbg_select_frame({{session_id}}::INTEGER, {{frame_id}}::INTEGER) p +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/set_breakpoint.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/set_breakpoint.sql new file mode 100755 index 0000000..61c8b13 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/set_breakpoint.sql @@ -0,0 +1,4 @@ +{### Set the breakpoints for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_set_breakpoint({{session_id}}::int ,{{poid}}::OID, {{foid}}::OID, {{line_number}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/step_into.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/step_into.sql new file mode 100755 index 0000000..837287d --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/step_into.sql @@ -0,0 +1,11 @@ +{### Step into function for debugging ###} +{% if session_id %} + SELECT + p.pkg AS pkg, p.func AS func, p.targetName AS targetName, + p.linenumber AS linenumber, pldbg_get_source({{session_id}}::INTEGER, p.pkg, p.func) AS src, + (SELECT + s.args + FROM pldbg_get_stack({{session_id}}::INTEGER) s + WHERE s.func = p.func AND s.pkg = p.pkg) AS args + FROM pldbg_step_into({{session_id}}::INTEGER) p +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/step_over.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/step_over.sql new file mode 100755 index 0000000..a5ced66 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/step_over.sql @@ -0,0 +1,11 @@ +{### Step over function for debugging ###} +{% if session_id %} + SELECT + p.pkg AS pkg, p.func AS func, p.targetName AS targetName, + p.linenumber AS linenumber, pldbg_get_source({{session_id}}::INTEGER, p.pkg, p.func) AS src, + (SELECT + s.args + FROM pldbg_get_stack({{session_id}}::INTEGER) s + WHERE s.func = p.func AND s.pkg = p.pkg) AS args + FROM pldbg_step_over({{session_id}}::INTEGER) p +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/wait_for_breakpoint.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/wait_for_breakpoint.sql new file mode 100755 index 0000000..0271056 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/wait_for_breakpoint.sql @@ -0,0 +1,11 @@ +{### select the frame to debug the function ###} +{% if session_id %} + SELECT + p.pkg AS pkg, p.func AS func, p.targetName AS targetName, + p.linenumber AS linenumber, pldbg_get_source({{session_id}}::INTEGER, p.pkg, p.func) AS src, + (SELECT + s.args + FROM pldbg_get_stack({{session_id}}::INTEGER) s + WHERE s.func = p.func AND s.pkg = p.pkg) AS args + FROM pldbg_wait_for_breakpoint({{session_id}}::INTEGER) p; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v1/wait_for_target.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/wait_for_target.sql new file mode 100755 index 0000000..c4f5634 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v1/wait_for_target.sql @@ -0,0 +1,4 @@ +{### Wait for the target for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_wait_for_target({{session_id}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/Backend_running.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/Backend_running.sql new file mode 100755 index 0000000..c5770d0 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/Backend_running.sql @@ -0,0 +1,4 @@ +{### Check backend target is running or not for debugging ###} +{% if backend_pid %} + SELECT COUNT(*) FROM (SELECT pg_catalog.pg_stat_get_backend_idset() AS bid) AS s WHERE pg_catalog.pg_stat_get_backend_pid(s.bid) = {{ conn|qtIdent(backend_pid) }}::integer; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/abort_target.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/abort_target.sql new file mode 100755 index 0000000..8f89ae3 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/abort_target.sql @@ -0,0 +1,4 @@ +{### Abort the target for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_abort_target({{session_id}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/add_breakpoint_edb.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/add_breakpoint_edb.sql new file mode 100755 index 0000000..0129462 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/add_breakpoint_edb.sql @@ -0,0 +1,4 @@ +{### Add EDB breakpoints for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_set_global_breakpoint({{session_id}}::int, {{package_oid}}::int, {{function_oid}}::OID, -1, {{target_oid}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/add_breakpoint_pg.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/add_breakpoint_pg.sql new file mode 100755 index 0000000..f83e1cf --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/add_breakpoint_pg.sql @@ -0,0 +1,4 @@ +{### Add PG breakpoint for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_set_global_breakpoint({{session_id}}, {{function_oid}}, -1, NULL) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/attach_to_port.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/attach_to_port.sql new file mode 100755 index 0000000..bb74477 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/attach_to_port.sql @@ -0,0 +1,4 @@ +{### Attach the target to port for debugging ###} +{% if port %} + SELECT * FROM pldbg_attach_to_port({{port}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/clear_breakpoint.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/clear_breakpoint.sql new file mode 100755 index 0000000..c3d83f8 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/clear_breakpoint.sql @@ -0,0 +1,4 @@ +{### Clear breakpoints for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_drop_breakpoint({{session_id}}::int, {{foid}}::OID, {{line_number}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/continue.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/continue.sql new file mode 100755 index 0000000..85dcd27 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/continue.sql @@ -0,0 +1,11 @@ +{### Continue for debugging ###} +{% if session_id %} + SELECT + p.func, p.targetName, p.linenumber, + pldbg_get_source({{session_id}}::INTEGER, p.func) AS src, + (SELECT + s.args + FROM pldbg_get_stack({{session_id}}::INTEGER) s + WHERE s.func = p.func) AS args + FROM pldbg_continue({{session_id}}::INTEGER) p +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/create_listener.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/create_listener.sql new file mode 100755 index 0000000..1d47739 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/create_listener.sql @@ -0,0 +1,2 @@ +{### Create listener for debugging ###} + SELECT * from pldbg_create_listener() diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_plpgsql_execute_target.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_plpgsql_execute_target.sql new file mode 100755 index 0000000..9bdb8ef --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_plpgsql_execute_target.sql @@ -0,0 +1,4 @@ +{### Debug execute target for plpgsql function ###} +{% if function_oid %} + SELECT plpgsql_oid_debug({{function_oid}}::OID) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_plpgsql_init.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_plpgsql_init.sql new file mode 100755 index 0000000..e56ad98 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_plpgsql_init.sql @@ -0,0 +1,4 @@ +{### Debug Initialization for plpgsql function ###} +{% if packge_init_oid %} + SELECT plpgsql_oid_debug({{packge_init_oid}}::OID) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_spl_execute_target.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_spl_execute_target.sql new file mode 100755 index 0000000..7cb382f --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_spl_execute_target.sql @@ -0,0 +1,4 @@ +{### Debug execute target for EDB spl function ###} +{% if function_oid %} + SELECT edb_oid_debug({{function_oid}}::OID) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_spl_init.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_spl_init.sql new file mode 100755 index 0000000..d16b1b1 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/debug_spl_init.sql @@ -0,0 +1,4 @@ +{### Debug Initialization for EDB spl function ###} +{% if packge_init_oid %} + SELECT edb_oid_debug({{packge_init_oid}}::OID) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/deposit_value.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/deposit_value.sql new file mode 100755 index 0000000..6ee3259 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/deposit_value.sql @@ -0,0 +1,4 @@ +{### Change the variable value and submit during debugging ###} +{% if session_id %} + SELECT * FROM pldbg_deposit_value({{session_id}}::int, {{var_name|qtLiteral }}, {{line_number}}, {{val|qtLiteral}}) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_breakpoints.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_breakpoints.sql new file mode 100755 index 0000000..19da5db --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_breakpoints.sql @@ -0,0 +1,4 @@ +{### Get the breakpoint information for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_get_breakpoints({{session_id}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_function_info.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_function_info.sql new file mode 100755 index 0000000..7989da7 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_function_info.sql @@ -0,0 +1,66 @@ +{### To fetch debug function information ###} +SELECT + p.proname AS name, l.lanname, p.proretset, p.prorettype, y.typname AS rettype, + CASE WHEN proallargtypes IS NOT NULL THEN + pg_catalog.array_to_string(ARRAY( + SELECT + pg_catalog.format_type(p.proallargtypes[s.i], NULL) + FROM + pg_catalog.generate_series(0, pg_catalog.array_upper( + p.proallargtypes, 1)) AS s(i)), ',') + ELSE + pg_catalog.array_to_string(ARRAY( + SELECT + pg_catalog.format_type(p.proargtypes[s.i], NULL) + FROM + pg_catalog.generate_series(0, pg_catalog.array_upper( + p.proargtypes, 1)) AS s(i)), ',') + END AS proargtypenames, + CASE WHEN proallargtypes IS NOT NULL THEN + pg_catalog.array_to_string(ARRAY( + SELECT proallargtypes[s.i] FROM + pg_catalog.generate_series(0, pg_catalog.array_upper(proallargtypes, 1)) s(i)), ',') + ELSE + pg_catalog.array_to_string(ARRAY( + SELECT proargtypes[s.i] FROM + 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, + pg_catalog.array_to_string(proargmodes, ',') AS proargmodes, + + {% 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, + CASE WHEN n.nspparent <> 0 THEN (SELECT oid FROM pg_proc WHERE pronamespace=n.oid AND proname='cons') ELSE 0 END AS pkgconsoid, + CASE WHEN n.nspparent <> 0 THEN g.oid ELSE n.oid END AS schema, + CASE WHEN n.nspparent <> 0 THEN g.nspname ELSE n.nspname END AS schemaname, + NOT (l.lanname = 'edbspl' AND protype = '1') AS isfunc, + {%else%} + 0 AS pkg, + '' AS pkgname, + 0 AS pkgconsoid, + n.oid AS schema, + n.nspname AS schemaname, + true AS isfunc, + {%endif%} + pg_catalog.pg_get_function_identity_arguments(p.oid) AS signature, + + {% if hasFeatureFunctionDefaults %} + pg_catalog.pg_get_expr(p.proargdefaults, 'pg_catalog.pg_class'::regclass, false) AS proargdefaults, + p.pronargdefaults + {%else%} + '' AS proargdefaults, 0 AS pronargdefaults + {%endif%} + FROM + pg_catalog.pg_proc p + LEFT JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + LEFT JOIN pg_catalog.pg_language l ON p.prolang = l.oid + LEFT JOIN pg_catalog.pg_type y ON p.prorettype = y.oid + + {% if is_ppas_database %} + LEFT JOIN pg_catalog.pg_namespace g ON n.nspparent = g.oid + {% endif %} + + {% if fid %} + WHERE p.oid = {{fid}}::int; + {% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_stack_info.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_stack_info.sql new file mode 100755 index 0000000..05034c9 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_stack_info.sql @@ -0,0 +1,4 @@ +{### Get the stack information for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_get_stack({{session_id}}::int) ORDER BY level +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_variables.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_variables.sql new file mode 100755 index 0000000..47ebed5 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/get_variables.sql @@ -0,0 +1,8 @@ +{### Get the variables information for debugging ###} +{% if session_id %} + SELECT + name, varClass, value, + pg_catalog.format_type(dtype, NULL) as dtype, isconst + FROM pldbg_get_variables({{session_id}}::int) + ORDER BY varClass +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/select_frame.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/select_frame.sql new file mode 100755 index 0000000..4a18839 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/select_frame.sql @@ -0,0 +1,11 @@ +{### select the frame to debug the function ###} +{% if session_id %} + SELECT + p.func AS func, p.targetName AS targetName, p.linenumber AS linenumber, + CASE WHEN p.func <> 0 THEN pldbg_get_source({{session_id}}::INTEGER, p.func) ELSE '' END AS src, + (SELECT + s.args + FROM pldbg_get_stack({{session_id}}::INTEGER) s + WHERE s.func = p.func) AS args + FROM pldbg_select_frame({{session_id}}::INTEGER, {{frame_id}}::INTEGER) p +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/set_breakpoint.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/set_breakpoint.sql new file mode 100755 index 0000000..a56d6f6 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/set_breakpoint.sql @@ -0,0 +1,4 @@ +{### Set the breakpoints for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_set_breakpoint({{session_id}}::int, {{foid}}::OID,{{line_number}}::int) +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/step_into.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/step_into.sql new file mode 100755 index 0000000..cd05240 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/step_into.sql @@ -0,0 +1,11 @@ +{### Step into function for debugging ###} +{% if session_id %} + SELECT + p.func, p.targetName, p.linenumber, + pldbg_get_source({{session_id}}::INTEGER, p.func) AS src, + (SELECT + s.args + FROM pldbg_get_stack({{session_id}}::INTEGER) s + WHERE s.func = p.func) AS args + FROM pldbg_step_into({{session_id}}::INTEGER) p +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/step_over.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/step_over.sql new file mode 100755 index 0000000..ed3153d --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/step_over.sql @@ -0,0 +1,11 @@ +{### Step over function for debugging ###} +{% if session_id %} + SELECT + p.func, p.targetName, p.linenumber, + pldbg_get_source({{session_id}}::INTEGER, p.func) AS src, + (SELECT + s.args + FROM pldbg_get_stack({{session_id}}::INTEGER) s + WHERE s.func = p.func) AS args + FROM pldbg_step_over({{session_id}}::INTEGER) p +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/wait_for_breakpoint.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/wait_for_breakpoint.sql new file mode 100755 index 0000000..96efcb8 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/wait_for_breakpoint.sql @@ -0,0 +1,12 @@ +{### select the frame to debug the function ###} +{% if session_id %} + SELECT + p.func AS func, p.targetName AS targetName, + p.linenumber AS linenumber, + pldbg_get_source({{session_id}}::INTEGER, p.func) AS src, + (SELECT + s.args + FROM pldbg_get_stack({{session_id}}::INTEGER) s + WHERE s.func = p.func) AS args + FROM pldbg_wait_for_breakpoint({{session_id}}::INTEGER) p +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/v2/wait_for_target.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/wait_for_target.sql new file mode 100755 index 0000000..c4f5634 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/v2/wait_for_target.sql @@ -0,0 +1,4 @@ +{### Wait for the target for debugging ###} +{% if session_id %} + SELECT * FROM pldbg_wait_for_target({{session_id}}::int) +{% endif %} \ No newline at end of file