diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index 44d55da8..ba3b7ee8 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -33,10 +33,10 @@ from pgadmin.settings import get_setting from config import PG_DEFAULT_DRIVER from pgadmin.model import db, DebuggerFunctionArguments +from pgadmin.tools.debugger.utils.debugger_instance import DebuggerInstance # Constants ASYNC_OK = 1 -debugger_close_session_lock = Lock() class DebuggerModule(PgAdminModule): @@ -234,14 +234,7 @@ class DebuggerModule(PgAdminModule): :param user: :return: """ - with debugger_close_session_lock: - if 'debuggerData' in session: - for trans_id in session['debuggerData']: - close_debugger_session(trans_id) - - # Delete the all debugger data from session variable - del session['debuggerData'] - del session['functionData'] + close_debugger_session(None, close_all=True) blueprint = DebuggerModule(MODULE_NAME, __name__) @@ -291,26 +284,6 @@ def script_debugger_direct_js(): ) -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'], endpoint='init_for_function' @@ -404,10 +377,19 @@ def init_function(node_type, sid, did, scid, fid, trid=None): # Check that the function is actually debuggable... if r_set['rows'][0]: + # If func/proc is not defined in package body + # then it is not debuggable + if (r_set['rows'][0]['pkgname'] is not None or + r_set['rows'][0]['pkgname'] != '') and \ + r_set['rows'][0]['prosrc'] == '': + ret_status = False + msg = r_set['rows'][0]['name'] + ' ' + \ + gettext("is not defined in package body.") + # 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" cannot be debugged. - if ":" in r_set['rows'][0]['name']: + elif ":" in r_set['rows'][0]['name']: ret_status = False msg = gettext( "Functions with a colon in the name cannot be debugged.") @@ -469,12 +451,6 @@ def init_function(node_type, sid, did, scid, fid, trid=None): current_app.logger.debug(msg) return internal_server_error(msg) - # Store the function information in session variable - if 'funcData' not in session: - function_data = dict() - else: - function_data = session['funcData'] - data = {'name': r_set['rows'][0]['proargnames'], 'type': r_set['rows'][0]['proargtypenames'], 'use_default': r_set['rows'][0]['pronargdefaults'], @@ -502,7 +478,9 @@ def init_function(node_type, sid, did, scid, fid, trid=None): r_set['rows'][0]['require_input'] = data['require_input'] - function_data = { + # Create a debugger instance + de_inst = DebuggerInstance() + de_inst.function_data = { 'oid': fid, 'name': r_set['rows'][0]['name'], 'is_func': r_set['rows'][0]['isfunc'], @@ -522,10 +500,11 @@ def init_function(node_type, sid, did, scid, fid, trid=None): 'args_value': '' } - session['funcData'] = function_data - return make_json_response( - data=r_set['rows'], + data=dict( + debug_info=r_set['rows'], + trans_id=de_inst.trans_id + ), status=200 ) @@ -533,16 +512,15 @@ def init_function(node_type, sid, did, scid, fid, trid=None): @blueprint.route('/direct/', methods=['GET'], endpoint='direct') @login_required def direct_new(trans_id): - debugger_data = session['debuggerData'] + de_inst = DebuggerInstance(trans_id) + # Return from the function if transaction id not found - if str(trans_id) not in debugger_data: + if de_inst.debugger_data is None: 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 + debug_type = 0 if de_inst.debugger_data['debug_type'] == 'indirect' else 1 """ Animations and transitions are not automatically GPU accelerated and by @@ -572,12 +550,11 @@ def direct_new(trans_id): function_arguments = '(' if 'functionData' in session: - session_function_data = session['functionData'][str(trans_id)] - if 'args_name' in session_function_data and \ - session_function_data['args_name'] is not None and \ - session_function_data['args_name'] != '': - args_name_list = session_function_data['args_name'].split(",") - args_type_list = session_function_data['args_type'].split(",") + if 'args_name' in de_inst.function_data and \ + de_inst.function_data['args_name'] is not None and \ + de_inst.function_data['args_name'] != '': + args_name_list = de_inst.function_data['args_name'].split(",") + args_type_list = de_inst.function_data['args_type'].split(",") index = 0 for args_name in args_name_list: function_arguments = '{}{} {}, '.format(function_arguments, @@ -592,34 +569,38 @@ def direct_new(trans_id): layout = get_setting('Debugger/Layout') + function_name_with_arguments = \ + de_inst.debugger_data['function_name'] + function_arguments + return render_template( "debugger/direct.html", _=gettext, - function_name=obj['function_name'], + function_name=de_inst.debugger_data['function_name'], uniqueId=trans_id, debug_type=debug_type, is_desktop_mode=current_app.PGADMIN_RUNTIME, is_linux=is_linux_platform, client_platform=user_agent.platform, - function_name_with_arguments=obj['function_name'] + function_arguments, + function_name_with_arguments=function_name_with_arguments, layout=layout ) @blueprint.route( - '/initialize_target/////' - '', + '/initialize_target/////' + '/', methods=['GET', 'POST'], endpoint='initialize_target_for_function' ) @blueprint.route( - '/initialize_target/////' - '/', + '/initialize_target/////' + '//', methods=['GET', 'POST'], endpoint='initialize_target_for_trigger' ) @login_required -def initialize_target(debug_type, sid, did, scid, func_id, tri_id=None): +def initialize_target(debug_type, trans_id, sid, did, + scid, func_id, tri_id=None): """ initialize_target(debug_type, sid, did, scid, func_id, tri_id) @@ -718,16 +699,6 @@ def initialize_target(debug_type, sid, did, scid, func_id, tri_id=None): func_id = tr_set['rows'][0]['tgfoid'] - # 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 @@ -756,6 +727,7 @@ def initialize_target(debug_type, sid, did, scid, func_id, tri_id=None): # Add the debugger version information to pgadmin4 log file current_app.logger.debug("Debugger version is: %d", debugger_version) + de_inst = DebuggerInstance(trans_id) # 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 @@ -764,65 +736,26 @@ def initialize_target(debug_type, sid, did, scid, func_id, tri_id=None): if request.method == 'POST': data = json.loads(request.values['data'], encoding='utf-8') if data: - d = session['funcData'] - d['args_value'] = data - session['funcData'] = d + de_inst.function_data['args_value'] = data # 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)] = { + de_inst.debugger_data = { 'conn_id': conn_id, 'server_id': sid, 'database_id': did, 'schema_id': scid, 'function_id': func_id, - 'function_name': session['funcData']['name'], + 'function_name': de_inst.function_data['name'], '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_ppas_database': func_data['is_ppas_database'], - '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'], - 'pkgname': func_data['pkgname'], - 'pkg': func_data['pkg'], - 'require_input': func_data['require_input'], - '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'] + de_inst.update_session() return make_json_response(data={'status': status, 'debuggerTransId': trans_id}) @@ -843,24 +776,9 @@ def close(trans_id): 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}) - - # Return from the function if transaction id not found - if str(trans_id) not in session['debuggerData']: - return make_json_response(data={'status': True}) - with debugger_close_session_lock: - try: - close_debugger_session(trans_id) - # Delete the existing debugger data in session variable - del session['debuggerData'][str(trans_id)] - del session['functionData'][str(trans_id)] - return make_json_response(data={'status': True}) - except Exception as e: - return internal_server_error(errormsg=str(e)) + close_debugger_session(trans_id) + return make_json_response(data={'status': True}) @blueprint.route( @@ -878,8 +796,8 @@ def restart_debugging(trans_id): - Transaction ID """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + de_inst = DebuggerInstance(trans_id) + if de_inst.debugger_data is None: return make_json_response( data={ 'status': False, @@ -889,44 +807,39 @@ def restart_debugging(trans_id): ) } ) - 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']) + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + de_inst.debugger_data['server_id']) + conn = manager.connection( + did=de_inst.debugger_data['database_id'], + conn_id=de_inst.debugger_data['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: - session_function_data = session['functionData'][str(trans_id)] - 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_function_data['arg_mode'], - 'proargtypenames': session_function_data['args_type'], - 'pronargdefaults': session_function_data['use_default'], - 'proargdefaults': session_function_data['default_value'], - 'proargnames': session_function_data['args_name'], - 'require_input': session_function_data['require_input'] - } + if de_inst.debugger_data['restart_debug'] == 0: + de_inst.debugger_data['restart_debug'] = 1 + de_inst.update_session() + + de_inst.function_data = { + 'server_id': de_inst.debugger_data['server_id'], + 'database_id': de_inst.debugger_data['database_id'], + 'schema_id': de_inst.debugger_data['schema_id'], + 'function_id': de_inst.debugger_data['function_id'], + 'trans_id': str(trans_id), + 'proargmodes': de_inst.function_data['arg_mode'], + 'proargtypenames': de_inst.function_data['args_type'], + 'pronargdefaults': de_inst.function_data['use_default'], + 'proargdefaults': de_inst.function_data['default_value'], + 'proargnames': de_inst.function_data['args_name'], + 'require_input': de_inst.function_data['require_input'] + } return make_json_response( data={ - 'status': True, 'restart_debug': True, 'result': function_data + 'status': True, 'restart_debug': True, + 'result': de_inst.function_data } ) else: @@ -935,8 +848,7 @@ def restart_debugging(trans_id): 'Not connected to server or connection with the server has ' 'been closed.' ) - - return make_json_response(data={'status': status}) + return make_json_response(data={'status': status, 'result': result}) @blueprint.route( @@ -956,8 +868,8 @@ def start_debugger_listener(trans_id): - Transaction ID """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + de_inst = DebuggerInstance(trans_id) + if de_inst.debugger_data is None: return make_json_response( data={ 'status': False, @@ -967,17 +879,18 @@ def start_debugger_listener(trans_id): ) } ) - obj = debugger_data[str(trans_id)] driver = get_driver(PG_DEFAULT_DRIVER) - manager = driver.connection_manager(obj['server_id']) - conn = manager.connection(did=obj['database_id'], conn_id=obj['conn_id']) + manager = driver.connection_manager(de_inst.debugger_data['server_id']) + conn = manager.connection( + did=de_inst.debugger_data['database_id'], + conn_id=de_inst.debugger_data['conn_id']) ver = manager.version server_type = manager.server_type # find the debugger version and execute the query accordingly - dbg_version = obj['debugger_version'] + dbg_version = de_inst.debugger_data['debugger_version'] if dbg_version <= 2: template_path = 'debugger/sql/v1' else: @@ -988,94 +901,91 @@ def start_debugger_listener(trans_id): if request.method == 'POST': data = json.loads(request.values['data'], encoding='utf-8') 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) + de_inst.function_data['args_value'] = data + de_inst.update_session() 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': + if de_inst.debugger_data['debug_type'] == 'direct': str_query = '' - session_function_data = session['functionData'][str(trans_id)] - if session_function_data['pkg'] == 0: + if de_inst.function_data['pkg'] == 0: # Form the function name with schema name func_name = driver.qtIdent( conn, - session_function_data['schema'], - session_function_data['name'] + de_inst.function_data['schema'], + de_inst.function_data['name'] ) else: # Form the edb package function/procedure name with schema name func_name = driver.qtIdent( - conn, session_function_data['schema'], - session_function_data['pkgname'], - session_function_data['name'] + conn, de_inst.function_data['schema'], + de_inst.function_data['pkgname'], + de_inst.function_data['name'] ) - if obj['restart_debug'] == 0: + if de_inst.debugger_data['restart_debug'] == 0: # render the SQL template and send the query to server - if session_function_data['language'] == 'plpgsql': + if de_inst.function_data['language'] == 'plpgsql': sql = render_template( "/".join([template_path, 'debug_plpgsql_execute_target.sql']), - packge_oid=session_function_data['pkg'], - function_oid=obj['function_id'] + packge_oid=de_inst.function_data['pkg'], + function_oid=de_inst.debugger_data['function_id'] ) else: sql = render_template( "/".join([template_path, 'debug_spl_execute_target.sql']), - packge_oid=session_function_data['pkg'], - function_oid=obj['function_id'] + packge_oid=de_inst.function_data['pkg'], + function_oid=de_inst.debugger_data['function_id'] ) status, res = conn.execute_dict(sql) if not status: return internal_server_error(errormsg=res) - if session_function_data['arg_mode']: + if de_inst.function_data['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_function_data['args_name']: - arg_name = session_function_data['args_name'].split(",") + if de_inst.function_data['args_name']: + arg_name = de_inst.function_data['args_name'].split(",") if '_retval_' in arg_name: - arg_mode = session_function_data['arg_mode'].split(",") + arg_mode = de_inst.function_data['arg_mode'].split(",") arg_mode.pop() else: - arg_mode = session_function_data['arg_mode'].split(",") + arg_mode = de_inst.function_data['arg_mode'].split(",") else: - arg_mode = session_function_data['arg_mode'].split(",") + arg_mode = de_inst.function_data['arg_mode'].split(",") else: arg_mode = ['i'] * len( - session_function_data['args_type'].split(",") + de_inst.function_data['args_type'].split(",") ) - if session_function_data['args_type']: - if session_function_data['args_name']: - arg_name = session_function_data['args_name'].split(",") + if de_inst.function_data['args_type']: + if de_inst.function_data['args_name']: + arg_name = de_inst.function_data['args_name'].split(",") if '_retval_' in arg_name: - arg_type = session_function_data[ + arg_type = de_inst.function_data[ 'args_type'].split(",") arg_type.pop() else: - arg_type = session_function_data[ + arg_type = de_inst.function_data[ 'args_type'].split(",") else: - arg_type = session_function_data['args_type'].split(",") + arg_type = de_inst.function_data['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_function_data['is_func'], - lan_name=session_function_data['language'], - ret_type=session_function_data['return_type'], - data=session_function_data['args_value'], + is_func=de_inst.function_data['is_func'], + lan_name=de_inst.function_data['language'], + ret_type=de_inst.function_data['return_type'], + data=de_inst.function_data['args_value'], arg_type=arg_type, args_mode=arg_mode ) @@ -1083,10 +993,10 @@ def start_debugger_listener(trans_id): str_query = render_template( "/".join(['debugger/sql', 'execute_plpgsql.sql']), func_name=func_name, - is_func=session_function_data['is_func'], - ret_type=session_function_data['return_type'], - data=session_function_data['args_value'], - is_ppas_database=session_function_data['is_ppas_database'] + is_func=de_inst.function_data['is_func'], + ret_type=de_inst.function_data['return_type'], + data=de_inst.function_data['args_value'], + is_ppas_database=de_inst.function_data['is_ppas_database'] ) status, result = conn.execute_async(str_query) @@ -1144,13 +1054,12 @@ def start_debugger_listener(trans_id): 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) + de_inst.debugger_data['exe_conn_id'] = \ + de_inst.debugger_data['conn_id'] + de_inst.debugger_data['restart_debug'] = 1 + de_inst.debugger_data['frame_id'] = 0 + de_inst.debugger_data['session_id'] = int_session_id + de_inst.update_session() return make_json_response( data={'status': status, 'result': res} ) @@ -1196,8 +1105,8 @@ def execute_debugger_query(trans_id, query_type): - Type of query to execute """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + de_inst = DebuggerInstance(trans_id) + if de_inst.debugger_data is None: return make_json_response( data={ 'status': False, @@ -1207,15 +1116,15 @@ def execute_debugger_query(trans_id, query_type): ) } ) - obj = debugger_data[str(trans_id)] - manager = get_driver( - PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + de_inst.debugger_data['server_id']) conn = manager.connection( - did=obj['database_id'], conn_id=obj['exe_conn_id']) + did=de_inst.debugger_data['database_id'], + conn_id=de_inst.debugger_data['exe_conn_id']) # find the debugger version and execute the query accordingly - dbg_version = obj['debugger_version'] + dbg_version = de_inst.debugger_data['debugger_version'] if dbg_version <= 2: template_path = 'debugger/sql/v1' else: @@ -1224,7 +1133,7 @@ def execute_debugger_query(trans_id, query_type): if conn.connected(): sql = render_template( "/".join([template_path, query_type + ".sql"]), - session_id=obj['session_id'] + session_id=de_inst.debugger_data['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. @@ -1233,10 +1142,9 @@ def execute_debugger_query(trans_id, query_type): 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) + if de_inst.debugger_data['frame_id'] != 0: + de_inst.debugger_data['frame_id'] = 0 + de_inst.update_session() status, result = conn.execute_async(sql) if not status: @@ -1282,8 +1190,8 @@ def messages(trans_id): - unique transaction id. """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + de_inst = DebuggerInstance(trans_id) + if de_inst.debugger_data is None: return make_json_response( data={ 'status': 'NotConnected', @@ -1293,11 +1201,12 @@ def messages(trans_id): ) } ) - 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']) + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + de_inst.debugger_data['server_id']) + conn = manager.connection( + did=de_inst.debugger_data['database_id'], + conn_id=de_inst.debugger_data['conn_id']) port_number = '' @@ -1357,8 +1266,8 @@ def start_execution(trans_id, port_num): - Port number to attach """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + de_inst = DebuggerInstance(trans_id) + if de_inst.debugger_data is None: return make_json_response( data={ 'status': 'NotConnected', @@ -1368,16 +1277,15 @@ def start_execution(trans_id, port_num): ) } ) - 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) + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + de_inst.debugger_data['server_id']) + conn = manager.connection( + did=de_inst.debugger_data['database_id'], + conn_id=exe_conn_id) except Exception as e: return internal_server_error(errormsg=str(e)) @@ -1386,13 +1294,8 @@ def start_execution(trans_id, port_num): 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'] + dbg_version = de_inst.debugger_data['debugger_version'] if dbg_version <= 2: template_path = 'debugger/sql/v1' else: @@ -1405,13 +1308,13 @@ def start_execution(trans_id, port_num): 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) + de_inst.debugger_data['restart_debug'] = 0 + de_inst.debugger_data['frame_id'] = 0 + de_inst.debugger_data['exe_conn_id'] = exe_conn_id + de_inst.debugger_data['debugger_version'] = dbg_version + de_inst.debugger_data['session_id'] = \ + res_port['rows'][0]['pldbg_attach_to_port'] + de_inst.update_session() return make_json_response( data={ @@ -1441,8 +1344,9 @@ def set_clear_breakpoint(trans_id, line_no, set_type): - 0 - clear the breakpoint, 1 - set the breakpoint """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + de_inst = DebuggerInstance(trans_id) + + if de_inst.debugger_data is None: return make_json_response( data={ 'status': False, @@ -1452,15 +1356,15 @@ def set_clear_breakpoint(trans_id, line_no, set_type): ) } ) - obj = debugger_data[str(trans_id)] - manager = get_driver( - PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + de_inst.debugger_data['server_id']) conn = manager.connection( - did=obj['database_id'], conn_id=obj['exe_conn_id']) + did=de_inst.debugger_data['database_id'], + conn_id=de_inst.debugger_data['exe_conn_id']) # find the debugger version and execute the query accordingly - dbg_version = obj['debugger_version'] + dbg_version = de_inst.debugger_data['debugger_version'] if dbg_version <= 2: template_path = 'debugger/sql/v1' else: @@ -1474,7 +1378,7 @@ def set_clear_breakpoint(trans_id, line_no, set_type): # 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'] + session_id=de_inst.debugger_data['session_id'] ) status, res_stack = conn.execute_dict(sql_) if not status: @@ -1483,7 +1387,7 @@ def set_clear_breakpoint(trans_id, line_no, set_type): # 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'] + foid = res_stack['rows'][de_inst.debugger_data['frame_id']]['func'] # Check the result of the stack before setting the breakpoint if conn.connected(): @@ -1494,7 +1398,7 @@ def set_clear_breakpoint(trans_id, line_no, set_type): sql = render_template( "/".join([template_path, query_type + ".sql"]), - session_id=obj['session_id'], + session_id=de_inst.debugger_data['session_id'], foid=foid, line_number=line_no ) @@ -1528,8 +1432,9 @@ def clear_all_breakpoint(trans_id): trans_id - Transaction ID """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + + de_inst = DebuggerInstance(trans_id) + if de_inst.debugger_data is None: return make_json_response( data={ 'status': False, @@ -1539,22 +1444,19 @@ def clear_all_breakpoint(trans_id): ) } ) - obj = debugger_data[str(trans_id)] - - manager = get_driver( - PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + de_inst.debugger_data['server_id']) conn = manager.connection( - did=obj['database_id'], conn_id=obj['exe_conn_id']) + did=de_inst.debugger_data['database_id'], + conn_id=de_inst.debugger_data['exe_conn_id']) # find the debugger version and execute the query accordingly - dbg_version = obj['debugger_version'] + dbg_version = de_inst.debugger_data['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']: @@ -1562,8 +1464,9 @@ def clear_all_breakpoint(trans_id): 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 + session_id=de_inst.debugger_data['session_id'], + foid=de_inst.debugger_data['function_id'], + line_number=line_no ) status, result = conn.execute_dict(sql) @@ -1597,8 +1500,9 @@ def deposit_parameter_value(trans_id): trans_id - Transaction ID """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + + de_inst = DebuggerInstance(trans_id) + if de_inst.debugger_data is None: return make_json_response( data={ 'status': False, @@ -1606,22 +1510,19 @@ def deposit_parameter_value(trans_id): 'with the server has been closed.') } ) - obj = debugger_data[str(trans_id)] - - manager = get_driver( - PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + de_inst.debugger_data['server_id']) conn = manager.connection( - did=obj['database_id'], conn_id=obj['exe_conn_id']) + did=de_inst.debugger_data['database_id'], + conn_id=de_inst.debugger_data['exe_conn_id']) # find the debugger version and execute the query accordingly - dbg_version = obj['debugger_version'] + dbg_version = de_inst.debugger_data['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'], encoding='utf-8') @@ -1629,7 +1530,7 @@ def deposit_parameter_value(trans_id): if data: sql = render_template( "/".join([template_path, "deposit_value.sql"]), - session_id=obj['session_id'], + session_id=de_inst.debugger_data['session_id'], var_name=data[0]['name'], line_number=-1, val=data[0]['value'] ) @@ -1678,8 +1579,8 @@ def select_frame(trans_id, frame_id): frame_id - Frame id selected """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + de_inst = DebuggerInstance(trans_id) + if de_inst.debugger_data is None: return make_json_response( data={ 'status': False, @@ -1689,28 +1590,27 @@ def select_frame(trans_id, frame_id): ) } ) - obj = debugger_data[str(trans_id)] - manager = get_driver( - PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + de_inst.debugger_data['server_id']) conn = manager.connection( - did=obj['database_id'], conn_id=obj['exe_conn_id']) + did=de_inst.debugger_data['database_id'], + conn_id=de_inst.debugger_data['exe_conn_id']) # find the debugger version and execute the query accordingly - dbg_version = obj['debugger_version'] + dbg_version = de_inst.debugger_data['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) + de_inst.debugger_data['frame_id'] = frame_id + de_inst.update_session() if conn.connected(): sql = render_template( "/".join([template_path, "select_frame.sql"]), - session_id=obj['session_id'], + session_id=de_inst.debugger_data['session_id'], frame_id=frame_id ) @@ -1950,8 +1850,8 @@ def poll_end_execution_result(trans_id): - unique transaction id. """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + de_inst = DebuggerInstance(trans_id) + if de_inst.debugger_data is None: return make_json_response( data={'status': 'NotConnected', 'result': gettext( @@ -1959,11 +1859,12 @@ def poll_end_execution_result(trans_id): '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']) + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + de_inst.debugger_data['server_id']) + conn = manager.connection( + did=de_inst.debugger_data['database_id'], + conn_id=de_inst.debugger_data['conn_id']) if conn.connected(): statusmsg = conn.status_message() @@ -1980,11 +1881,10 @@ def poll_end_execution_result(trans_id): } ) - session_function_data = session['functionData'][str(trans_id)] if status == ASYNC_OK and \ - not session_function_data['is_func'] and\ - (session_function_data['language'] == 'edbspl' or - session_function_data['language'] == 'plpgsql'): + not de_inst.function_data['is_func'] and\ + (de_inst.function_data['language'] == 'edbspl' or + de_inst.function_data['language'] == 'plpgsql'): status = 'Success' additional_msgs = conn.messages() if len(additional_msgs) > 0: @@ -2077,8 +1977,8 @@ def poll_result(trans_id): - unique transaction id. """ - debugger_data = session['debuggerData'] - if str(trans_id) not in debugger_data: + de_inst = DebuggerInstance(trans_id) + if de_inst.debugger_data is None: return make_json_response( data={ 'status': 'NotConnected', @@ -2086,12 +1986,12 @@ def poll_result(trans_id): 'with the server has been closed.') } ) - obj = debugger_data[str(trans_id)] - manager = get_driver( - PG_DEFAULT_DRIVER).connection_manager(obj['server_id']) + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + de_inst.debugger_data['server_id']) conn = manager.connection( - did=obj['database_id'], conn_id=obj['exe_conn_id']) + did=de_inst.debugger_data['database_id'], + conn_id=de_inst.debugger_data['exe_conn_id']) if conn.connected(): status, result = conn.poll() @@ -2117,7 +2017,7 @@ def poll_result(trans_id): ) -def close_debugger_session(trans_id): +def close_debugger_session(_trans_id, close_all=False): """ This function is used to cancel the debugger transaction and release the connection. @@ -2125,21 +2025,39 @@ def close_debugger_session(trans_id): :param trans_id: Transaction id :return: """ - dbg_obj = session['debuggerData'][str(trans_id)] - manager = get_driver( - PG_DEFAULT_DRIVER).connection_manager(dbg_obj['server_id']) + if close_all: + trans_ids = DebuggerInstance.get_trans_ids() + else: + trans_ids = [_trans_id] - if manager is not None: - conn = manager.connection( - did=dbg_obj['database_id'], conn_id=dbg_obj['conn_id']) - if conn.connected(): - conn.cancel_transaction(dbg_obj['conn_id'], - dbg_obj['database_id']) - conn = manager.connection( - did=dbg_obj['database_id'], conn_id=dbg_obj['exe_conn_id']) - if conn.connected(): - conn.cancel_transaction(dbg_obj['exe_conn_id'], - dbg_obj['database_id']) - manager.release(conn_id=dbg_obj['conn_id']) - manager.release(conn_id=dbg_obj['exe_conn_id']) + for trans_id in trans_ids: + de_inst = DebuggerInstance(trans_id) + dbg_obj = de_inst.debugger_data + + try: + if dbg_obj is not None: + manager = get_driver(PG_DEFAULT_DRIVER).\ + connection_manager(dbg_obj['server_id']) + + if manager is not None: + conn = manager.connection( + did=dbg_obj['database_id'], + conn_id=dbg_obj['conn_id']) + if conn.connected(): + conn.cancel_transaction( + dbg_obj['conn_id'], + dbg_obj['database_id']) + conn = manager.connection( + did=dbg_obj['database_id'], + conn_id=dbg_obj['exe_conn_id']) + if conn.connected(): + conn.cancel_transaction( + dbg_obj['exe_conn_id'], + dbg_obj['database_id']) + manager.release(conn_id=dbg_obj['conn_id']) + manager.release(conn_id=dbg_obj['exe_conn_id']) + except Exception as _: + raise + finally: + de_inst.clear() diff --git a/web/pgadmin/tools/debugger/static/js/debugger.js b/web/pgadmin/tools/debugger/static/js/debugger.js index 2ee1d55c..c91e418f 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger.js +++ b/web/pgadmin/tools/debugger/static/js/debugger.js @@ -343,17 +343,17 @@ define([ var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]), baseUrl; - if (d._type == 'function') { + if (d._type == 'function' || d._type == 'edbfunc') { baseUrl = url_for( 'debugger.initialize_target_for_function', { 'debug_type': 'indirect', 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, - 'func_id': treeInfo.function._id, + 'func_id': debuggerUtils.getFunctionId(treeInfo), } ); - } else if (d._type == 'procedure') { + } else if (d._type == 'procedure' || d._type == 'edbproc') { baseUrl = url_for( 'debugger.initialize_target_for_function', { 'debug_type': 'indirect', @@ -363,24 +363,6 @@ define([ 'func_id': debuggerUtils.getProcedureId(treeInfo), } ); - } else if (d._type == 'edbfunc') { - // Get the existing function parameters available from sqlite database - baseUrl = url_for('debugger.initialize_target_for_function', { - 'debug_type': 'indirect', - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.edbfunc._id, - }); - } else if (d._type == 'edbproc') { - // Get the existing function parameters available from sqlite database - baseUrl = url_for('debugger.initialize_target_for_function', { - 'debug_type': 'indirect', - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.edbproc._id, - }); } else if (d._type == 'trigger_function') { baseUrl = url_for( 'debugger.initialize_target_for_function', { @@ -491,9 +473,11 @@ define([ }) .done(function(res) { + let debug_info = res.data.debug_info, + trans_id = res.data.trans_id; // 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, is_edb_proc); + if (debug_info[0]['require_input']) { + get_function_arguments(debug_info[0], 0, is_edb_proc, trans_id); } 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 @@ -509,20 +493,22 @@ define([ var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]), baseUrl; - if (d._type == 'function') { + if (d._type == 'function' || d._type == 'edbfunc') { baseUrl = url_for( 'debugger.initialize_target_for_function', { 'debug_type': 'direct', + 'trans_id': trans_id, 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, - 'func_id': treeInfo.function._id, + 'func_id': debuggerUtils.getFunctionId(treeInfo), } ); - } else { + } else if(d._type == 'procedure' || d._type == 'edbproc') { baseUrl = url_for( 'debugger.initialize_target_for_function', { 'debug_type': 'direct', + 'trans_id': trans_id, 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, @@ -535,10 +521,10 @@ define([ url: baseUrl, method: 'GET', }) - .done(function(res) { + .done(function() { var url = url_for('debugger.direct', { - 'trans_id': res.data.debuggerTransId, + 'trans_id': trans_id, }); if (self.preferences.debugger_new_browser_tab) { @@ -563,7 +549,7 @@ define([ // Register Panel Closed event panel.on(wcDocker.EVENT.CLOSED, function() { var closeUrl = url_for('debugger.close', { - 'trans_id': res.data.debuggerTransId, + 'trans_id': trans_id, }); $.ajax({ url: closeUrl, diff --git a/web/pgadmin/tools/debugger/static/js/debugger_ui.js b/web/pgadmin/tools/debugger/static/js/debugger_ui.js index 46d34eed..f05a22dc 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger_ui.js +++ b/web/pgadmin/tools/debugger/static/js/debugger_ui.js @@ -163,11 +163,11 @@ define([ } }; - var res = function(debug_info, restart_debug, is_edb_proc) { + var res = function(debug_info, restart_debug, is_edb_proc, trans_id) { if (!Alertify.debuggerInputArgsDialog) { Alertify.dialog('debuggerInputArgsDialog', function factory() { return { - main: function(title, debug_info, restart_debug, is_edb_proc) { + main: function(title, debug_info, restart_debug, is_edb_proc, trans_id) { this.preferences = window.top.pgAdmin.Browser.get_preferences_for_module('debugger'); this.set('title', title); @@ -175,6 +175,7 @@ define([ // other functions other than main function. this.set('debug_info', debug_info); this.set('restart_debug', restart_debug); + this.set('trans_id', trans_id); // Variables to store the data sent from sqlite database var func_args_data = this.func_args_data = []; @@ -579,6 +580,7 @@ define([ settings: { debug_info: undefined, restart_debug: undefined, + trans_id: undefined, }, setup: function() { return { @@ -706,6 +708,7 @@ define([ if (d._type == 'function') { baseUrl = url_for('debugger.initialize_target_for_function', { 'debug_type': 'direct', + 'trans_id': self.setting('trans_id'), 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, @@ -714,6 +717,7 @@ define([ } else if (d._type == 'procedure') { baseUrl = url_for('debugger.initialize_target_for_function', { 'debug_type': 'direct', + 'trans_id': self.setting('trans_id'), 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, @@ -722,6 +726,7 @@ define([ } else if (d._type == 'edbfunc') { baseUrl = url_for('debugger.initialize_target_for_function', { 'debug_type': 'direct', + 'trans_id': self.setting('trans_id'), 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, @@ -730,6 +735,7 @@ define([ } else if (d._type == 'edbproc') { baseUrl = url_for('debugger.initialize_target_for_function', { 'debug_type': 'direct', + 'trans_id': self.setting('trans_id'), 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, @@ -885,7 +891,12 @@ define([ } if (e.button.text === gettext('Cancel')) { - //close the dialog... + /* Clear the trans id */ + $.ajax({ + method: 'DELETE', + url: url_for('debugger.close', {'trans_id': this.setting('trans_id')}), + }); + return false; } }, @@ -959,7 +970,7 @@ define([ } Alertify.debuggerInputArgsDialog( - gettext('Debugger'), debug_info, restart_debug, is_edb_proc + gettext('Debugger'), debug_info, restart_debug, is_edb_proc, trans_id ).resizeTo(pgBrowser.stdW.md,pgBrowser.stdH.md); }; diff --git a/web/pgadmin/tools/debugger/static/js/debugger_utils.js b/web/pgadmin/tools/debugger/static/js/debugger_utils.js index a3529d84..282acb01 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger_utils.js +++ b/web/pgadmin/tools/debugger/static/js/debugger_utils.js @@ -18,6 +18,18 @@ function setFocusToDebuggerEditor(editor, command) { } } +function getFunctionId(treeInfoObject) { + let objectId; + if(treeInfoObject) { + if (treeInfoObject.function && treeInfoObject.function._id) { + objectId = treeInfoObject.function._id; + } else if (treeInfoObject.edbfunc && treeInfoObject.edbfunc._id) { + objectId = treeInfoObject.edbfunc._id; + } + } + return objectId; +} + function getProcedureId(treeInfoObject) { let objectId; if(treeInfoObject) { @@ -32,5 +44,6 @@ function getProcedureId(treeInfoObject) { module.exports = { setFocusToDebuggerEditor: setFocusToDebuggerEditor, + getFunctionId: getFunctionId, getProcedureId: getProcedureId, }; diff --git a/web/pgadmin/tools/debugger/utils/debugger_instance.py b/web/pgadmin/tools/debugger/utils/debugger_instance.py new file mode 100644 index 00000000..d16021e1 --- /dev/null +++ b/web/pgadmin/tools/debugger/utils/debugger_instance.py @@ -0,0 +1,72 @@ +from flask import session +from threading import Lock +import random + +debugger_sessions_lock = Lock() + + +class DebuggerInstance: + def __init__(self, trans_id=None): + if trans_id is None: + self._trans_id = str(random.randint(1, 9999999)) + else: + self._trans_id = trans_id + + self._function_data = None + self._debugger_data = None + self.load_from_session() + + @property + def trans_id(self): + """ + trans_id be readonly with no setter + """ + return self._trans_id + + @property + def function_data(self): + return self._function_data + + @function_data.setter + def function_data(self, data): + self._function_data = data + self.update_session() + + @property + def debugger_data(self): + return self._debugger_data + + @debugger_data.setter + def debugger_data(self, data): + self._debugger_data = data + self.update_session() + + @staticmethod + def get_trans_ids(): + if '__debugger_sessions' in session: + return [trans_id for trans_id in session['__debugger_sessions']] + else: + return [] + + def load_from_session(self): + if '__debugger_sessions' in session: + if str(self.trans_id) in session['__debugger_sessions']: + trans_data = session['__debugger_sessions'][str(self.trans_id)] + self.function_data = trans_data.get('function_data', None) + self.debugger_data = trans_data.get('debugger_data', None) + + def update_session(self): + with debugger_sessions_lock: + if '__debugger_sessions' not in session: + session['__debugger_sessions'] = dict() + + session['__debugger_sessions'][str(self.trans_id)] = dict( + function_data=self.function_data, + debugger_data=self.debugger_data + ) + + def clear(self): + with debugger_sessions_lock: + if '__debugger_sessions' in session: + if str(self.trans_id) in session['__debugger_sessions']: + session['__debugger_sessions'].pop(str(self.trans_id))