////////////////////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2018, 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'; 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) return; const self = this; let service = axios.create({}); self.explainPlan = explainPlan; const sqlStatementWithAnalyze = ExecuteQuery.prepareAnalyzeSql(sqlStatement, explainPlan); self.initializeExecutionOnSqlEditor(sqlStatementWithAnalyze); service.post( this.generateURLReconnectionFlag(connect), JSON.stringify(sqlStatementWithAnalyze), {headers: {'Content-Type': 'application/json'}}) .then(function (result) { let httpMessageData = result.data; self.removeGridViewMarker(); if (ExecuteQuery.isSqlCorrect(httpMessageData)) { self.loadingScreen.setMessage('Waiting for the query execution to complete...'); self.updateSqlEditorStateWithInformationFromServer(httpMessageData.data); // If status is True then poll the result. self.delayedPoll(); } else { self.loadingScreen.hide(); self.enableSQLEditorButtons(); self.sqlServerObject.update_msg_history(false, httpMessageData.data.result); // 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; let service = axios.create({}); service.get( url_for('sqleditor.poll', { 'trans_id': self.sqlServerObject.transId, }) ).then( (httpMessage) => { if (ExecuteQuery.isQueryFinished(httpMessage)) { self.loadingScreen.setMessage('Loading data from the database server and rendering...'); self.sqlServerObject.call_render_after_poll(httpMessage.data.data); } 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); } } ).catch( error => { // Enable/Disable query tool button only if is_query_tool is true. self.sqlServerObject.resetQueryHistoryObject(self.sqlServerObject); self.loadingScreen.hide(); if (self.sqlServerObject.is_query_tool) { self.enableSQLEditorButtons(); } 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); }); } initializeExecutionOnSqlEditor(sqlStatement) { this.loadingScreen.show('Initializing query execution...'); $('#btn-flash').prop('disabled', true); 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(); } 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('execute', [this.explainPlan]); this.userManagement.pgaLogin(); } if (httpErrorHandler.httpResponseRequiresNewTransaction(httpMessage.response)) { this.sqlServerObject.saveState('execute', [this.explainPlan]); this.sqlServerObject.initTransaction(); } if (this.wasDatabaseConnectionLost(httpMessage)) { this.sqlServerObject.saveState('execute', [this.explainPlan]); this.sqlServerObject.handle_connection_lost(false, httpMessage); } 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'; } 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'); } } enableSQLEditorButtons() { this.sqlServerObject.disable_tool_buttons(false); $('#btn-cancel-query').prop('disabled', true); } disableSQLEditorButtons() { this.sqlServerObject.disable_tool_buttons(true); $('#btn-cancel-query').prop('disabled', false); } 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; } 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, };