public inbox for [email protected]help / color / mirror / Atom feed
[pgAdmin][RM4329] Initialization error when parameterised functions debugged in parallel in two separate tabs 8+ messages / 3 participants [nested] [flat]
* [pgAdmin][RM4329] Initialization error when parameterised functions debugged in parallel in two separate tabs @ 2019-06-07 06:50 Aditya Toshniwal <[email protected]> 0 siblings, 1 reply; 8+ messages in thread From: Aditya Toshniwal @ 2019-06-07 06:50 UTC (permalink / raw) To: pgadmin-hackers Hi Hackers, Attached is the patch for debugger improvements. The changes include: 1) Change the way debug info is stored in session. Removed redundant session related code in debugger code. All the session related handling done at one place. 2) Fixed a bug where debugger was not opening for EPAS package function. 3) If a package is defined without body and we try to debug a proc/func, the debugger opened a blank window. Changes made so that it will throw error as "XYZ is not defined in package body." -- Thanks and Regards, Aditya Toshniwal Software Engineer | EnterpriseDB India | Pune "Don't Complain about Heat, Plant a TREE" Attachments: [application/octet-stream] RM4329.patch (56.3K, 3-RM4329.patch) download | inline diff: 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/<node_type>/<int:sid>/<int:did>/<int:scid>/<int:fid>', 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/<int:trans_id>', 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/<debug_type>/<int:sid>/<int:did>/<int:scid>/' - '<int:func_id>', + '/initialize_target/<debug_type>/<int:trans_id>/<int:sid>/<int:did>/' + '<int:scid>/<int:func_id>', methods=['GET', 'POST'], endpoint='initialize_target_for_function' ) @blueprint.route( - '/initialize_target/<debug_type>/<int:sid>/<int:did>/<int:scid>/' - '<int:func_id>/<int:tri_id>', + '/initialize_target/<debug_type>/<int:trans_id>/<int:sid>/<int:did>/' + '<int:scid>/<int:func_id>/<int:tri_id>', 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)) ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: [pgAdmin][RM4329] Initialization error when parameterised functions debugged in parallel in two separate tabs @ 2019-06-10 07:28 Akshay Joshi <[email protected]> parent: Aditya Toshniwal <[email protected]> 0 siblings, 1 reply; 8+ messages in thread From: Akshay Joshi @ 2019-06-10 07:28 UTC (permalink / raw) To: Aditya Toshniwal <[email protected]>; +Cc: pgadmin-hackers Hi Aditya Following are the review comments: - "Set breakpoint" option not working, when click it throws an error. - Create an empty function and try to debug that. It should show proper error message. - Got the following backend error when closing the connection, please fix this: - File "E:\Projects\pgadmin4\web\pgadmin\tools\debugger\__init__.py", line 2053, in close_debugger_session conn_id=dbg_obj['exe_conn_id']) On Fri, Jun 7, 2019 at 12:21 PM Aditya Toshniwal < [email protected]> wrote: > Hi Hackers, > > Attached is the patch for debugger improvements. The changes include: > 1) Change the way debug info is stored in session. Removed redundant > session related code in debugger code. All the session related handling > done at one place. > 2) Fixed a bug where debugger was not opening for EPAS package function. > 3) If a package is defined without body and we try to debug a proc/func, > the debugger opened a blank window. Changes made so that it will throw > error as "XYZ is not defined in package body." > > -- > Thanks and Regards, > Aditya Toshniwal > Software Engineer | EnterpriseDB India | Pune > "Don't Complain about Heat, Plant a TREE" > -- *Thanks & Regards* *Akshay Joshi* *Sr. Software Architect* *EnterpriseDB Software India Private Limited* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: [pgAdmin][RM4329] Initialization error when parameterised functions debugged in parallel in two separate tabs @ 2019-06-10 12:57 Aditya Toshniwal <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 8+ messages in thread From: Aditya Toshniwal @ 2019-06-10 12:57 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers Hi Hackers, Attached is the updated patch with fixes. On Mon, Jun 10, 2019 at 12:58 PM Akshay Joshi <[email protected]> wrote: > Hi Aditya > > Following are the review comments: > > - "Set breakpoint" option not working, when click it throws an error. > > Fixed. > > - Create an empty function and try to debug that. It should show > proper error message. > > This seems to be a bug in the debugger itself. I'll raise a bug with simulation steps if it is. But, not sure where to raise. > > - Got the following backend error when closing the connection, please > fix this: > - File > "E:\Projects\pgadmin4\web\pgadmin\tools\debugger\__init__.py", line 2053, > in close_debugger_session > conn_id=dbg_obj['exe_conn_id']) > > Fixed. > > On Fri, Jun 7, 2019 at 12:21 PM Aditya Toshniwal < > [email protected]> wrote: > >> Hi Hackers, >> >> Attached is the patch for debugger improvements. The changes include: >> 1) Change the way debug info is stored in session. Removed redundant >> session related code in debugger code. All the session related handling >> done at one place. >> 2) Fixed a bug where debugger was not opening for EPAS package function. >> 3) If a package is defined without body and we try to debug a proc/func, >> the debugger opened a blank window. Changes made so that it will throw >> error as "XYZ is not defined in package body." >> >> -- >> Thanks and Regards, >> Aditya Toshniwal >> Software Engineer | EnterpriseDB India | Pune >> "Don't Complain about Heat, Plant a TREE" >> > > > -- > *Thanks & Regards* > *Akshay Joshi* > > *Sr. Software Architect* > *EnterpriseDB Software India Private Limited* > *Mobile: +91 976-788-8246* > -- Thanks and Regards, Aditya Toshniwal Software Engineer | EnterpriseDB India | Pune "Don't Complain about Heat, Plant a TREE" Attachments: [application/octet-stream] RM4329_v2.patch (59.8K, 3-RM4329_v2.patch) download | inline diff: diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index 44d55da8..6d1a80b4 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -15,7 +15,6 @@ import simplejson as json import random import re -from threading import Lock from flask import url_for, Response, render_template, request, session, \ current_app from flask_babelex import gettext @@ -33,10 +32,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 +233,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 +283,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/<node_type>/<int:sid>/<int:did>/<int:scid>/<int:fid>', methods=['GET'], endpoint='init_for_function' @@ -332,6 +304,8 @@ def init_function(node_type, sid, did, scid, fid, trid=None): not require these data because user will provide all the arguments and other functions information through another session to invoke the target. + It will also create a unique transaction id and store the information + into session variable. Parameters: node_type @@ -404,10 +378,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 +452,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 +479,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 +501,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 +513,15 @@ def init_function(node_type, sid, did, scid, fid, trid=None): @blueprint.route('/direct/<int:trans_id>', 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 +551,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,40 +570,42 @@ 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/<debug_type>/<int:sid>/<int:did>/<int:scid>/' - '<int:func_id>', + '/initialize_target/<debug_type>/<int:trans_id>/<int:sid>/<int:did>/' + '<int:scid>/<int:func_id>', methods=['GET', 'POST'], endpoint='initialize_target_for_function' ) @blueprint.route( - '/initialize_target/<debug_type>/<int:sid>/<int:did>/<int:scid>/' - '<int:func_id>/<int:tri_id>', + '/initialize_target/<debug_type>/<int:trans_id>/<int:sid>/<int:did>/' + '<int:scid>/<int:func_id>/<int:tri_id>', 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) 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 @@ -718,16 +698,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 +726,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 +735,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 +775,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 +795,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 +806,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 +847,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 +867,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 +878,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 +900,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 +992,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) @@ -1117,7 +1026,7 @@ def start_debugger_listener(trans_id): sql = render_template( "/".join([template_path, 'add_breakpoint_edb.sql']), session_id=int_session_id, - function_oid=obj['function_id'] + function_oid=de_inst.debugger_data['function_id'] ) status, res = conn.execute_dict(sql) @@ -1127,7 +1036,7 @@ def start_debugger_listener(trans_id): sql = render_template( "/".join([template_path, 'add_breakpoint_pg.sql']), session_id=int_session_id, - function_oid=obj['function_id'] + function_oid=de_inst.debugger_data['function_id'] ) status, res = conn.execute_dict(sql) @@ -1144,13 +1053,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 +1104,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 +1115,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 +1132,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 +1141,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 +1189,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 +1200,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 +1265,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 +1276,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 +1293,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 +1307,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 +1343,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 +1355,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 +1377,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 +1386,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 +1397,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 +1431,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 +1443,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 +1463,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 +1499,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 +1509,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 +1529,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 +1578,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 +1589,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 +1849,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 +1858,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 +1880,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 +1976,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 +1985,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 +2016,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 +2024,41 @@ 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']) + manager.release(conn_id=dbg_obj['conn_id']) + + if 'exe_conn_id' in dbg_obj: + 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['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..a2149bf1 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger.js +++ b/web/pgadmin/tools/debugger/static/js/debugger.js @@ -313,8 +313,8 @@ define([ url: _url, cache: false, }) - .done(function() { - self.start_global_debugger(); + .done(function(res) { + self.start_global_debugger(args, item, res.data.trans_id); }) .fail(function(xhr) { try { @@ -329,7 +329,7 @@ define([ }, //Callback function when user start the indirect debugging ( Listen to another session to invoke the target ) - start_global_debugger: function(args, item) { + start_global_debugger: function(args, item, trans_id) { // Initialize the target and create asynchronous connection and unique transaction ID var t = pgBrowser.tree, i = item || t.selected(), @@ -343,48 +343,33 @@ 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', + '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 if (d._type == 'procedure') { + } else if (d._type == 'procedure' || d._type == 'edbproc') { baseUrl = url_for( 'debugger.initialize_target_for_function', { 'debug_type': 'indirect', + 'trans_id': trans_id, 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, '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', { 'debug_type': 'indirect', + 'trans_id': trans_id, 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, @@ -395,6 +380,7 @@ define([ baseUrl = url_for( 'debugger.initialize_target_for_trigger', { 'debug_type': 'indirect', + 'trans_id': trans_id, 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, @@ -406,6 +392,7 @@ define([ baseUrl = url_for( 'debugger.initialize_target_for_trigger', { 'debug_type': 'indirect', + 'trans_id': trans_id, 'sid': treeInfo.server._id, 'did': treeInfo.database._id, 'scid': treeInfo.schema._id, @@ -491,9 +478,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 +498,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 +526,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 +554,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)) ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: [pgAdmin][RM4329] Initialization error when parameterised functions debugged in parallel in two separate tabs @ 2019-06-10 13:58 Dave Page <[email protected]> parent: Aditya Toshniwal <[email protected]> 0 siblings, 1 reply; 8+ messages in thread From: Dave Page @ 2019-06-10 13:58 UTC (permalink / raw) To: Aditya Toshniwal <[email protected]>; +Cc: Akshay Joshi <[email protected]>; pgadmin-hackers Thanks, patch applied. On Mon, Jun 10, 2019 at 1:58 PM Aditya Toshniwal < [email protected]> wrote: > Hi Hackers, > > Attached is the updated patch with fixes. > > On Mon, Jun 10, 2019 at 12:58 PM Akshay Joshi < > [email protected]> wrote: > >> Hi Aditya >> >> Following are the review comments: >> >> - "Set breakpoint" option not working, when click it throws an error. >> >> Fixed. > >> >> - Create an empty function and try to debug that. It should show >> proper error message. >> >> This seems to be a bug in the debugger itself. I'll raise a bug with > simulation steps if it is. But, not sure where to raise. > >> >> - Got the following backend error when closing the connection, please >> fix this: >> - File >> "E:\Projects\pgadmin4\web\pgadmin\tools\debugger\__init__.py", line 2053, >> in close_debugger_session >> conn_id=dbg_obj['exe_conn_id']) >> >> Fixed. > >> >> On Fri, Jun 7, 2019 at 12:21 PM Aditya Toshniwal < >> [email protected]> wrote: >> >>> Hi Hackers, >>> >>> Attached is the patch for debugger improvements. The changes include: >>> 1) Change the way debug info is stored in session. Removed redundant >>> session related code in debugger code. All the session related handling >>> done at one place. >>> 2) Fixed a bug where debugger was not opening for EPAS package function. >>> 3) If a package is defined without body and we try to debug a proc/func, >>> the debugger opened a blank window. Changes made so that it will throw >>> error as "XYZ is not defined in package body." >>> >>> -- >>> Thanks and Regards, >>> Aditya Toshniwal >>> Software Engineer | EnterpriseDB India | Pune >>> "Don't Complain about Heat, Plant a TREE" >>> >> >> >> -- >> *Thanks & Regards* >> *Akshay Joshi* >> >> *Sr. Software Architect* >> *EnterpriseDB Software India Private Limited* >> *Mobile: +91 976-788-8246* >> > > > -- > Thanks and Regards, > Aditya Toshniwal > Software Engineer | EnterpriseDB India | Pune > "Don't Complain about Heat, Plant a TREE" > -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EnterpriseDB UK: http://www.enterprisedb.com The Enterprise PostgreSQL Company ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: [pgAdmin][RM4329] Initialization error when parameterised functions debugged in parallel in two separate tabs @ 2019-06-14 09:37 Aditya Toshniwal <[email protected]> parent: Dave Page <[email protected]> 0 siblings, 1 reply; 8+ messages in thread From: Aditya Toshniwal @ 2019-06-14 09:37 UTC (permalink / raw) To: Dave Page <[email protected]>; +Cc: Akshay Joshi <[email protected]>; pgadmin-hackers Hi Hackers, I have missed a line while implementing this. Attached is the patch to fix that. Although it has not caused any trouble, but still it should be changed. Kindly review. On Mon, Jun 10, 2019 at 7:28 PM Dave Page <[email protected]> wrote: > Thanks, patch applied. > > On Mon, Jun 10, 2019 at 1:58 PM Aditya Toshniwal < > [email protected]> wrote: > >> Hi Hackers, >> >> Attached is the updated patch with fixes. >> >> On Mon, Jun 10, 2019 at 12:58 PM Akshay Joshi < >> [email protected]> wrote: >> >>> Hi Aditya >>> >>> Following are the review comments: >>> >>> - "Set breakpoint" option not working, when click it throws an error. >>> >>> Fixed. >> >>> >>> - Create an empty function and try to debug that. It should show >>> proper error message. >>> >>> This seems to be a bug in the debugger itself. I'll raise a bug with >> simulation steps if it is. But, not sure where to raise. >> >>> >>> - Got the following backend error when closing the connection, >>> please fix this: >>> - File >>> "E:\Projects\pgadmin4\web\pgadmin\tools\debugger\__init__.py", line 2053, >>> in close_debugger_session >>> conn_id=dbg_obj['exe_conn_id']) >>> >>> Fixed. >> >>> >>> On Fri, Jun 7, 2019 at 12:21 PM Aditya Toshniwal < >>> [email protected]> wrote: >>> >>>> Hi Hackers, >>>> >>>> Attached is the patch for debugger improvements. The changes include: >>>> 1) Change the way debug info is stored in session. Removed redundant >>>> session related code in debugger code. All the session related handling >>>> done at one place. >>>> 2) Fixed a bug where debugger was not opening for EPAS package function. >>>> 3) If a package is defined without body and we try to debug a >>>> proc/func, the debugger opened a blank window. Changes made so that it will >>>> throw error as "XYZ is not defined in package body." >>>> >>>> -- >>>> Thanks and Regards, >>>> Aditya Toshniwal >>>> Software Engineer | EnterpriseDB India | Pune >>>> "Don't Complain about Heat, Plant a TREE" >>>> >>> >>> >>> -- >>> *Thanks & Regards* >>> *Akshay Joshi* >>> >>> *Sr. Software Architect* >>> *EnterpriseDB Software India Private Limited* >>> *Mobile: +91 976-788-8246* >>> >> >> >> -- >> Thanks and Regards, >> Aditya Toshniwal >> Software Engineer | EnterpriseDB India | Pune >> "Don't Complain about Heat, Plant a TREE" >> > > > -- > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EnterpriseDB UK: http://www.enterprisedb.com > The Enterprise PostgreSQL Company > -- Thanks and Regards, Aditya Toshniwal Software Engineer | EnterpriseDB India | Pune "Don't Complain about Heat, Plant a TREE" Attachments: [application/octet-stream] RM4329_v2.part2.patch (614B, 3-RM4329_v2.part2.patch) download | inline diff: diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index babd1efa..9a01ac32 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -568,7 +568,7 @@ def direct_new(trans_id): user_agent = UserAgent(request.headers.get('User-Agent')) function_arguments = '(' - if 'functionData' in session: + if de_inst.function_data is not None: 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'] != '': ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: [pgAdmin][RM4329] Initialization error when parameterised functions debugged in parallel in two separate tabs @ 2019-06-14 11:47 Dave Page <[email protected]> parent: Aditya Toshniwal <[email protected]> 0 siblings, 1 reply; 8+ messages in thread From: Dave Page @ 2019-06-14 11:47 UTC (permalink / raw) To: Aditya Toshniwal <[email protected]>; +Cc: Akshay Joshi <[email protected]>; pgadmin-hackers Thanks, applied. On Fri, Jun 14, 2019 at 10:38 AM Aditya Toshniwal < [email protected]> wrote: > Hi Hackers, > > I have missed a line while implementing this. Attached is the patch to fix > that. > Although it has not caused any trouble, but still it should be changed. > Kindly review. > > On Mon, Jun 10, 2019 at 7:28 PM Dave Page <[email protected]> wrote: > >> Thanks, patch applied. >> >> On Mon, Jun 10, 2019 at 1:58 PM Aditya Toshniwal < >> [email protected]> wrote: >> >>> Hi Hackers, >>> >>> Attached is the updated patch with fixes. >>> >>> On Mon, Jun 10, 2019 at 12:58 PM Akshay Joshi < >>> [email protected]> wrote: >>> >>>> Hi Aditya >>>> >>>> Following are the review comments: >>>> >>>> - "Set breakpoint" option not working, when click it throws an >>>> error. >>>> >>>> Fixed. >>> >>>> >>>> - Create an empty function and try to debug that. It should show >>>> proper error message. >>>> >>>> This seems to be a bug in the debugger itself. I'll raise a bug with >>> simulation steps if it is. But, not sure where to raise. >>> >>>> >>>> - Got the following backend error when closing the connection, >>>> please fix this: >>>> - File >>>> "E:\Projects\pgadmin4\web\pgadmin\tools\debugger\__init__.py", line 2053, >>>> in close_debugger_session >>>> conn_id=dbg_obj['exe_conn_id']) >>>> >>>> Fixed. >>> >>>> >>>> On Fri, Jun 7, 2019 at 12:21 PM Aditya Toshniwal < >>>> [email protected]> wrote: >>>> >>>>> Hi Hackers, >>>>> >>>>> Attached is the patch for debugger improvements. The changes include: >>>>> 1) Change the way debug info is stored in session. Removed redundant >>>>> session related code in debugger code. All the session related handling >>>>> done at one place. >>>>> 2) Fixed a bug where debugger was not opening for EPAS package >>>>> function. >>>>> 3) If a package is defined without body and we try to debug a >>>>> proc/func, the debugger opened a blank window. Changes made so that it will >>>>> throw error as "XYZ is not defined in package body." >>>>> >>>>> -- >>>>> Thanks and Regards, >>>>> Aditya Toshniwal >>>>> Software Engineer | EnterpriseDB India | Pune >>>>> "Don't Complain about Heat, Plant a TREE" >>>>> >>>> >>>> >>>> -- >>>> *Thanks & Regards* >>>> *Akshay Joshi* >>>> >>>> *Sr. Software Architect* >>>> *EnterpriseDB Software India Private Limited* >>>> *Mobile: +91 976-788-8246* >>>> >>> >>> >>> -- >>> Thanks and Regards, >>> Aditya Toshniwal >>> Software Engineer | EnterpriseDB India | Pune >>> "Don't Complain about Heat, Plant a TREE" >>> >> >> >> -- >> Dave Page >> Blog: http://pgsnake.blogspot.com >> Twitter: @pgsnake >> >> EnterpriseDB UK: http://www.enterprisedb.com >> The Enterprise PostgreSQL Company >> > > > -- > Thanks and Regards, > Aditya Toshniwal > Software Engineer | EnterpriseDB India | Pune > "Don't Complain about Heat, Plant a TREE" > -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EnterpriseDB UK: http://www.enterprisedb.com The Enterprise PostgreSQL Company ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: [pgAdmin][RM4329] Initialization error when parameterised functions debugged in parallel in two separate tabs @ 2019-06-19 07:04 Aditya Toshniwal <[email protected]> parent: Dave Page <[email protected]> 0 siblings, 1 reply; 8+ messages in thread From: Aditya Toshniwal @ 2019-06-19 07:04 UTC (permalink / raw) To: Dave Page <[email protected]>; +Cc: Akshay Joshi <[email protected]>; pgadmin-hackers Hi, This is not working on Python 2.7 :( Attached patch will add an __init__ file in the utils directory under debugger to make it work with python 2.7 On Fri, Jun 14, 2019 at 5:17 PM Dave Page <[email protected]> wrote: > Thanks, applied. > > On Fri, Jun 14, 2019 at 10:38 AM Aditya Toshniwal < > [email protected]> wrote: > >> Hi Hackers, >> >> I have missed a line while implementing this. Attached is the patch to >> fix that. >> Although it has not caused any trouble, but still it should be changed. >> Kindly review. >> >> On Mon, Jun 10, 2019 at 7:28 PM Dave Page <[email protected]> wrote: >> >>> Thanks, patch applied. >>> >>> On Mon, Jun 10, 2019 at 1:58 PM Aditya Toshniwal < >>> [email protected]> wrote: >>> >>>> Hi Hackers, >>>> >>>> Attached is the updated patch with fixes. >>>> >>>> On Mon, Jun 10, 2019 at 12:58 PM Akshay Joshi < >>>> [email protected]> wrote: >>>> >>>>> Hi Aditya >>>>> >>>>> Following are the review comments: >>>>> >>>>> - "Set breakpoint" option not working, when click it throws an >>>>> error. >>>>> >>>>> Fixed. >>>> >>>>> >>>>> - Create an empty function and try to debug that. It should show >>>>> proper error message. >>>>> >>>>> This seems to be a bug in the debugger itself. I'll raise a bug with >>>> simulation steps if it is. But, not sure where to raise. >>>> >>>>> >>>>> - Got the following backend error when closing the connection, >>>>> please fix this: >>>>> - File >>>>> "E:\Projects\pgadmin4\web\pgadmin\tools\debugger\__init__.py", line 2053, >>>>> in close_debugger_session >>>>> conn_id=dbg_obj['exe_conn_id']) >>>>> >>>>> Fixed. >>>> >>>>> >>>>> On Fri, Jun 7, 2019 at 12:21 PM Aditya Toshniwal < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Hackers, >>>>>> >>>>>> Attached is the patch for debugger improvements. The changes include: >>>>>> 1) Change the way debug info is stored in session. Removed redundant >>>>>> session related code in debugger code. All the session related handling >>>>>> done at one place. >>>>>> 2) Fixed a bug where debugger was not opening for EPAS package >>>>>> function. >>>>>> 3) If a package is defined without body and we try to debug a >>>>>> proc/func, the debugger opened a blank window. Changes made so that it will >>>>>> throw error as "XYZ is not defined in package body." >>>>>> >>>>>> -- >>>>>> Thanks and Regards, >>>>>> Aditya Toshniwal >>>>>> Software Engineer | EnterpriseDB India | Pune >>>>>> "Don't Complain about Heat, Plant a TREE" >>>>>> >>>>> >>>>> >>>>> -- >>>>> *Thanks & Regards* >>>>> *Akshay Joshi* >>>>> >>>>> *Sr. Software Architect* >>>>> *EnterpriseDB Software India Private Limited* >>>>> *Mobile: +91 976-788-8246* >>>>> >>>> >>>> >>>> -- >>>> Thanks and Regards, >>>> Aditya Toshniwal >>>> Software Engineer | EnterpriseDB India | Pune >>>> "Don't Complain about Heat, Plant a TREE" >>>> >>> >>> >>> -- >>> Dave Page >>> Blog: http://pgsnake.blogspot.com >>> Twitter: @pgsnake >>> >>> EnterpriseDB UK: http://www.enterprisedb.com >>> The Enterprise PostgreSQL Company >>> >> >> >> -- >> Thanks and Regards, >> Aditya Toshniwal >> Software Engineer | EnterpriseDB India | Pune >> "Don't Complain about Heat, Plant a TREE" >> > > > -- > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EnterpriseDB UK: http://www.enterprisedb.com > The Enterprise PostgreSQL Company > -- Thanks and Regards, Aditya Toshniwal Software Engineer | EnterpriseDB India | Pune "Don't Complain about Heat, Plant a TREE" Attachments: [application/octet-stream] debugger.py27.patch (151B, 3-debugger.py27.patch) download | inline diff: diff --git a/web/pgadmin/tools/debugger/utils/__init__.py b/web/pgadmin/tools/debugger/utils/__init__.py new file mode 100644 index 00000000..e69de29b ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: [pgAdmin][RM4329] Initialization error when parameterised functions debugged in parallel in two separate tabs @ 2019-06-19 08:02 Akshay Joshi <[email protected]> parent: Aditya Toshniwal <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Akshay Joshi @ 2019-06-19 08:02 UTC (permalink / raw) To: Aditya Toshniwal <[email protected]>; +Cc: Dave Page <[email protected]>; pgadmin-hackers Thanks patch applied. On Wed, Jun 19, 2019 at 12:35 PM Aditya Toshniwal < [email protected]> wrote: > Hi, > > This is not working on Python 2.7 :( > Attached patch will add an __init__ file in the utils directory under > debugger to make it work with python 2.7 > > > On Fri, Jun 14, 2019 at 5:17 PM Dave Page <[email protected]> wrote: > >> Thanks, applied. >> >> On Fri, Jun 14, 2019 at 10:38 AM Aditya Toshniwal < >> [email protected]> wrote: >> >>> Hi Hackers, >>> >>> I have missed a line while implementing this. Attached is the patch to >>> fix that. >>> Although it has not caused any trouble, but still it should be changed. >>> Kindly review. >>> >>> On Mon, Jun 10, 2019 at 7:28 PM Dave Page <[email protected]> wrote: >>> >>>> Thanks, patch applied. >>>> >>>> On Mon, Jun 10, 2019 at 1:58 PM Aditya Toshniwal < >>>> [email protected]> wrote: >>>> >>>>> Hi Hackers, >>>>> >>>>> Attached is the updated patch with fixes. >>>>> >>>>> On Mon, Jun 10, 2019 at 12:58 PM Akshay Joshi < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Aditya >>>>>> >>>>>> Following are the review comments: >>>>>> >>>>>> - "Set breakpoint" option not working, when click it throws an >>>>>> error. >>>>>> >>>>>> Fixed. >>>>> >>>>>> >>>>>> - Create an empty function and try to debug that. It should show >>>>>> proper error message. >>>>>> >>>>>> This seems to be a bug in the debugger itself. I'll raise a bug with >>>>> simulation steps if it is. But, not sure where to raise. >>>>> >>>>>> >>>>>> - Got the following backend error when closing the connection, >>>>>> please fix this: >>>>>> - File >>>>>> "E:\Projects\pgadmin4\web\pgadmin\tools\debugger\__init__.py", line 2053, >>>>>> in close_debugger_session >>>>>> conn_id=dbg_obj['exe_conn_id']) >>>>>> >>>>>> Fixed. >>>>> >>>>>> >>>>>> On Fri, Jun 7, 2019 at 12:21 PM Aditya Toshniwal < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Hi Hackers, >>>>>>> >>>>>>> Attached is the patch for debugger improvements. The changes include: >>>>>>> 1) Change the way debug info is stored in session. Removed redundant >>>>>>> session related code in debugger code. All the session related handling >>>>>>> done at one place. >>>>>>> 2) Fixed a bug where debugger was not opening for EPAS package >>>>>>> function. >>>>>>> 3) If a package is defined without body and we try to debug a >>>>>>> proc/func, the debugger opened a blank window. Changes made so that it will >>>>>>> throw error as "XYZ is not defined in package body." >>>>>>> >>>>>>> -- >>>>>>> Thanks and Regards, >>>>>>> Aditya Toshniwal >>>>>>> Software Engineer | EnterpriseDB India | Pune >>>>>>> "Don't Complain about Heat, Plant a TREE" >>>>>>> >>>>>> >>>>>> >>>>>> -- >>>>>> *Thanks & Regards* >>>>>> *Akshay Joshi* >>>>>> >>>>>> *Sr. Software Architect* >>>>>> *EnterpriseDB Software India Private Limited* >>>>>> *Mobile: +91 976-788-8246* >>>>>> >>>>> >>>>> >>>>> -- >>>>> Thanks and Regards, >>>>> Aditya Toshniwal >>>>> Software Engineer | EnterpriseDB India | Pune >>>>> "Don't Complain about Heat, Plant a TREE" >>>>> >>>> >>>> >>>> -- >>>> Dave Page >>>> Blog: http://pgsnake.blogspot.com >>>> Twitter: @pgsnake >>>> >>>> EnterpriseDB UK: http://www.enterprisedb.com >>>> The Enterprise PostgreSQL Company >>>> >>> >>> >>> -- >>> Thanks and Regards, >>> Aditya Toshniwal >>> Software Engineer | EnterpriseDB India | Pune >>> "Don't Complain about Heat, Plant a TREE" >>> >> >> >> -- >> Dave Page >> Blog: http://pgsnake.blogspot.com >> Twitter: @pgsnake >> >> EnterpriseDB UK: http://www.enterprisedb.com >> The Enterprise PostgreSQL Company >> > > > -- > Thanks and Regards, > Aditya Toshniwal > Software Engineer | EnterpriseDB India | Pune > "Don't Complain about Heat, Plant a TREE" > -- *Thanks & Regards* *Akshay Joshi* *Sr. Software Architect* *EnterpriseDB Software India Private Limited* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 8+ messages in thread
end of thread, other threads:[~2019-06-19 08:02 UTC | newest] Thread overview: 8+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2019-06-07 06:50 [pgAdmin][RM4329] Initialization error when parameterised functions debugged in parallel in two separate tabs Aditya Toshniwal <[email protected]> 2019-06-10 07:28 ` Akshay Joshi <[email protected]> 2019-06-10 12:57 ` Aditya Toshniwal <[email protected]> 2019-06-10 13:58 ` Dave Page <[email protected]> 2019-06-14 09:37 ` Aditya Toshniwal <[email protected]> 2019-06-14 11:47 ` Dave Page <[email protected]> 2019-06-19 07:04 ` Aditya Toshniwal <[email protected]> 2019-06-19 08:02 ` Akshay Joshi <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox