////////////////////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2021, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////////////////// import gettext from '../gettext'; import $ from 'jquery'; import url_for from '../url_for'; import axios from 'axios'; import * as httpErrorHandler from './query_tool_http_error_handler'; import * as queryTxnStatus from 'sources/sqleditor/query_txn_status_constants'; import * as SqlEditorUtils from 'sources/sqleditor_utils'; class LoadingScreen { constructor(sqlEditor) { this.sqlEditor = sqlEditor; } setMessage(message) { this.sqlEditor.trigger( 'pgadmin-sqleditor:loading-icon:message', gettext(message) ); } show(withMessage) { this.sqlEditor.trigger( 'pgadmin-sqleditor:loading-icon:show', withMessage ); } hide() { this.sqlEditor.trigger('pgadmin-sqleditor:loading-icon:hide'); } } class ExecuteQuery { constructor(sqlEditor, userManagement) { this.sqlServerObject = sqlEditor; this.loadingScreen = new LoadingScreen(sqlEditor); this.userManagement = userManagement; } delayedPoll() { const self = this; setTimeout( () => { self.poll(); }, self.sqlServerObject.POLL_FALLBACK_TIME()); } execute(sqlStatement, explainPlan, connect) { // If it is an empty query, do nothing. if (sqlStatement.length <= 0) { // Enable query execution button if user execute empty query. $('#btn-flash').prop('disabled', false); return; } const self = this; self.explainPlan = explainPlan; const sqlStatementWithAnalyze = ExecuteQuery.prepareAnalyzeSql(sqlStatement, explainPlan); self.initializeExecutionOnSqlEditor(sqlStatementWithAnalyze); axios.post( this.generateURLReconnectionFlag(connect), JSON.stringify(sqlStatementWithAnalyze), {headers: {'Content-Type': 'application/json'}}) .then(function (result) { let httpMessageData = result.data; self.removeGridViewMarker(); self.updateSqlEditorLastTransactionStatus(httpMessageData.data.transaction_status); if (ExecuteQuery.isSqlCorrect(httpMessageData)) { self.loadingScreen.setMessage('Waiting for the query to complete...'); // Disable drop down arrow to change connections SqlEditorUtils.disable_connection_dropdown(true); self.updateSqlEditorStateWithInformationFromServer(httpMessageData.data); // If status is True then poll the result. self.delayedPoll(); } else { self.loadingScreen.hide(); self.enableSQLEditorButtons(); self.disableDownloadButton(); // Enable/Disable commit and rollback button. if (result.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INTRANS || result.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INERROR) { self.enableTransactionButtons(); } else { self.disableTransactionButtons(); } self.sqlServerObject.update_msg_history(false, httpMessageData.data.result); if ('notifies' in httpMessageData.data) self.sqlServerObject.update_notifications(httpMessageData.data.notifies); // Highlight the error in the sql panel self.sqlServerObject._highlight_error(httpMessageData.data.result); } }).catch(function (error) { self.onExecuteHTTPError(error); } ); } generateURLReconnectionFlag(shouldReconnect) { let url = url_for('sqleditor.query_tool_start', { 'trans_id': this.sqlServerObject.transId, }); if (shouldReconnect) { url += '?connect=1'; } return url; } poll() { const self = this; axios.get( url_for('sqleditor.poll', { 'trans_id': self.sqlServerObject.transId, }) ).then( (httpMessage) => { self.updateSqlEditorLastTransactionStatus(httpMessage.data.data.transaction_status); // Enable/Disable commit and rollback button. if (httpMessage.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INTRANS || httpMessage.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INERROR) { self.enableTransactionButtons(); } else { self.disableTransactionButtons(); } if (ExecuteQuery.isQueryFinished(httpMessage)) { if (this.sqlServerObject.close_on_idle_transaction && httpMessage.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_IDLE) this.sqlServerObject.check_needed_confirmations_before_closing_panel(); self.loadingScreen.setMessage('Loading data from the database server and rendering...'); self.sqlServerObject.call_render_after_poll(httpMessage.data.data); if ('notifies' in httpMessage.data.data) self.sqlServerObject.update_notifications(httpMessage.data.data.notifies); } else if (ExecuteQuery.isQueryStillRunning(httpMessage)) { // If status is Busy then poll the result by recursive call to the poll function this.delayedPoll(); self.sqlServerObject.setIsQueryRunning(true); if (httpMessage.data.data.result) { self.sqlServerObject.update_msg_history(httpMessage.data.data.status, httpMessage.data.data.result, false); } } else if (ExecuteQuery.isConnectionToServerLostWhilePolling(httpMessage)) { self.loadingScreen.hide(); // Enable/Disable query tool button only if is_query_tool is true. if (self.sqlServerObject.is_query_tool) { self.enableSQLEditorButtons(); } self.sqlServerObject.update_msg_history(false, httpMessage.data.data.result, true); } else if (ExecuteQuery.isQueryCancelled(httpMessage)) { self.loadingScreen.hide(); self.sqlServerObject.update_msg_history(false, 'Execution Cancelled!', true); } // Enable connection list drop down again for query tool only if(self.sqlServerObject.is_query_tool && !ExecuteQuery.isQueryStillRunning(httpMessage)){ SqlEditorUtils.disable_connection_dropdown(false); } } ).catch( error => { // Enable/Disable query tool button only if is_query_tool is true. self.sqlServerObject.resetQueryHistoryObject(self.sqlServerObject); self.loadingScreen.hide(); self.sqlServerObject.setIsQueryRunning(false); if (self.sqlServerObject.is_query_tool) { self.enableSQLEditorButtons(); SqlEditorUtils.disable_connection_dropdown(false); } if(error.response) { if(ExecuteQuery.wasConnectionLostToPythonServer(error.response)) { self.handleConnectionToServerLost(); return; } const errorData = error.response.data; if (self.userManagement.isPgaLoginRequired(errorData)) { return self.userManagement.pgaLogin(); } let msg = ExecuteQuery.extractErrorMessage(errorData); self.sqlServerObject.update_msg_history(false, msg); // Highlight the error in the sql panel self.sqlServerObject._highlight_error(msg); } else if(error.request) { self.handleConnectionToServerLost(); return; } else { console.error(error); } }); } initializeExecutionOnSqlEditor(sqlStatement) { this.loadingScreen.show(gettext('Running query...')); $('#btn-flash').prop('disabled', true); this.disableDownloadButton(); this.sqlServerObject.query_start_time = new Date(); if (typeof sqlStatement === 'object') { this.sqlServerObject.query = sqlStatement['sql']; } else { this.sqlServerObject.query = sqlStatement; } this.sqlServerObject.rows_affected = 0; this.sqlServerObject._init_polling_flags(); this.disableSQLEditorButtons(); this.disableTransactionButtons(); } static prepareAnalyzeSql(sqlStatement, analyzeSql) { let sqlStatementWithAnalyze = { sql: sqlStatement, explain_plan: analyzeSql, }; return sqlStatementWithAnalyze; } onExecuteHTTPError(httpMessage) { this.loadingScreen.hide(); this.enableSQLEditorButtons(); if (ExecuteQuery.wasConnectionLostToPythonServer(httpMessage.response)) { this.handleConnectionToServerLost(); return; } if (this.userManagement.isPgaLoginRequired(httpMessage.response)) { this.sqlServerObject.saveState('check_data_changes_to_execute_query', [this.explainPlan]); this.userManagement.pgaLogin(); } if (httpErrorHandler.httpResponseRequiresNewTransaction(httpMessage.response)) { this.sqlServerObject.saveState('check_data_changes_to_execute_query', [this.explainPlan]); this.sqlServerObject.initTransaction(); } if (this.wasDatabaseConnectionLost(httpMessage)) { this.sqlServerObject.saveState('check_data_changes_to_execute_query', [this.explainPlan]); this.sqlServerObject.handle_connection_lost(false, httpMessage); } if(this.isCryptKeyMissing(httpMessage)) { this.sqlServerObject.saveState('check_data_changes_to_execute_query', [this.explainPlan]); this.sqlServerObject.handle_cryptkey_missing(); return; } let msg = httpMessage.response.data.errormsg; this.sqlServerObject.update_msg_history(false, msg); } wasDatabaseConnectionLost(httpMessage) { return httpMessage.response.status === 503 && httpMessage.response.data.info !== undefined && httpMessage.response.data.info === 'CONNECTION_LOST'; } isCryptKeyMissing(httpMessage) { return httpMessage.response.status === 503 && httpMessage.response.data.info !== undefined && httpMessage.response.data.info === 'CRYPTKEY_MISSING'; } removeGridViewMarker() { if (this.sqlServerObject.gridView.marker) { this.sqlServerObject.gridView.marker.clear(); delete this.sqlServerObject.gridView.marker; this.sqlServerObject.gridView.marker = null; // Remove already existing marker this.sqlServerObject.gridView.query_tool_obj.removeLineClass(this.sqlServerObject.marked_line_no, 'wrap', 'CodeMirror-activeline-background'); } } disableDownloadButton() { this.sqlServerObject.enable_disable_download_btn(true); } enableSQLEditorButtons() { this.sqlServerObject.disable_tool_buttons(false); } disableSQLEditorButtons() { this.sqlServerObject.disable_tool_buttons(true); } enableTransactionButtons() { this.sqlServerObject.disable_transaction_buttons(false); } disableTransactionButtons() { this.sqlServerObject.special_sql = undefined; this.sqlServerObject.disable_transaction_buttons(true); } static wasConnectionLostToPythonServer(httpResponse) { return _.isUndefined(httpResponse) || _.isUndefined(httpResponse.data); } handleConnectionToServerLost() { this.sqlServerObject.update_msg_history(false, gettext('Not connected to the server or the connection to the server has been closed.') ); } updateSqlEditorStateWithInformationFromServer(messageData) { this.sqlServerObject.can_edit = messageData.can_edit; this.sqlServerObject.can_filter = messageData.can_filter; this.sqlServerObject.info_notifier_timeout = messageData.info_notifier_timeout; } updateSqlEditorLastTransactionStatus(transactionStatus) { this.sqlServerObject.last_transaction_status = transactionStatus; } static isSqlCorrect(httpMessageData) { return httpMessageData.data.status; } static extractErrorMessage(httpMessage) { let msg = httpMessage.errormsg; if (httpMessage.responseJSON !== undefined && httpMessage.responseJSON.errormsg !== undefined) msg = httpMessage.responseJSON.errormsg; return msg; } static isQueryFinished(httpMessage) { return httpMessage.data.data.status === 'Success'; } static isQueryStillRunning(httpMessage) { return httpMessage.data.data.status === 'Busy'; } static isQueryCancelled(httpMessage) { return httpMessage.data.data.status === 'Cancel'; } static isConnectionToServerLostWhilePolling(httpMessage) { return httpMessage.data.data.status === 'NotConnected'; } } module.exports = { ExecuteQuery: ExecuteQuery, };