diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index 80ec9a892..ada149231 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -41,6 +41,7 @@ MODULE_NAME = 'debugger' # Constants PLDBG_EXTN = 'pldbgapi' ASYNC_OK = 1 +BUSY = 3 DEBUGGER_SQL_PATH = 'debugger/sql' DEBUGGER_SQL_V1_PATH = 'debugger/sql/v1' DEBUGGER_SQL_V3_PATH = 'debugger/sql/v3' @@ -263,6 +264,9 @@ def index(): def execute_dict_search_path(conn, sql, search_path): + if conn.transaction_status() == 1: + return True, BUSY + sql_search = SET_SEARCH_PATH.format(search_path) status, res = conn.execute_void(sql_search) @@ -276,6 +280,9 @@ def execute_dict_search_path(conn, sql, search_path): def execute_async_search_path(conn, sql, search_path): + if conn.transaction_status() == 1: + return True, BUSY + sql_search = SET_SEARCH_PATH.format(search_path) status, res = conn.execute_void(sql_search) @@ -1214,18 +1221,24 @@ def execute_debugger_query(trans_id, query_type): status, result = execute_async_search_path( conn, sql, de_inst.debugger_data['search_path']) + if result == BUSY: + return make_json_response( + data={'status': 'Busy', 'result': []} + ) + if result and 'select() failed waiting for target' in result: status = True result = None if not status: return internal_server_error(errormsg=result) + return make_json_response( data={'status': status, 'result': result} ) - status, result = execute_dict_search_path( conn, sql, de_inst.debugger_data['search_path']) + if not status: return internal_server_error(errormsg=result) if query_type == 'abort_target': @@ -1234,6 +1247,11 @@ def execute_debugger_query(trans_id, query_type): data={'status': 'Success', 'result': result} ) + if result == BUSY: + return make_json_response( + data={'status': 'Busy', 'result': []} + ) + return make_json_response( data={'status': 'Success', 'result': result['rows']} ) @@ -1356,6 +1374,11 @@ def start_execution(trans_id, port_num): if not status_port: return internal_server_error(errormsg=res_port) + if res_port == BUSY: + return make_json_response( + data={'status': 'Busy', 'result': []} + ) + de_inst.debugger_data['restart_debug'] = 0 de_inst.debugger_data['frame_id'] = 0 de_inst.debugger_data['exe_conn_id'] = exe_conn_id @@ -1430,6 +1453,11 @@ def set_clear_breakpoint(trans_id, line_no, set_type): if not status: return internal_server_error(errormsg=res_stack) + if res_stack == BUSY: + return make_json_response( + data={'status': 'Busy', 'result': []} + ) + # 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. @@ -1450,9 +1478,16 @@ def set_clear_breakpoint(trans_id, line_no, set_type): status, result = execute_dict_search_path( conn, sql, de_inst.debugger_data['search_path']) - result = result['rows'] + if not status: return internal_server_error(errormsg=result) + + if result == BUSY: + return make_json_response( + data={'status': 'Busy', 'result': []} + ) + + result = result['rows'] else: status = False result = SERVER_CONNECTION_CLOSED @@ -1536,6 +1571,11 @@ def clear_all_breakpoint(trans_id): conn, sql, de_inst.debugger_data['search_path']) if not status: return internal_server_error(errormsg=result) + + if result == BUSY: + return make_json_response( + data={'status': 'Busy', 'result': []} + ) result = result['rows'] else: return make_json_response(data={'status': False}) @@ -1599,6 +1639,11 @@ def deposit_parameter_value(trans_id): if not status: return internal_server_error(errormsg=result) + if result == BUSY: + return make_json_response( + data={'status': 'Busy', 'result': []} + ) + # Check if value deposited successfully or not and depending on # the result, return the message information. if result['rows'][0]['pldbg_deposit_value']: @@ -1670,6 +1715,12 @@ def select_frame(trans_id, frame_id): status, result = execute_dict_search_path( conn, sql, de_inst.debugger_data['search_path']) + + if result == BUSY: + return make_json_response( + data={'status': 'Busy', 'result': []} + ) + if not status: return internal_server_error(errormsg=result) else: diff --git a/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx b/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx index d9a20adb9..0a62f13f8 100644 --- a/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx +++ b/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx @@ -66,21 +66,34 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, eventBus.current.fireEvent(DEBUGGER_EVENTS.GET_TOOL_BAR_BUTTON_STATUS, { disabled: false }); }; + const getExecuteQueryUrl = (transId, queryType) => { + return url_for('debugger.execute_query', { + 'trans_id': transId, + 'query_type': queryType, + }); + }; + + const isSuccess = (httpStatus) => { + return httpStatus.data.data.status === 'Success'; + }; + + const isBusy = (httpStatus) => { + return httpStatus.data.data.status === 'Busy'; + }; + // Function to get the breakpoint information from the server const getBreakpointInformation = (transId, callBackFunc) => { let result = ''; // Make ajax call to listen the database message - let baseUrl = url_for('debugger.execute_query', { - 'trans_id': transId, - 'query_type': 'get_breakpoints', - }); api({ - url: baseUrl, + url: getExecuteQueryUrl(transId, 'get_breakpoints'), method: 'GET', }) .then(function (res) { - if (res.data.data.status === 'Success') { + if (isBusy(res)) { + getBreakpointInformation(transId, callBackFunc); + } else if (isSuccess(res)) { result = res.data.data.result; if (callBackFunc) { callBackFunc(result); @@ -189,11 +202,11 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, method: 'GET', }) .then(function (res) { - if (res.data.data.status === 'Success') { + if (isSuccess(res)) { enableToolbarButtons(); // If status is Success then find the port number to attach the executer. startExecution(transId, res.data.data.result); - } else if (res.data.data.status === 'Busy') { + } else if (isBusy(res)) { // If status is Busy then poll the result by recursive call to the poll function messages(transId); } else if (res.data.data.status === 'NotConnected') { @@ -224,7 +237,9 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, method: 'GET', }) .then(function (res) { - if (res.data.data.status === 'Success') { + if (isBusy(res)) { + startExecution(transId, port_num); + } else if (isSuccess(res)) { // If status is Success then find the port number to attach the executer. executeQuery(transId); } else if (res.data.data.status === 'NotConnected') { @@ -244,17 +259,12 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, const executeQuery = (transId) => { // Make ajax call to listen the database message - let baseUrl = url_for( - 'debugger.execute_query', { - 'trans_id': transId, - 'query_type': 'wait_for_breakpoint', - }); api({ - url: baseUrl, + url: getExecuteQueryUrl(transId, 'wait_for_breakpoint'), method: 'GET', }) .then(function (res) { - if (res.data.data.status === 'Success') { + if (isSuccess(res)) { // set the return code to the code editor text area if ( res.data.data.result[0].src != null && @@ -388,24 +398,26 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, }) .catch(raiseClearBrekpointError); }; - // Make ajax call to listen the database message - let baseUrl = url_for('debugger.execute_query', { - 'trans_id': params.transId, - 'query_type': 'get_breakpoints', - }); - api({ - url: baseUrl, - method: 'GET', - }) - .then(function (res) { - if (res.data.data.status === 'Success') { - let result = res.data.data.result; - clearBreakpoint(result); - } else if (res.data.data.status === 'NotConnected') { - raiseFetchingBreakpointError(); - } + + let get_breakpoint = () => { + // Make ajax call to listen the database message + api({ + url: getExecuteQueryUrl(params.transId, 'get_breakpoints'), + method: 'GET', }) - .catch(raiseFetchingBreakpointError); + .then(function (res) { + if (isBusy(res)) { + get_breakpoint(); + } else if (isSuccess(res)) { + let result = res.data.data.result; + clearBreakpoint(result); + } else if (res.data.data.status === 'NotConnected') { + raiseFetchingBreakpointError(); + } + }) + .catch(raiseFetchingBreakpointError); + }; + get_breakpoint(); }; @@ -419,13 +431,8 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, const stopDebugging = () => { disableToolbarButtons(); // Make ajax call to listen the database message - let baseUrl = url_for( - 'debugger.execute_query', { - 'trans_id': params.transId, - 'query_type': 'abort_target', - }); api({ - url: baseUrl, + url: getExecuteQueryUrl(params.transId, 'abort_target'), method: 'GET', }) .then(function (res) { @@ -627,7 +634,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, method: 'GET', }) .then(function (res) { - if (res.data.data.status === 'Success') { + if (isSuccess(res)) { if (res.data.data.result == undefined) { /* "result" is undefined only in case of EDB procedure. @@ -655,7 +662,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, } else { updateResultAndMessages(res); } - } else if (res.data.data.status === 'Busy') { + } else if (isBusy(res)) { // If status is Busy then poll the result by recursive call to // the poll function pollEndExecutionResult(transId); @@ -692,16 +699,14 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, restart(params.transId); } else { // Make ajax call to listen the database message - let baseUrl = url_for('debugger.execute_query', { - 'trans_id': params.transId, - 'query_type': 'continue', - }); api({ - url: baseUrl, + url: getExecuteQueryUrl(params.transId, 'continue'), method: 'GET', }) .then(function (res) { - if (res.data.data.status) { + if (isBusy(res)) { + continueDebugger(); + } else if (res.data.data.status) { pollResult(params.transId); } else { pgAdmin.Browser.notifier.alert( @@ -723,12 +728,8 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, disableToolbarButtons(); // Make ajax call to listen the database message - let baseUrl = url_for('debugger.execute_query', { - 'trans_id': params.transId, - 'query_type': 'step_over', - }); api({ - url: baseUrl, + url: getExecuteQueryUrl(params.transId, 'step_over'), method: 'GET', }) .then(function (res) { @@ -775,22 +776,21 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, // Get the local variable information of the functions and update the grid const getLocalVariables = (transId) => { // Make ajax call to listen the database message - let baseUrl = url_for( - 'debugger.execute_query', { - 'trans_id': transId, - 'query_type': 'get_variables', - }); api({ - url: baseUrl, + url: getExecuteQueryUrl(transId, 'get_variables'), method: 'GET', }) .then(function (res) { - if (res.data.data.status === 'Success') { + if (isBusy(res)) { + getLocalVariables(transId); + } + else if (isSuccess(res)) { // Call function to update local variables let variablesResult = res.data.data.result.filter((lvar) => { return lvar.varclass == 'L'; }); eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_LOCAL_VARIABLES, variablesResult); + enableToolbarButtons(); let parametersResult = res.data.data.result.filter((lvar) => { return lvar.varclass == 'A'; @@ -822,17 +822,14 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, const getStackInformation = (transId) => { // Make ajax call to listen the database message - let baseUrl = url_for( - 'debugger.execute_query', { - 'trans_id': transId, - 'query_type': 'get_stack_info', - }); api({ - url: baseUrl, + url: getExecuteQueryUrl(transId, 'get_stack_info'), method: 'GET', }) .then(function (res) { - if (res.data.data.status === 'Success') { + if (isBusy(res)) { + getStackInformation(transId); + } else if (isSuccess(res)) { // Call function to update stack information eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_STACK, res.data.data.result); // Call function to create and update stack information @@ -867,6 +864,8 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, const updateInfo = (res, transId) => { if (!params.directDebugger.debug_type && !params.directDebugger.first_time_indirect_debug) { + // Enable all the buttons as we got the results + enableToolbarButtons(); setLoaderText(''); editor.current.setActiveLine(-1); clearAllBreakpoint(transId); @@ -940,16 +939,14 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, }, }) .then(function (res) { - if (res.data.data.status === 'Success') { + if (isSuccess(res)) { // If no result then poll again to wait for results. if (res.data.data.result == null || res.data.data.result.length == 0) { pollResult(transId); } else { updateInfo(res, transId); - // Enable all the buttons as we got the results - enableToolbarButtons(); } - } else if (res.data.data.status === 'Busy') { + } else if (isBusy(res)) { params.directDebugger.polling_timeout_idle = true; checkDebuggerStatus(transId); } else if (res.data.data.status === 'NotConnected') { @@ -967,16 +964,14 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, const stepInto = () => { disableToolbarButtons(); // Make ajax call to listen the database message - let baseUrl = url_for('debugger.execute_query', { - 'trans_id': params.transId, - 'query_type': 'step_into', - }); api({ - url: baseUrl, + url: getExecuteQueryUrl(params.transId, 'step_into'), method: 'GET', }) .then(function (res) { - if (res.data.data.status) { + if(isBusy(res)) { + stepInto(); + } else if (res.data.data.status) { pollResult(params.transId); } else { pgAdmin.Browser.notifier.alert( @@ -1005,7 +1000,9 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId, data: data, }) .then(function (res) { - if (res.data.data.status) { + if(isBusy(res)) { + onChangesLocalVarParameters(data); + } else if (res.data.data.status) { // Get the updated variables value getLocalVariables(params.transId); // Show the message to the user that deposit value is success or failure diff --git a/web/regression/feature_tests/xss_checks_pgadmin_debugger_test.py b/web/regression/feature_tests/xss_checks_pgadmin_debugger_test.py index c2d98ead1..5e422ed0f 100644 --- a/web/regression/feature_tests/xss_checks_pgadmin_debugger_test.py +++ b/web/regression/feature_tests/xss_checks_pgadmin_debugger_test.py @@ -78,7 +78,6 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest): By.CSS_SELECTOR, "div[data-label='Debugging']") ).perform() - # time.sleep(2) wait.until(EC.presence_of_element_located( (By.CSS_SELECTOR, "li[data-label='Debug']"))) @@ -87,7 +86,7 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest): # We need to check if debugger plugin is installed or not try: - wait = WebDriverWait(self.page.driver, 2) + wait = WebDriverWait(self.page.driver, 10) is_error = wait.until(EC.presence_of_element_located( (By.XPATH, "//div[contains(@class,'MuiDialogTitle-root')]" "//div[text()='Debugger Error']") @@ -120,6 +119,7 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest): wait.until(EC.presence_of_element_located( (By.XPATH, "//span[contains(.,'Hello, pgAdmin4')]")) ) + self.page.click_element( self.page.driver.find_elements(By.XPATH, "//button")[2] )