codekingpro/portable-devtools
115k
1##########################################################################2#3# pgAdmin 4 - PostgreSQL Tools4#5# Copyright (C) 2013 - 2024, The pgAdmin Development Team6# This software is released under the PostgreSQL Licence7#8##########################################################################9 10"""A blueprint module implementing the debugger"""11 12import json13import secrets14import re15import copy16 17from flask import render_template, request, current_app18from flask_babel import gettext19from pgadmin.user_login_check import pga_login_required20from werkzeug.user_agent import UserAgent21 22from pgadmin.utils import PgAdminModule, \23 SHORTCUT_FIELDS as shortcut_fields, \24 ACCESSKEY_FIELDS as accesskey_fields25from pgadmin.utils.ajax import bad_request26from pgadmin.utils.ajax import make_json_response, \27 internal_server_error, gone28from pgadmin.utils.driver import get_driver29from pgadmin.settings import get_setting30 31from config import PG_DEFAULT_DRIVER32from pgadmin.model import db, DebuggerFunctionArguments33from pgadmin.tools.debugger.utils.debugger_instance import DebuggerInstance34from pgadmin.browser.server_groups.servers.databases.extensions.utils \35 import get_extension_details36from pgadmin.utils.constants import PREF_LABEL_KEYBOARD_SHORTCUTS, \37 SERVER_CONNECTION_CLOSED38from pgadmin.preferences import preferences39 40MODULE_NAME = 'debugger'41 42# Constants43PLDBG_EXTN = 'pldbgapi'44ASYNC_OK = 145DEBUGGER_SQL_PATH = 'debugger/sql'46DEBUGGER_SQL_V1_PATH = 'debugger/sql/v1'47DEBUGGER_SQL_V3_PATH = 'debugger/sql/v3'48 49 50class DebuggerModule(PgAdminModule):51 """52 class DebuggerModule(PgAdminModule)53 54 A module class for debugger which is derived from PgAdminModule.55 """56 LABEL = gettext("Debugger")57 58 def register_preferences(self):59 self.preference.register(60 'keyboard_shortcuts', 'btn_start',61 gettext('Accesskey (Continue/Start)'), 'keyboardshortcut',62 {63 'key': {64 'key_code': 67,65 'char': 'c'66 }67 },68 category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,69 fields=accesskey_fields70 )71 72 self.preference.register(73 'keyboard_shortcuts', 'btn_stop',74 gettext('Accesskey (Stop)'), 'keyboardshortcut',75 {76 'key': {77 'key_code': 83,78 'char': 's'79 }80 },81 category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,82 fields=accesskey_fields83 )84 85 self.preference.register(86 'keyboard_shortcuts', 'btn_step_into',87 gettext('Accesskey (Step into)'), 'keyboardshortcut',88 {89 'key': {90 'key_code': 73,91 'char': 'i'92 }93 },94 category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,95 fields=accesskey_fields96 )97 98 self.preference.register(99 'keyboard_shortcuts', 'btn_step_over',100 gettext('Accesskey (Step over)'), 'keyboardshortcut',101 {102 'key': {103 'key_code': 79,104 'char': 'o'105 }106 },107 category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,108 fields=accesskey_fields109 )110 111 self.preference.register(112 'keyboard_shortcuts', 'btn_toggle_breakpoint',113 gettext('Accesskey (Toggle breakpoint)'), 'keyboardshortcut',114 {115 'key': {116 'key_code': 84,117 'char': 't'118 }119 },120 category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,121 fields=accesskey_fields122 )123 124 self.preference.register(125 'keyboard_shortcuts', 'btn_clear_breakpoints',126 gettext('Accesskey (Clear all breakpoints)'), 'keyboardshortcut',127 {128 'key': {129 'key_code': 88,130 'char': 'x'131 }132 },133 category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,134 fields=accesskey_fields135 )136 137 self.preference.register(138 'keyboard_shortcuts',139 'edit_grid_values',140 gettext('Edit grid values'),141 'keyboardshortcut',142 {143 'alt': True,144 'shift': True,145 'control': False,146 'key': {147 'key_code': 81,148 'char': 'q'149 }150 },151 category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,152 fields=shortcut_fields153 )154 155 self.preference.register(156 'keyboard_shortcuts',157 'move_previous',158 gettext('Previous tab'),159 'keyboardshortcut',160 {161 'alt': True,162 'shift': True,163 'control': False,164 'key': {165 'key_code': 219,166 'char': '['167 }168 },169 category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,170 fields=shortcut_fields171 )172 173 self.preference.register(174 'keyboard_shortcuts',175 'move_next',176 gettext('Next tab'),177 'keyboardshortcut',178 {179 'alt': True,180 'shift': True,181 'control': False,182 'key': {183 'key_code': 221,184 'char': ']'185 }186 },187 category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,188 fields=shortcut_fields189 )190 191 self.preference.register(192 'keyboard_shortcuts',193 'switch_panel',194 gettext('Switch Panel'),195 'keyboardshortcut',196 {197 'alt': True,198 'shift': True,199 'control': False,200 'key': {201 'key_code': 9,202 'char': 'Tab'203 }204 },205 category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,206 fields=shortcut_fields207 )208 209 def get_exposed_url_endpoints(self):210 """211 Returns the list of URLs exposed to the client.212 """213 return ['debugger.index', 'debugger.init_for_function',214 'debugger.init_for_trigger',215 'debugger.direct', 'debugger.initialize_target_for_function',216 'debugger.initialize_target_for_trigger', 'debugger.close',217 'debugger.restart',218 'debugger.start_listener', 'debugger.execute_query',219 'debugger.messages',220 'debugger.start_execution', 'debugger.set_breakpoint',221 'debugger.clear_all_breakpoint', 'debugger.deposit_value',222 'debugger.select_frame', 'debugger.get_arguments',223 'debugger.set_arguments', 'debugger.clear_arguments',224 'debugger.poll_end_execution_result', 'debugger.poll_result'225 ]226 227 def on_logout(self):228 """229 This is a callback function when user logout from pgAdmin230 :param user:231 :return:232 """233 close_debugger_session(None, close_all=True)234 235 236blueprint = DebuggerModule(MODULE_NAME, __name__)237 238 239@blueprint.route("/", endpoint='index')240@pga_login_required241def index():242 return bad_request(243 errormsg=gettext("This URL cannot be called directly.")244 )245 246 247def execute_dict_search_path(conn, sql, search_path):248 sql_search = "SET search_path={0};".format(search_path)249 status, res = conn.execute_void(sql_search)250 251 if not status:252 current_app.logger.debug(253 "Error setting the search path.")254 return False, res255 256 status, res = conn.execute_dict(sql)257 return status, res258 259 260def execute_async_search_path(conn, sql, search_path):261 sql_search = "SET search_path={0};".format(search_path)262 status, res = conn.execute_void(sql_search)263 264 if not status:265 current_app.logger.debug(266 "Error setting the search path.")267 return False, res268 269 status, res = conn.execute_async(sql)270 return status, res271 272 273def check_server_type(server_type, manager):274 """275 Check for server is ppas or not and is_proc_supported or not.276 :param server_type:277 :param manager:278 :return:279 """280 ppas_server = False281 is_proc_supported = False282 if server_type == 'ppas':283 ppas_server = True284 else:285 is_proc_supported = True if manager.version >= 110000 else False286 287 return ppas_server, is_proc_supported288 289 290def check_node_type(node_type, fid, trid, conn, ppas_server,291 is_proc_supported):292 """293 Check node type and return fid.294 :param node_type:295 :param fid:296 :param trid:297 :param conn:298 :param ppas_server:299 :param is_proc_supported:300 :return:301 """302 if node_type == 'trigger':303 # Find trigger function id from trigger id304 sql = render_template(305 "/".join([DEBUGGER_SQL_PATH, 'get_trigger_function_info.sql']),306 table_id=fid, trigger_id=trid307 )308 309 status, tr_set = conn.execute_dict(sql)310 if not status:311 current_app.logger.debug(312 "Error retrieving trigger function information from database")313 return True, internal_server_error(errormsg=tr_set), None, None314 315 fid = tr_set['rows'][0]['tgfoid']316 317 # if ppas server and node type is edb function or procedure then extract318 # last argument as function id319 if node_type == 'edbfunc' or node_type == 'edbproc':320 fid = trid321 322 sql = ''323 sql = render_template(324 "/".join([DEBUGGER_SQL_PATH, 'get_function_debug_info.sql']),325 is_ppas_database=ppas_server,326 hasFeatureFunctionDefaults=True,327 fid=fid,328 is_proc_supported=is_proc_supported329 330 )331 status, r_set = conn.execute_dict(sql)332 if not status:333 current_app.logger.debug(334 "Error retrieving function information from database")335 return True, internal_server_error(errormsg=r_set), None, None336 337 if len(r_set['rows']) == 0:338 return True, gone(339 gettext("The specified %s could not be found." % node_type)), \340 None, None341 342 return False, None, r_set, status343 344 345@blueprint.route(346 '/init/<node_type>/<int:sid>/<int:did>/<int:scid>/<int:fid>',347 methods=['GET'], endpoint='init_for_function'348)349@blueprint.route(350 '/init/<node_type>/<int:sid>/<int:did>/<int:scid>/<int:fid>/<int:trid>',351 methods=['GET'], endpoint='init_for_trigger'352)353@pga_login_required354def init_function(node_type, sid, did, scid, fid, trid=None):355 """356 init_function(node_type, sid, did, scid, fid, trid)357 358 This method is responsible to initialize the function required for359 debugging.360 This method is also responsible for storing the all functions data to361 session variable.362 This is only required for direct debugging. As Indirect debugging does363 not require these data because user will364 provide all the arguments and other functions information through another365 session to invoke the target.366 It will also create a unique transaction id and store the information367 into session variable.368 369 Parameters:370 node_type371 - Node type - Function or Procedure372 sid373 - Server Id374 did375 - Database Id376 scid377 - Schema Id378 fid379 - Function Id380 trid381 - Trigger Function Id382 """383 manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)384 conn = manager.connection(did=did)385 386 # Get the server version, server type and user information387 server_type = manager.server_type388 389 # Check server type is ppas or not390 ppas_server, is_proc_supported = check_server_type(server_type, manager)391 392 is_error, err_msg, r_set, status = check_node_type(node_type, fid, trid,393 conn, ppas_server,394 is_proc_supported)395 if is_error:396 return err_msg397 398 ret_status = status399 msg = ''400 # Check that the function is actually debuggable...401 if r_set['rows'][0]:402 # If func/proc is not defined in package body403 # then it is not debuggable404 if (r_set['rows'][0]['pkgname'] is not None or405 r_set['rows'][0]['pkgname'] != '') and \406 r_set['rows'][0]['prosrc'] == '':407 ret_status = False408 msg = r_set['rows'][0]['name'] + ' ' + \409 gettext("is not defined in package body.")410 411 # Function with a colon in the name cannot be debugged.412 # If this is an EDB wrapped function, no debugging allowed413 # Function with return type "trigger" cannot be debugged.414 elif ":" in r_set['rows'][0]['name']:415 ret_status = False416 msg = gettext(417 "Functions with a colon in the name cannot be debugged.")418 elif ppas_server and r_set['rows'][0]['prosrc'].lstrip().startswith(419 '$__EDBwrapped__$'):420 ret_status = False421 msg = gettext(422 "EDB Advanced Server wrapped functions cannot be debugged.")423 # We cannot debug if PPAS and argument mode is VARIADIC424 elif ppas_server and r_set['rows'][0]['lanname'] == 'edbspl' and \425 r_set['rows'][0]['proargmodes'] is not None and \426 'v' in r_set['rows'][0]['proargmodes']:427 ret_status = False428 msg = gettext(429 "An 'edbspl' target with a variadic argument is not supported"430 " and cannot be debugged."431 )432 else:433 is_error, err_msg, ret_status, msg = check_debugger_enabled(434 conn, ret_status)435 436 if is_error:437 return err_msg438 439 # Return the response that function cannot be debug...440 if not ret_status:441 current_app.logger.debug(msg)442 return internal_server_error(msg)443 444 data = {'name': r_set['rows'][0]['proargnames'],445 'type': r_set['rows'][0]['proargtypenames'],446 'use_default': r_set['rows'][0]['pronargdefaults'],447 'default_value': r_set['rows'][0]['proargdefaults'],448 'require_input': True}449 450 # Below will check do we really required for the user input arguments and451 # show input dialog452 data['require_input'] = check_user_ip_req(r_set, data)453 454 r_set['rows'][0]['require_input'] = data['require_input']455 456 # Create a debugger instance457 de_inst = DebuggerInstance()458 de_inst.function_data = {459 'oid': fid,460 'name': r_set['rows'][0]['name'],461 'is_func': r_set['rows'][0]['isfunc'],462 'is_ppas_database': ppas_server,463 'is_callable': False,464 'schema': r_set['rows'][0]['schemaname'],465 'language': r_set['rows'][0]['lanname'],466 'return_type': r_set['rows'][0]['rettype'],467 'args_type': r_set['rows'][0]['proargtypenames'],468 'args_name': r_set['rows'][0]['proargnames'],469 'arg_mode': r_set['rows'][0]['proargmodes'],470 'use_default': r_set['rows'][0]['pronargdefaults'],471 'default_value': r_set['rows'][0]['proargdefaults'],472 'pkgname': r_set['rows'][0]['pkgname'],473 'pkg': r_set['rows'][0]['pkg'],474 'require_input': data['require_input'],475 'args_value': ''476 }477 478 return make_json_response(479 data=dict(480 debug_info=r_set['rows'],481 trans_id=de_inst.trans_id482 ),483 status=200484 )485 486 487def check_debugger_enabled(conn, ret_status):488 """489 Check debugger enabled or not, also check pldbgapi extension is present490 or not491 :param conn:492 :param ret_status:493 :return:494 """495 msg = ''496 status_in, rid_tar = conn.execute_scalar(497 "SELECT count(*) FROM pg_catalog.pg_proc WHERE "498 "proname = 'pldbg_get_target_info'"499 )500 if not status_in:501 current_app.logger.debug(502 "Failed to find the pldbgapi extension in this database.")503 return True, internal_server_error(504 gettext("Failed to find the pldbgapi extension in "505 "this database.")506 ), None, None507 508 # We also need to check to make sure that the debugger library is509 # also available.510 status_in, ret_oid = conn.execute_scalar(511 "SELECT count(*) FROM pg_catalog.pg_proc WHERE "512 "proname = 'plpgsql_oid_debug'"513 )514 if not status_in:515 current_app.logger.debug(516 "Failed to find the pldbgapi extension in this database.")517 return True, internal_server_error(518 gettext("Failed to find the pldbgapi extension in "519 "this database.")520 ), None, None521 522 # Debugger plugin is configured but pldggapi extension is not523 # created so return error524 if rid_tar == '0' or ret_oid == '0':525 msg = gettext(526 "The debugger plugin is not enabled. Please create the "527 "pldbgapi extension in this database."528 )529 ret_status = False530 return False, None, ret_status, msg531 532 533def check_user_ip_req(r_set, data):534 """535 Check if user input is required or not for debugger.536 :param r_set:537 :param data:538 :return:539 """540 if not r_set['rows'][0]['proargtypenames']:541 return False542 543 if r_set['rows'][0]['pkg'] != 0 and \544 r_set['rows'][0]['pkgconsoid'] != 0:545 data['require_input'] = True546 547 if r_set['rows'][0]['proargmodes']:548 pro_arg_modes = r_set['rows'][0]['proargmodes'].split(",")549 for pr_arg_mode in pro_arg_modes:550 if pr_arg_mode == 'o' or pr_arg_mode == 't':551 data['require_input'] = False552 else:553 data['require_input'] = True554 break555 556 return data['require_input']557 558 559@blueprint.route('/direct/<int:trans_id>', methods=['GET'], endpoint='direct')560@pga_login_required561def direct_new(trans_id):562 de_inst = DebuggerInstance(trans_id)563 564 # Return from the function if transaction id not found565 if de_inst.debugger_data is None:566 return make_json_response(data={'status': True})567 568 # if indirect debugging pass value 0 to client and for direct debugging569 # pass it to 1570 debug_type = 0 if de_inst.debugger_data['debug_type'] == 'indirect' else 1571 572 """573 Animations and transitions are not automatically GPU accelerated and by574 default use browser's slow rendering engine.575 We need to set 'translate3d' value of '-webkit-transform' property in576 order to use GPU.577 After applying this property under linux, Webkit calculates wrong position578 of the elements so panel contents are not visible.579 To make it work, we need to explicitly set '-webkit-transform' property580 to 'none' for .ajs-notifier, .ajs-message, .ajs-modal classes.581 582 This issue is only with linux runtime application and observed in Query583 tool and debugger. When we open 'Open File' dialog then whole Query-tool584 panel content is not visible though it contains HTML element in back end.585 586 The port number should have already been set by the runtime if we're587 running in desktop mode.588 """589 is_linux_platform = False590 591 from sys import platform as _platform592 if "linux" in _platform:593 is_linux_platform = True594 595 # We need client OS information to render correct Keyboard shortcuts596 user_agent = UserAgent(request.headers.get('User-Agent'))597 598 function_arguments = '('599 if de_inst.function_data is not None and \600 'args_name' in de_inst.function_data and \601 de_inst.function_data['args_name'] is not None and \602 de_inst.function_data['args_name'] != '':603 args_name_list = de_inst.function_data['args_name'].split(",")604 args_type_list = de_inst.function_data['args_type'].split(",")605 index = 0606 for args_name in args_name_list:607 function_arguments = '{}{} {}, '.format(function_arguments,608 args_name,609 args_type_list[index])610 index += 1611 # Remove extra comma and space from the arguments list612 if len(args_name_list) > 0:613 function_arguments = function_arguments[:-2]614 615 function_arguments += ')'616 617 layout = get_setting('Debugger/Layout')618 619 function_name_with_arguments = \620 de_inst.debugger_data['function_name'] + function_arguments621 622 manager = get_driver(PG_DEFAULT_DRIVER).get_connection(623 de_inst.debugger_data['server_id'],624 database=de_inst.debugger_data['database_id'],625 conn_id=de_inst.debugger_data['conn_id'])626 title = get_debugger_title(de_inst.debugger_data['function_name'],627 function_arguments,628 de_inst.function_data['schema'], manager.db)629 630 return render_template(631 "debugger/direct.html",632 _=gettext,633 function_name=de_inst.debugger_data['function_name'],634 title=title,635 uniqueId=trans_id,636 debug_type=debug_type,637 is_desktop_mode=current_app.PGADMIN_RUNTIME,638 is_linux=is_linux_platform,639 client_platform=user_agent.platform,640 function_name_with_arguments=function_name_with_arguments,641 layout=layout642 )643 644 645def get_debugger_title(function_name, args, schema, database):646 browser_pref = preferences('browser', 'debugger_tab_title_placeholder')647 placeholders = browser_pref.json['value']648 title = placeholders.replace('%FUNCTION%', function_name)649 if title.find('%ARGS%') != -1:650 args = args.split('(')[-1][:-1]651 title = title.replace('%ARGS%', args)652 title = title.replace('%SCHEMA%', schema)653 title = title.replace('%DATABASE%', database)654 655 return title656 657 658def get_debugger_version(conn, search_path):659 """660 Function returns the debugger version.661 :param conn:662 :return:663 """664 debugger_version = 0665 status, res = conn.execute_void("SET search_path={0};".format(search_path))666 667 if not status:668 return False, internal_server_error(errormsg=res)669 670 status, rid = conn.execute_scalar(671 "SELECT COUNT(*) FROM pg_catalog.pg_proc p"672 " LEFT JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid"673 " WHERE n.nspname = ANY(current_schemas(false)) AND"674 " p.proname = 'pldbg_get_proxy_info';"675 )676 677 if not status:678 return False, internal_server_error(errormsg=rid)679 680 if int(rid) == 0:681 debugger_version = 1682 else:683 status, rid = conn.execute_scalar(684 "SELECT proxyapiver FROM pldbg_get_proxy_info();")685 686 if status and rid in (2, 3):687 debugger_version = rid688 689 return True, debugger_version690 691 692def validate_debug(conn, debug_type, is_superuser):693 """694 This function is used to validate the options required for debugger.695 :param conn:696 :param debug_type:697 :param is_superuser:698 :return:699 """700 if debug_type == 'indirect':701 if not is_superuser:702 # If user is super user then we should check debugger library is703 # loaded or not704 msg = gettext("You must be a superuser to set a global breakpoint"705 " and perform indirect debugging.")706 return False, internal_server_error(errormsg=msg)707 708 status, rid_pre = conn.execute_scalar(709 "SHOW shared_preload_libraries"710 )711 712 if not status:713 return False, internal_server_error(714 gettext("Could not fetch debugger plugin information.")715 )716 717 # Need to check if plugin is really loaded or not with718 # "plugin_debugger" string719 if "plugin_debugger" not in rid_pre:720 msg = gettext(721 "The debugger plugin is not enabled. "722 "Please add the plugin to the shared_preload_libraries "723 "setting in the postgresql.conf file and restart the "724 "database server for indirect debugging."725 )726 current_app.logger.debug(msg)727 return False, internal_server_error(msg)728 729 # Check debugger extension version for EPAS 11 and above.730 # If it is 1.0 then return error to upgrade the extension.731 status, ext_version = conn.execute_scalar(732 "SELECT installed_version FROM pg_catalog.pg_available_extensions "733 "WHERE name = 'pldbgapi'"734 )735 if not status:736 return False, internal_server_error(errormsg=ext_version)737 if conn.manager.server_type == 'ppas' and conn.manager.sversion >= 110000 \738 and float(ext_version) < 1.1:739 return False, internal_server_error(740 errormsg=gettext("Please upgrade the pldbgapi extension "741 "to 1.1 or above and try again."))742 743 return True, None744 745 746def get_search_path(conn):747 status, res = get_extension_details(conn, PLDBG_EXTN)748 if not status:749 return False, internal_server_error(errormsg=res)750 751 status, res = conn.execute_scalar(752 "SELECT current_setting('search_path')||',{0}'".format(res['schema']))753 754 if not status:755 return False, internal_server_error(errormsg=res)756 757 return True, res758 759 760@blueprint.route(761 '/initialize_target/<debug_type>/<int:trans_id>/<int:sid>/<int:did>/'762 '<int:scid>/<int:func_id>',763 methods=['POST'],764 endpoint='initialize_target_for_function'765)766@blueprint.route(767 '/initialize_target/<debug_type>/<int:trans_id>/<int:sid>/<int:did>/'768 '<int:scid>/<int:func_id>/<int:tri_id>',769 methods=['POST'],770 endpoint='initialize_target_for_trigger'771)772@pga_login_required773def initialize_target(debug_type, trans_id, sid, did,774 scid, func_id, tri_id=None):775 """776 initialize_target(debug_type, sid, did, scid, func_id, tri_id)777 778 This method is responsible for creating an asynchronous connection.779 780 Parameters:781 debug_type782 - Type of debugging (Direct or Indirect)783 sid784 - Server Id785 did786 - Database Id787 scid788 - Schema Id789 func_id790 - Function Id791 tri_id792 - Trigger Function Id793 """794 795 # Create asynchronous connection using random connection id.796 conn_id = str(secrets.choice(range(1, 9999999)))797 manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)798 conn = manager.connection(did=did, conn_id=conn_id)799 data_obj = {}800 801 # Connect the Server802 status, msg = conn.connect()803 if not status:804 return internal_server_error(errormsg=str(msg))805 806 user = manager.user_info807 status, error = validate_debug(conn, debug_type, user['is_superuser'])808 if not status:809 return error810 811 if tri_id is not None:812 # Find trigger function id from trigger id813 sql = render_template(814 "/".join([DEBUGGER_SQL_PATH, 'get_trigger_function_info.sql']),815 table_id=func_id, trigger_id=tri_id816 )817 818 status, tr_set = conn.execute_dict(sql)819 if not status:820 current_app.logger.debug(821 "Error retrieving trigger function information from database")822 return internal_server_error(errormsg=tr_set)823 824 func_id = tr_set['rows'][0]['tgfoid']825 826 status, search_path = get_search_path(conn)827 if not status:828 return search_path829 830 # Find out the debugger version and store it in session variables831 status, debugger_version = get_debugger_version(conn, search_path)832 if not status:833 return debugger_version834 835 # Add the debugger version information to pgadmin4 log file836 current_app.logger.debug("Debugger version is: %d", debugger_version)837 838 de_inst = DebuggerInstance(trans_id)839 # We need to pass the value entered by the user in dialog for direct840 # debugging, Here we get the value in case of direct debugging so update841 # the session variables accordingly, For indirect debugging user will842 # provide the data from another session so below condition will843 # be be required844 if request.data:845 de_inst.function_data['args_value'] = \846 json.loads(request.data)847 848 # Update the debugger data session variable849 # Here frame_id is required when user debug the multilevel function.850 # When user select the frame from client we need to update the frame851 # here and set the breakpoint information on that function oid852 de_inst.debugger_data = {853 'conn_id': conn_id,854 'server_id': sid,855 'database_id': did,856 'schema_id': scid,857 'function_id': func_id,858 'function_name': de_inst.function_data['name'],859 'debug_type': debug_type,860 'debugger_version': debugger_version,861 'search_path': search_path,862 'frame_id': 0,863 'restart_debug': 0864 }865 866 de_inst.update_session()867 data_obj['db_name'] = conn.db868 869 return make_json_response(data={'status': status,870 'debuggerTransId': trans_id,871 'data_obj': data_obj})872 873 874@blueprint.route(875 '/close/<int:trans_id>', methods=["DELETE"], endpoint='close'876)877def close(trans_id):878 """879 close(trans_id)880 881 This method is used to close the asynchronous connection882 and remove the information of unique transaction id from883 the session variable.884 885 Parameters:886 trans_id887 - unique transaction id.888 """889 890 close_debugger_session(trans_id)891 return make_json_response(data={'status': True})892 893 894@blueprint.route(895 '/restart/<int:trans_id>', methods=['GET'], endpoint='restart'896)897@pga_login_required898def restart_debugging(trans_id):899 """900 restart_debugging(trans_id)901 902 This method is responsible to restart the same function for the debugging.903 904 Parameters:905 trans_id906 - Transaction ID907 """908 909 de_inst = DebuggerInstance(trans_id)910 if de_inst.debugger_data is None:911 return make_json_response(912 data={913 'status': False,914 'result': SERVER_CONNECTION_CLOSED915 }916 )917 918 manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(919 de_inst.debugger_data['server_id'])920 conn = manager.connection(921 did=de_inst.debugger_data['database_id'],922 conn_id=de_inst.debugger_data['conn_id'])923 924 if conn.connected():925 # Update the session variable "restart_debug" to know that same926 # function debugging has been restarted. Delete the existing debugger927 # data in session variable and update with new data928 if de_inst.debugger_data['restart_debug'] == 0:929 de_inst.debugger_data['restart_debug'] = 1930 de_inst.update_session()931 932 de_inst.function_data.update({933 'server_id': de_inst.debugger_data['server_id'],934 'database_id': de_inst.debugger_data['database_id'],935 'schema_id': de_inst.debugger_data['schema_id'],936 'function_id': de_inst.debugger_data['function_id'],937 'trans_id': str(trans_id),938 'proargmodes': de_inst.function_data['arg_mode'],939 'proargtypenames': de_inst.function_data['args_type'],940 'pronargdefaults': de_inst.function_data['use_default'],941 'proargdefaults': de_inst.function_data['default_value'],942 'proargnames': de_inst.function_data['args_name'],943 'require_input': de_inst.function_data['require_input']944 })945 946 return make_json_response(947 data={948 'status': True, 'restart_debug': True,949 'result': de_inst.function_data950 }951 )952 else:953 status = False954 result = SERVER_CONNECTION_CLOSED955 return make_json_response(data={'status': status, 'result': result})956 957 958@blueprint.route(959 '/start_listener/<int:trans_id>', methods=['POST'],960 endpoint='start_listener'961)962@pga_login_required963def start_debugger_listener(trans_id):964 """965 start_debugger_listener(trans_id)966 967 This method is responsible to listen and get the required information968 requested by user during debugging969 970 Parameters:971 trans_id972 - Transaction ID973 """974 975 de_inst = DebuggerInstance(trans_id)976 if de_inst.debugger_data is None:977 return make_json_response(978 data={979 'status': False,980 'result': SERVER_CONNECTION_CLOSED981 }982 )983 984 driver = get_driver(PG_DEFAULT_DRIVER)985 manager = driver.connection_manager(de_inst.debugger_data['server_id'])986 conn = manager.connection(987 did=de_inst.debugger_data['database_id'],988 conn_id=de_inst.debugger_data['conn_id'])989 990 ver = manager.version991 server_type = manager.server_type992 993 # find the debugger version and execute the query accordingly994 dbg_version = de_inst.debugger_data['debugger_version']995 if dbg_version <= 2:996 template_path = DEBUGGER_SQL_V1_PATH997 else:998 template_path = DEBUGGER_SQL_V3_PATH999 1000 # If user again start the same debug function with different arguments1001 # then we need to save that values to session variable and database.1002 if request.data:1003 data = json.loads(request.data)1004 if data:1005 de_inst.function_data['args_value'] = data1006 de_inst.update_session()1007 1008 if conn.connected():1009 1010 # For the direct debugging extract the function arguments values from1011 # user and pass to jinja template to create the query for execution.1012 if de_inst.debugger_data['debug_type'] == 'direct':1013 str_query = ''1014 if de_inst.function_data['pkg'] == 0:1015 # Form the function name with schema name1016 func_name = driver.qtIdent(1017 conn,1018 de_inst.function_data['schema'],1019 de_inst.function_data['name']1020 )1021 else:1022 # Form the edb package function/procedure name with schema name1023 func_name = driver.qtIdent(1024 conn, de_inst.function_data['schema'],1025 de_inst.function_data['pkgname'],1026 de_inst.function_data['name']1027 )1028 1029 if de_inst.debugger_data['restart_debug'] == 0:1030 # render the SQL template and send the query to server1031 if de_inst.function_data['language'] == 'plpgsql':1032 sql = render_template(1033 "/".join([template_path,1034 'debug_plpgsql_execute_target.sql']),1035 packge_oid=de_inst.function_data['pkg'],1036 function_oid=de_inst.debugger_data['function_id']1037 )1038 else:1039 sql = render_template(1040 "/".join([template_path,1041 'debug_spl_execute_target.sql']),1042 packge_oid=de_inst.function_data['pkg'],1043 function_oid=de_inst.debugger_data['function_id']1044 )1045 status, res = execute_dict_search_path(1046 conn, sql, de_inst.debugger_data['search_path'])1047 if not status:1048 return internal_server_error(errormsg=res)1049 1050 if de_inst.function_data['arg_mode']:1051 # In EDBAS 90, if an SPL-function has both an OUT-parameter1052 # and a return value (which is not possible on PostgreSQL1053 # otherwise), the return value is transformed into an extra1054 # OUT-parameter named "_retval_"1055 if de_inst.function_data['args_name']:1056 arg_name = de_inst.function_data['args_name'].split(",")1057 if '_retval_' in arg_name:1058 arg_mode = de_inst.function_data['arg_mode'].split(",")1059 arg_mode.pop()1060 else:1061 arg_mode = de_inst.function_data['arg_mode'].split(",")1062 else:1063 arg_mode = de_inst.function_data['arg_mode'].split(",")1064 else:1065 arg_mode = ['i'] * len(1066 de_inst.function_data['args_type'].split(",")1067 )1068 1069 if de_inst.function_data['args_type']:1070 if de_inst.function_data['args_name']:1071 arg_name = de_inst.function_data['args_name'].split(",")1072 if '_retval_' in arg_name:1073 arg_type = de_inst.function_data[1074 'args_type'].split(",")1075 arg_type.pop()1076 else:1077 arg_type = de_inst.function_data[1078 'args_type'].split(",")1079 else:1080 arg_type = de_inst.function_data['args_type'].split(",")1081 1082 debugger_args_values = []1083 if de_inst.function_data['args_value']:1084 debugger_args_values = copy.deepcopy(1085 de_inst.function_data['args_value'])1086 for arg in debugger_args_values:1087 if arg['type'].endswith('[]') and arg['value'] and arg[1088 'value'] != 'NULL':1089 val_list = arg['value'][1:-1].split(',')1090 arg['value'] = get_debugger_arg_val(val_list)1091 1092 # Below are two different template to execute and start executer1093 if manager.server_type != 'pg' and manager.version < 90300:1094 str_query = render_template(1095 "/".join([DEBUGGER_SQL_PATH, 'execute_edbspl.sql']),1096 func_name=func_name,1097 is_func=de_inst.function_data['is_func'],1098 lan_name=de_inst.function_data['language'],1099 ret_type=de_inst.function_data['return_type'],1100 data=debugger_args_values,1101 arg_type=arg_type,1102 args_mode=arg_mode,1103 conn=conn1104 )1105 else:1106 str_query = render_template(1107 "/".join([DEBUGGER_SQL_PATH, 'execute_plpgsql.sql']),1108 func_name=func_name,1109 is_func=de_inst.function_data['is_func'],1110 ret_type=de_inst.function_data['return_type'],1111 data=debugger_args_values,1112 is_ppas_database=de_inst.function_data['is_ppas_database'],1113 conn=conn1114 )1115 1116 status, result = execute_async_search_path(1117 conn, str_query, de_inst.debugger_data['search_path'])1118 1119 if not status:1120 return internal_server_error(errormsg=result)1121 else:1122 1123 sql = render_template(1124 "/".join([template_path, 'create_listener.sql']))1125 1126 status, res = execute_dict_search_path(1127 conn, sql, de_inst.debugger_data['search_path'])1128 if not status:1129 return internal_server_error(errormsg=res)1130 1131 # Get and store the session variable which is required to fetch1132 # other information during debugging1133 int_session_id = res['rows'][0]['pldbg_create_listener']1134 1135 # In EnterpriseDB versions <= 9.1 the1136 # pldbg_set_global_breakpoint function took five arguments,1137 # the 2nd argument being the package's OID, if any. Starting1138 # with 9.2, the package OID argument is gone, and the function1139 # takes four arguments like the community version has always1140 # done.1141 if server_type == 'ppas' and ver <= 90100:1142 sql = render_template(1143 "/".join([template_path, 'add_breakpoint_edb.sql']),1144 session_id=int_session_id,1145 function_oid=de_inst.debugger_data['function_id']1146 )1147 1148 status, res = execute_dict_search_path(1149 conn, sql, de_inst.debugger_data['search_path'])1150 if not status:1151 return internal_server_error(errormsg=res)1152 else:1153 sql = render_template(1154 "/".join([template_path, 'add_breakpoint_pg.sql']),1155 session_id=int_session_id,1156 function_oid=de_inst.debugger_data['function_id']1157 )1158 1159 status, res = execute_dict_search_path(1160 conn, sql, de_inst.debugger_data['search_path'])1161 if not status:1162 return internal_server_error(errormsg=res)1163 1164 # wait for the target1165 sql = render_template(1166 "/".join([template_path, 'wait_for_target.sql']),1167 session_id=int_session_id1168 )1169 1170 de_inst.debugger_data['exe_conn_id'] = \1171 de_inst.debugger_data['conn_id']1172 de_inst.debugger_data['restart_debug'] = 11173 de_inst.debugger_data['frame_id'] = 01174 de_inst.debugger_data['session_id'] = int_session_id1175 de_inst.update_session()1176 1177 status, res = execute_async_search_path(1178 conn, sql, de_inst.debugger_data['search_path'])1179 if not status:1180 return internal_server_error(errormsg=res)1181 1182 return make_json_response(1183 data={'status': status, 'result': res}1184 )1185 else:1186 status = False1187 result = SERVER_CONNECTION_CLOSED1188 1189 return make_json_response(data={'status': status, 'result': result})1190 1191 1192def get_debugger_arg_val(val_list):1193 """Get debugger arguments is list"""1194 debugger_args_data = []1195 for _val in val_list:1196 debugger_args_data.append({1197 'value': _val1198 })1199 return debugger_args_data1200 