diff --git a/web/config.py b/web/config.py index 5f22c88..9cf2644 100644 --- a/web/config.py +++ b/web/config.py @@ -141,7 +141,7 @@ MAX_SESSION_IDLE_TIME = 60 # The schema version number for the configuration database # DO NOT CHANGE UNLESS YOU ARE A PGADMIN DEVELOPER!! -SETTINGS_SCHEMA_VERSION = 8 +SETTINGS_SCHEMA_VERSION = 9 # The default path to the SQLite database used to store user accounts and # settings. This default places the file in the same directory as this diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index 253bd67..b79f085 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -148,3 +148,23 @@ class UserPreference(db.Model): db.Integer, db.ForeignKey('user.id'), primary_key=True ) value = db.Column(db.String(1024), nullable=False) + +class DebuggerFunctionArguments(db.Model): + """Define the debugger input function arguments.""" + __tablename__ = 'debugger_function_arguments' + server_id = db.Column(db.Integer(), nullable=False, primary_key=True) + database_id = db.Column(db.Integer(), nullable=False, primary_key=True) + schema_id = db.Column(db.Integer(), nullable=False, primary_key=True) + function_id = db.Column(db.Integer(), nullable=False, primary_key=True) + arg_id = db.Column(db.Integer(), nullable=False, primary_key=True) + is_null = db.Column(db.Integer(), + db.CheckConstraint('is_null >= 0 AND is_null <= 1'), + nullable=False) + is_expression = db.Column(db.Integer(), + db.CheckConstraint('is_expression >= 0 AND is_expression <= 1'), + nullable=False) + use_default = db.Column(db.Integer(), + db.CheckConstraint('use_default >= 0 AND use_default <= 1'), + nullable=False) + + value = db.Column(db.String(), nullable=True) diff --git a/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js b/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js index e40898e..aa86853 100644 --- a/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js +++ b/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js @@ -564,6 +564,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/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py new file mode 100644 index 0000000..332acd7 --- /dev/null +++ b/web/pgadmin/tools/debugger/__init__.py @@ -0,0 +1,1324 @@ +########################################################################## +# +# 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, current_app +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.model import db, DebuggerFunctionArguments +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 + }) + + 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") + + +def update_session_debugger_transaction(trans_id, data): + """Update the session variables required for debugger with transaction ID""" + debugger_data = session['debuggerData'] + debugger_data[str(trans_id)] = data + + session['debuggerData'] = debugger_data + + +def update_session_function_transaction(trans_id, data): + """Update the session variables of functions required to debug with transaction ID""" + function_data = session['functionData'] + function_data[str(trans_id)] = data + session['functionData'] = function_data + + +@blueprint.route('/init/////', methods=['GET']) +@login_required +def init_function(node_type, sid, did, scid, fid): + """ + init_function(node_type, 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: + node_type + - Node type - Function or Procedure + 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: + current_app.logger.debug("Error getting function information from database") + 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: + current_app.logger.debug("ERROR: Couldn't fetch debugger target information") + 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: + current_app.logger.debug("Error: Function/Procedure can not be debug") + return internal_server_error("ERROR: Function/Procedure can not be debug.") + + # Store the function information in session variable + if 'funcData' not in session: + function_data = dict() + else: + function_data = session['funcData'] + + 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'], + 'use_default': r_set['rows'][0]['pronargdefaults'], + 'default_value': r_set['rows'][0]['proargdefaults'], + 'pkg': r_set['rows'][0]['pkg'], + 'args_value': '' + } + + session['funcData'] = 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 + + status, rid = conn.execute_scalar( + "SELECT proxyapiver FROM pldbg_get_proxy_info();" + ) + + if status: + if rid == 2 or rid == 3: + debugger_version = rid; + else: + status = False; + + # Add the debugger version information to pgadmin4 log file + current_app.logger.debug("Debugger version is: %d", debugger_version) + + # 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['funcData'] + d['args_value'] = data + session['funcData'] = 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, + 'schema_id': scid, + 'function_id': func_id, + 'debug_type': debug_type, + 'debugger_version': debugger_version, + 'frame_id': 0, + 'restart_debug': 0 + } + + # Store the grid dictionary into the session variable + session['debuggerData'] = debugger_data + + # Update the function session information with same transaction id + func_data = session['funcData'] + + # Store the function information in session variable + if 'functionData' not in session: + function_data = dict() + else: + function_data = session['functionData'] + + function_data[str(trans_id)] = { + 'oid': func_data['oid'], + 'name': func_data['name'], + 'is_func': func_data['is_func'], + 'is_callable': func_data['is_callable'], + 'schema': func_data['schema'], + 'language': func_data['language'], + 'return_type': func_data['return_type'], + 'args_type': func_data['args_type'], + 'args_name': func_data['args_name'], + 'arg_mode': func_data['arg_mode'], + 'use_default': func_data['use_default'], + 'default_value': func_data['default_value'], + 'pkg': func_data['pkg'], + 'args_value': func_data['args_value'] + } + + # Update the session variable of function information + session['functionData'] = function_data; + + # Delete the 'funcData' session variables as it is not used now as target is initialized + del session['funcData'] + + 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 session['debuggerData'][str(trans_id)] + del session['functionData'][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('/restart/', methods=['GET']) +@login_required +def restart_debugging(trans_id): + """ + restart_debugging(trans_id) + + This method is responsible to restart the same function for the 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']) + + if conn.connected(): + # Update the session variable "restart_debug" to know that same function debugging has been restarted. + # Delete the existing debugger data in session variable and update with new data + if obj['restart_debug'] == 0: + debugger_data = session['debuggerData'] + session_obj = debugger_data[str(trans_id)] + session_obj['restart_debug'] = 1 + update_session_debugger_transaction(trans_id, session_obj) + + # Store the function information in session variable + if 'functionData' not in session: + function_data = dict() + else: + function_data = { + 'server_id': obj['server_id'], + 'database_id': obj['database_id'], + 'schema_id': obj['schema_id'], + 'function_id': obj['function_id'], + 'trans_id': str(trans_id), + 'proargmodes': session['functionData'][str(trans_id)]['arg_mode'], + 'proargtypenames': session['functionData'][str(trans_id)]['args_type'], + 'pronargdefaults': session['functionData'][str(trans_id)]['use_default'], + 'proargdefaults': session['functionData'][str(trans_id)]['default_value'], + 'proargnames': session['functionData'][str(trans_id)]['args_name'] + } + + return make_json_response(data={'status': True, 'restart_debug': True, 'result': function_data}) + else: + status = False + result = gettext('Not connected to server Or connection with the server has been closed.') + + return make_json_response(data={'status': status}) + + +@blueprint.route('/start_listener/', methods=['GET', 'POST']) +@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 user again start the same debug function with different arguments then we need to save that values to session + # variable and database. + if request.method == 'POST': + data = json.loads(request.values['data']) + if data: + function_data = session['functionData'] + session_obj = function_data[str(trans_id)] + session_obj['args_value'] = data + update_session_function_transaction(trans_id, session_obj) + + 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'][str(trans_id)]['schema'] + '.' + session['functionData'][str(trans_id)]['name'] + + if obj['restart_debug'] == 0: + # render the SQL template and send the query to server + if session['functionData'][str(trans_id)]['language'] == 'plpgsql': + sql = render_template("/".join([template_path, 'debug_plpgsql_execute_target.sql']), + packge_oid=session['functionData'][str(trans_id)]['pkg'], + function_oid=obj['function_id']) + else: + sql = render_template("/".join([template_path, 'debug_spl_execute_target.sql']), + packge_oid=session['functionData'][str(trans_id)]['pkg'], + function_oid=obj['function_id']) + status, res = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + if session['functionData'][str(trans_id)]['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'][str(trans_id)]['args_name']: + arg_name = session['functionData'][str(trans_id)]['args_name'].split(","); + if '_retval_' in arg_name: + arg_mode = session['functionData'][str(trans_id)]['arg_mode'].split(","); + arg_mode.pop(); + else: + arg_mode = session['functionData'][str(trans_id)]['arg_mode'].split(","); + else: + arg_mode = session['functionData'][str(trans_id)]['arg_mode'].split(","); + else: + arg_mode = ['i'] * len(session['functionData'][str(trans_id)]['args_type'].split(",")); + + if session['functionData'][str(trans_id)]['args_type']: + if session['functionData'][str(trans_id)]['args_name']: + arg_name = session['functionData'][str(trans_id)]['args_name'].split(","); + if '_retval_' in arg_name: + arg_type = session['functionData'][str(trans_id)]['args_type'].split(","); + arg_type.pop(); + else: + arg_type = session['functionData'][str(trans_id)]['args_type'].split(","); + else: + arg_type = session['functionData'][str(trans_id)]['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'][str(trans_id)]['is_func'], + lan_name=session['functionData'][str(trans_id)]['language'], + ret_type=session['functionData'][str(trans_id)]['return_type'], + data=session['functionData'][str(trans_id)]['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'][str(trans_id)]['is_func'], + ret_type=session['functionData'][str(trans_id)]['return_type'], + data=session['functionData'][str(trans_id)]['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) + + debugger_data = session['debuggerData'] + session_obj = debugger_data[str(trans_id)] + session_obj['exe_conn_id'] = obj['conn_id'] + session_obj['restart_debug'] = 1 + session_obj['frame_id'] = 0 + session_obj['session_id'] = int_session_id + update_session_debugger_transaction(trans_id, session_obj) + 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': + # We should set the frame_id to 0 when execution starts. + if obj['frame_id'] != 0: + session_obj = debugger_data[str(trans_id)] + session_obj['frame_id'] = 0 + update_session_debugger_transaction(trans_id, session_obj) + + 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 session['debuggerData'][str(trans_id)] + del session['functionData'][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. + # Notice message returned by the server is "NOTICE: PLDBGBREAK:7". + # From the above message we need to find out port number as "7" so below logic will find 7 as port number + # and attach listened to that port number + 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'] + + # 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) + + session_obj = debugger_data[str(trans_id)] + session_obj['restart_debug'] = 0 + session_obj['frame_id'] = 0 + session_obj['exe_conn_id'] = exe_conn_id + session_obj['debugger_version'] = dbg_version + session_obj['session_id'] = res_port['rows'][0]['pldbg_attach_to_port'] + update_session_debugger_transaction(trans_id, session_obj) + + 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) + + # For multilevel function debugging, we need to fetch current selected frame's function oid for setting the + # breakpoint. For single function the frame id will be 0. + foid = res_stack['rows'][obj['frame_id']]['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' + + session_obj = debugger_data[str(trans_id)] + session_obj['frame_id'] = frame_id + update_session_debugger_transaction(trans_id, session_obj) + + 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('/get_arguments////', methods=['GET']) +@login_required +def get_arguments_sqlite(sid, did, scid, func_id): + """ + get_arguments_sqlite(sid, did, scid, func_id) + + This method is responsible to get the function arguments saved to sqlite database during first debugging. + + Parameters: + sid + - Server Id + did + - Database Id + scid + - Schema Id + func_id + - Function Id + """ + + """Get the count of the existing data available in sqlite database""" + DbgFuncArgsCount = DebuggerFunctionArguments.query.filter_by( + server_id=sid, database_id=did, schema_id=scid, function_id=func_id).count() + + args_data = [] + + if DbgFuncArgsCount: + """Update the Debugger Function Arguments settings""" + DbgFuncArgs = DebuggerFunctionArguments.query.filter_by( + server_id=sid, database_id=did, schema_id=scid, function_id=func_id) + + args_list = DbgFuncArgs.all() + + for i in range(0, DbgFuncArgsCount): + info = { + "arg_id": args_list[i].arg_id, + "is_null": args_list[i].is_null, + "is_expression": args_list[i].is_expression, + "use_default": args_list[i].use_default, + "value": args_list[i].value + } + args_data.append(info) + + # As we do have entry available for that function so we need to add that entry + return make_json_response(data={'result': args_data, 'args_count': DbgFuncArgsCount}) + else: + # As we do not have any entry available for that function so we need to add that entry + return make_json_response(data={'result': 'result', 'args_count': DbgFuncArgsCount}) + + +@blueprint.route('/set_arguments////', methods=['POST']) +@login_required +def set_arguments_sqlite(sid, did, scid, func_id): + """ + set_arguments_sqlite(sid, did, scid, func_id) + + This method is responsible for setting the value of function arguments to sqlite database + + Parameters: + sid + - Server Id + did + - Database Id + scid + - Schema Id + func_id + - Function Id + """ + + if request.values['data']: + data = json.loads(request.values['data']) + + try: + for i in range(0, len(data)): + DbgFuncArgsExists = DebuggerFunctionArguments.query.filter_by( + server_id=data[i]['server_id'], database_id=data[i]['database_id'], schema_id=data[i]['schema_id'], + function_id=data[i]['function_id'], arg_id=data[i]['arg_id']).count() + + # Check if data is already available in database then update the existing value otherwise add the new value + if DbgFuncArgsExists: + DbgFuncArgs = DebuggerFunctionArguments.query.filter_by( + server_id=data[i]['server_id'], database_id=data[i]['database_id'], schema_id=data[i]['schema_id'], + function_id=data[i]['function_id'], arg_id=data[i]['arg_id']).first() + + DbgFuncArgs.is_null = data[i]['is_null'] + DbgFuncArgs.is_expression = data[i]['is_expression'] + DbgFuncArgs.use_default = data[i]['use_default'] + DbgFuncArgs.value = data[i]['value'] + else: + debugger_func_args = DebuggerFunctionArguments( + server_id = data[i]['server_id'], + database_id = data[i]['database_id'], + schema_id = data[i]['schema_id'], + function_id = data[i]['function_id'], + arg_id = data[i]['arg_id'], + is_null = data[i]['is_null'], + is_expression = data[i]['is_expression'], + use_default = data[i]['use_default'], + value = data[i]['value'] + ) + + db.session.add(debugger_func_args) + + db.session.commit() + + except Exception as e: + current_app.logger.exception(e) + return make_json_response( + status=410, + success=0, + errormsg=e.message + ) + + return make_json_response(data={'status': True, 'result': 'Success'}) + + +@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']) + conn = manager.connection(did=obj['database_id'], conn_id=obj['conn_id']) + + if conn.connected(): + statusmsg = conn.status_message() + status, result, my_result = conn.poll() + if status == ASYNC_OK and session['functionData'][str(trans_id)]['language'] == 'edbspl': + status = 'Success' + return make_json_response(success=1, info=gettext("Execution Completed."), + data={'status': status, 'status_message': statusmsg}) + 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 and result is not None: + 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..2dceb53 --- /dev/null +++ b/web/pgadmin/tools/debugger/static/css/debugger.css @@ -0,0 +1,72 @@ +.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; +} + +.top-container .breakpoints { + width: 0.9em; +} + +.top-container .CodeMirror-activeline-background { + background: #50B0F0; +} \ No newline at end of file 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..718ef2f --- /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..6d24848 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/js/debugger.js @@ -0,0 +1,329 @@ +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' + },{ + name: 'procedure_direct_debugger', node: 'procedure', module: this, + applies: ['object', 'context'], callback: 'get_function_information', + category: 'Debugging', priority: 10, label: '{{ _('Debug') }}', + data: {object: 'procedure'}, icon: 'fa fa-arrow-circle-right', + enable: 'can_debug' + }, { + name: 'procedure_indirect_debugger', node: 'procedure', module: this, + applies: ['object', 'context'], callback: 'check_func_debuggable', + category: 'Debugging', priority: 10, label: '{{ _('Set breakpoint') }}', + data: {object: 'procedure'}, 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]); + + if (d._type == "function") { + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "indirect/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.function._id; + } + else { + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "indirect/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.procedure._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], 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]); + + if (d._type == "function") { + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "direct/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.function._id; + } + else { + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "direct/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.procedure._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..8b58bf3 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/js/debugger_ui.js @@ -0,0 +1,657 @@ +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": + // As we are getting this value as text from sqlite database so we need to type cast it. + if (model.get('value') != undefined) { + model.set({'value': parseInt(model.get('value'))},{silent:true}); + } + + return Backgrid.IntegerCell; + break; + + case "real": + // As we are getting this value as text from sqlite database so we need to type cast it. + if (model.get('value') != undefined) { + model.set({'value': parseFloat(model.get('value'))} ,{silent:true}); + } + return Backgrid.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, restart_debug) { + if (!Alertify.debuggerInputArgsDialog) { + Alertify.dialog('debuggerInputArgsDialog', function factory() { + return { + main:function(title, data, restart_debug) { + this.set('title', title); + this.data = data; + this.restart_debug = restart_debug; + + // Variables to store the data sent from sqlite database + var func_args_data = this.func_args_data = []; + + // As we are not getting pgBrowser.tree when we debug again so tree info will be updated from the server data + if (restart_debug == 0) { + 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]); + + if (d._type == "function") { + // Get the existing function parameters available from sqlite database + var _Url = "{{ url_for('debugger.index') }}" + "get_arguments/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.function._id; + } + else { + // Get the existing function parameters available from sqlite database + var _Url = "{{ url_for('debugger.index') }}" + "get_arguments/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.procedure._id; + } + } + else { + // Get the existing function parameters available from sqlite database + var _Url = "{{ url_for('debugger.index') }}" + "get_arguments/" + this.data.server_id + + "/" + this.data.database_id + "/" + this.data.schema_id + "/" + this.data.function_id; + } + + $.ajax({ + url: _Url, + method: 'GET', + async: false, + success: function(res) { + if (res.data.args_count != 0) { + for (i = 0; i < res.data.result.length; i++) { + // Below will format the data to be stored in sqlite database + func_args_data.push({ + 'arg_id': res.data.result[i]['arg_id'], + 'is_null': res.data.result[i]['is_null'], + 'is_expression': res.data.result[i]['is_expression'], + 'use_default': res.data.result[i]['use_default'], + 'value': res.data.result[i]['value'] + }); + } + } + }, + error: function(e) { + Alertify.alert( + 'Debugger Set arguments error' + ); + } + }); + + 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 = []; + var func_obj = []; + + // Below will calculate the input argument id required to store in sqlite database + var input_arg_id = this.input_arg_id = []; + if (this.data['proargmodes'] != null) { + argmode_1 = this.data['proargmodes'].split(","); + for (k = 0; k < argmode_1.length; k++) { + if (argmode_1[k] == 'i' || argmode_1[k] == 'b') { + input_arg_id.push(k) + } + } + } + else { + argtype_1 = this.data['proargtypenames'].split(","); + for (k = 0; k < argtype_1.length; k++) { + input_arg_id.push(k) + } + } + + 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]}); + } + + + } + } + + // Need to update the func_obj variable from sqlite database if available + // TODO: Need to check, how to update the value in Array fields.... + if (func_args_data.length != 0) { + for (i = 0; i < func_args_data.length; i++) { + var index = func_args_data[i]['arg_id']; + func_obj.push({ "name": argname[index], "type": argtype[index], "is_null": func_args_data[i]['is_null'] ? true: false, "expr": func_args_data[i]['is_expression']? true: false, "value": func_args_data[i]['value'], "use_default": func_args_data[i]['use_default']? true: false, "default_value": def_val_list[index]}); + } + } + } + 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": ""}); + def_val_list[i] = ""; + } + } + 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]}); + } + } + } + } + + // Need to update the func_obj variable from sqlite database if available + // TODO: Need to check, how to update the value in Array fields.... + if (func_args_data.length != 0) { + for (i = 0; i < func_args_data.length; i++) { + var index = func_args_data[i]['arg_id']; + func_obj.push({ "name": myargname[index], "type": argtype[index], "is_null": func_args_data[i]['is_null'] ? true: false, "expr": func_args_data[i]['is_expression']? true: false, "value": func_args_data[i]['value'], "use_default": func_args_data[i]['use_default']? true: false, "default_value": def_val_list[index]}); + } + } + } + + // Check if the arguments already available in the sqlite database then we should use the existing arguments + if (func_args_data.length == 0) { + var debuggerInputArgsColl = this.debuggerInputArgsColl = new DebuggerInputArgCollections(my_obj); + } + else { + var debuggerInputArgsColl = this.debuggerInputArgsColl = new DebuggerInputArgCollections(func_obj); + } + + // Initialize a new Grid instance + if (this.grid) { + this.grid.remove(); + this.grid = null; + } + 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; + + // If the debugging is started again then treeInfo is already stored in this.data so we can use the same. + if (self.restart_debug == 0) { + 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 = []; + var sqlite_func_args_list = this.sqlite_func_args_list = []; + var int_count = 0; + + this.grid.collection.each(function(m) { + + // TODO: Removed temporary for testing..... + // Check if value is set to NULL then we should ignore the value field + if (m.get('is_null')) { + args_value_list.push({ 'name': m.get('name'), + 'type': m.get('type'), + 'value': 'NULL'}); + } + else { + // 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')}); + } + } + + if (self.restart_debug == 0) { + // Below will format the data to be stored in sqlite database + sqlite_func_args_list.push({ + 'server_id': treeInfo.server._id, + 'database_id': treeInfo.database._id, + 'schema_id': treeInfo.schema._id , + 'function_id': d._type == "function" ? treeInfo.function._id : treeInfo.procedure._id, + 'arg_id': self.input_arg_id[int_count], + 'is_null': m.get('is_null') ? 1 : 0, + 'is_expression': m.get('expr') ? 1 : 0, + 'use_default': m.get('use_default') ? 1 : 0, + 'value': m.get('value') + }); + } + else { + // Below will format the data to be stored in sqlite database + sqlite_func_args_list.push({ + 'server_id': self.data.server_id, + 'database_id': self.data.database_id, + 'schema_id': self.data.schema_id , + 'function_id': self.data.function_id, + 'arg_id': self.input_arg_id[int_count], + 'is_null': m.get('is_null') ? 1 : 0, + 'is_expression': m.get('expr') ? 1 : 0, + 'use_default': m.get('use_default') ? 1 : 0, + 'value': m.get('value') + }); + } + + int_count = int_count + 1; + }); + + // If debugging is not started again then we should initialize the target otherwise not + if (self.restart_debug == 0) { + if (d._type == "function") { + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "direct/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.function._id; + } + else { + var baseUrl = "{{ url_for('debugger.index') }}" + "initialize_target/" + "direct/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.procedure._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' + }); + }); + + if (d._type == "function") { + var _Url = "{{ url_for('debugger.index') }}" + "set_arguments/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.function._id; + } + else { + var _Url = "{{ url_for('debugger.index') }}" + "set_arguments/" + treeInfo.server._id + + "/" + treeInfo.database._id + "/" + treeInfo.schema._id + "/" + treeInfo.procedure._id; + } + + $.ajax({ + url: _Url, + method: 'POST', + data:{'data':JSON.stringify(sqlite_func_args_list)}, + success: function(res) { + }, + error: function(e) { + Alertify.alert( + 'Debugger Set arguments error' + ); + } + }); + }, + error: function(e) { + Alertify.alert( + 'Debugger target Initialize Error', + e.responseJSON.errormsg + ); + } + }); + } + else { + // If the debugging is started again then we should only set the arguments and start the listener again + var baseUrl = "{{ url_for('debugger.index') }}" + "start_listener/" + self.data.trans_id; + + $.ajax({ + url: baseUrl, + method: 'POST', + data:{'data':JSON.stringify(args_value_list)}, + success: function(res) { + //TODO: Anything required ? ..... + }, + error: function(e) { + Alertify.alert( + 'Debugger listener starting error', + e.responseJSON.errormsg + ); + } + }); + + // Set the new input arguments given by the user during debugging + var _Url = "{{ url_for('debugger.index') }}" + "set_arguments/" + self.data.server_id + + "/" + self.data.database_id + "/" + self.data.schema_id + "/" + self.data.function_id; + + $.ajax({ + url: _Url, + method: 'POST', + data:{'data':JSON.stringify(sqlite_func_args_list)}, + success: function(res) { + }, + error: function(e) { + Alertify.alert( + 'Debugger Set arguments error' + ); + } + }); + + } + + return true; + } + + if (e.button.text === "Cancel") { + //close the dialog... + return false; + } + }, + build:function() { + }, + prepare:function() { + /* + If we already have data available in sqlite database then we should enable the debug button otherwise + disable the debug button. + */ + if (this.func_args_data.length == 0) { + this.__internal.buttons[0].element.disabled = true; + } + else { + this.__internal.buttons[0].element.disabled = false; + } + + /* + Listen to the grid change event so that if any value changed by user then we can enable/disable the + debug button. + */ + 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, restart_debug).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..bc86b0d --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/js/direct.js @@ -0,0 +1,1388 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin','pgadmin.browser', + 'backbone', 'backgrid', 'codemirror', 'backform','pgadmin.tools.debugger.ui', + 'wcdocker', 'pgadmin.backform', 'pgadmin.backgrid', 'codemirror/addon/selection/active-line'], + function($, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, CodeMirror, Backform, debug_function_again) { + + if (pgAdmin.Browser.tree != null) { + 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 no result then poll again to wait for results. + if (res.data.result == null || res.data.result.length == 0) { + // NEEL: THIS IS CONDITION IS ADDED + self.poll_result(trans_id); + } + else { + 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 { + //TODO: NEEL ADDED + 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' + ); + } + }); + }, 1000 ); + + }, + + /* + 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; + + setTimeout( + function() { + $.ajax({ + url: baseUrl, + method: 'GET', + success: function(res) { + if (res.data.status === 'Success') { + if(res.data.result == undefined ) { + /* + "result" is undefined only in case of EDB procedure. As Once the EDB procedure execution is completed + then we are not getting any result so we need ignore the result. + */ + pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + pgTools.DirectDebug.direct_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); + } + else { + // 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); + pgTools.DirectDebug.direct_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); + + // NEEL: TODO: Added Execution is completed so again poll the result until user start the another debugging + //self.poll_end_execution_result(pgTools.DirectDebug.trans_id); + } + } + } + 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 + ); + } + }); + }, 1200); + + }, + + Restart: function(trans_id) { + + var baseUrl = "{{ url_for('debugger.index') }}" + "restart/" + trans_id; + + $.ajax({ + url: baseUrl, + success: function(res) { + // Restart the same function debugging with previous arguments + var restart_dbg = res.data.restart_debug ? 1 : 0; + debug_function_again(res.data.result, restart_dbg); + }, + 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) {} + } + }); + }, + + // Continue the execution until the next breakpoint + Continue: function(trans_id) { + var self = this; + + //Check first if previous execution was completed or not + if (pgTools.DirectDebug.direct_execution_completed) { + pgTools.DirectDebug.direct_execution_completed = false; + // TODO: We need to get the arguments given by the user from sqlite database + self.Restart(trans_id); + } + 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); + } + //NEEL: ADDED + //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'); + pgTools.DirectDebug.direct_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; + this.direct_execution_completed = 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 + // indirect debugging - 0 and for direct debugging - 1 + 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; +}); 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..d43f0e2 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/execute_edbspl.sql @@ -0,0 +1,104 @@ +{### 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 %} + +{% 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 ###} +{% if data[input_value_index]['type'] == 'text' and data[input_value_index]['value'] != 'NULL' %} +{% set tmp_val = data[input_value_index]['value']|qtLiteral %} +{% set str_declare = str_declare ~ " := " ~ strParam ~ " " ~ tmp_val ~ "::" ~ data[input_value_index]['type'] %} +{% else %} +{% set str_declare = str_declare ~ " := " ~ strParam ~ " " ~ data[input_value_index]['value'] ~ "::" ~ data[input_value_index]['type'] %} +{% endif %} +{% 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 ###} +{% if data[input_value_index]['type'] == 'text' and data[input_value_index]['value'] != 'NULL' %} +{% set tmp_var = data[input_value_index]['value']|qtLiteral %} +{% set str_statement = str_statement ~ tmp_var ~ "::" ~ data[input_value_index]['type'] %} +{% else %} +{% set str_statement = str_statement ~ data[input_value_index]['value'] ~ "::" ~ data[input_value_index]['type'] %} +{% endif %} +{% 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..ecd75f1 --- /dev/null +++ b/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql @@ -0,0 +1,32 @@ +{### Create executer function for plpgsql function debugging ###} +{% if not is_func %} + 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' and dict_item['value'] != 'NULL' %} +{{ dict_item['value']|qtLiteral }}::{{ dict_item['type'] }}{% if not loop.last %}, {% endif %} +{% elif dict_item['type'] == 'text' and dict_item['value'] == 'NULL' %} +{{ dict_item['value'] }}::{{ dict_item['type'] }}{% if not loop.last %}, {% endif %} +{% else %} +{% if '[]' in dict_item['type'] %} + ARRAY[ +{% for dict_list in dict_item['value'] %} +{% if 'value' in dict_list %} +{{ dict_list['value']|qtLiteral }}{% if not loop.last %}, {% endif %} +{% endif %} +{% endfor %} +]::{{ 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..7fd6694 --- /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}}, {{function_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..e1c324e --- /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}}, {{function_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..62a8e32 --- /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}}) +{% 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..34e4229 --- /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}}) +{% 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 diff --git a/web/setup.py b/web/setup.py index 690744a..01e69a1 100644 --- a/web/setup.py +++ b/web/setup.py @@ -217,6 +217,21 @@ CREATE TABLE user_preferences ( FOREIGN KEY(uid) REFERENCES user (id) )""") + if int(version.value) < 9: + db.engine.execute(""" +CREATE TABLE IF NOT EXISTS debugger_function_arguments ( + server_id INTEGER , + database_id INTEGER , + schema_id INTEGER , + function_id INTEGER , + arg_id INTEGER , + is_null INTEGER NOT NULL CHECK (is_null >= 0 AND is_null <= 1) , + is_expression INTEGER NOT NULL CHECK (is_expression >= 0 AND is_expression <= 1) , + use_default INTEGER NOT NULL CHECK (use_default >= 0 AND use_default <= 1) , + value TEXT, + PRIMARY KEY (server_id, database_id, schema_id, function_id, arg_id) + )""") + # Finally, update the schema version version.value = config.SETTINGS_SCHEMA_VERSION db.session.merge(version)