public inbox for [email protected]  
help / color / mirror / Atom feed
From: Joao De Almeida Pereira <[email protected]>
To: pgadmin-hackers <[email protected]>
Subject: [pgadmin][patch] [GreenPlum] When user press Explain Plan and Explain analyze plan an error is displayed
Date: Fri, 02 Feb 2018 22:50:30 +0000
Message-ID: <CAE+jjakOhMJBMqsUaQuL9w4an0V4Q4nvY51K3GqWzm8jbj9cog@mail.gmail.com> (raw)

Hi Hackers,
This is quite a big patch in order to solve the problem with the Explain
Plan.

We sent 2 patches that have the following:
*- update-javascript-packages.diff *
    Add package:
     is-docker to select a specific setting when running the Chrome tests in
     Docker

    Upgrade the version of:
    - babel-loader
    - extract-text-webpack-plugin
    - jasmine-core
    - jasmine-enzyme
    - moment
*- explain-plan-greenplum.diff*
  Extract SQLEditor.execute and SQLEditor._poll into their own files and
add test around them
  Extract SQLEditor backend functions that start executing query to their
own files and add tests around it
  Move the Explain SQL from the front-end and now pass the Explain plan
parameters as a JSON object in the start query call.
  Extract the compile_template_name into a function that can be used by the
different places that try to select the version of the template and the
server type


Thanks
Joao


Attachments:

  [application/octet-stream] explain-plan-greenplum.diff (182.4K, 3-explain-plan-greenplum.diff)
  download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_utils.py
new file mode 100644
index 00000000..e69de29b
diff --git a/web/pgadmin/static/js/sqleditor/execute_query.js b/web/pgadmin/static/js/sqleditor/execute_query.js
new file mode 100644
index 00000000..e91c9e85
--- /dev/null
+++ b/web/pgadmin/static/js/sqleditor/execute_query.js
@@ -0,0 +1,287 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// 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 transaction from './is_new_transaction_required';
+
+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) {
+    // 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(
+      url_for('sqleditor.query_tool_start', {
+        'trans_id': self.sqlServerObject.transId,
+      }),
+      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.response.data);
+      }
+    );
+  }
+
+  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 => {
+        const errorData = error.response.data;
+        // 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.wasConnectionLostToServer(errorData)) {
+          self.handleConnectionToServerLost();
+          return;
+        }
+        if (self.userManagement.is_pga_login_required(errorData)) {
+          return self.userManagement.pga_login();
+        }
+
+        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.wasConnectionLostToServer(httpMessage)) {
+      this.handleConnectionToServerLost();
+      return;
+    }
+
+    if (this.userManagement.is_pga_login_required(httpMessage)) {
+      this.sqlServerObject.save_state('execute', [this.explainPlan]);
+      this.userManagement.pga_login();
+    }
+
+    if (transaction.is_new_transaction_required(httpMessage)) {
+      this.sqlServerObject.save_state('execute', [this.explainPlan]);
+      this.sqlServerObject.init_transaction();
+    }
+
+    let msg = httpMessage.errormsg;
+    if (httpMessage.responseJSON !== undefined) {
+      if (httpMessage.responseJSON.errormsg !== undefined) {
+        msg = httpMessage.responseJSON.errormsg;
+      }
+
+      if (httpMessage.status === 503 && httpMessage.responseJSON.info !== undefined &&
+        httpMessage.responseJSON.info === 'CONNECTION_LOST') {
+        setTimeout(function () {
+          this.sqlServerObject.save_state('execute', [this.explainPlan]);
+          this.sqlServerObject.handle_connection_lost(false, httpMessage);
+        });
+      }
+    }
+
+    this.sqlServerObject.update_msg_history(false, msg);
+  }
+
+  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 wasConnectionLostToServer(errorMessage) {
+    return errorMessage.readyState === 0;
+  }
+
+  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,
+};
diff --git a/web/pgadmin/static/js/sqleditor/is_new_transaction_required.js b/web/pgadmin/static/js/sqleditor/is_new_transaction_required.js
new file mode 100644
index 00000000..9d83c926
--- /dev/null
+++ b/web/pgadmin/static/js/sqleditor/is_new_transaction_required.js
@@ -0,0 +1,14 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+
+export function is_new_transaction_required(xhr) {
+  return xhr.status === 404 && xhr.responseJSON &&
+    xhr.responseJSON.info &&
+    xhr.responseJSON.info === 'DATAGRID_TRANSACTION_REQUIRED';
+}
diff --git a/web/pgadmin/static/js/sqleditor/query_tool_actions.js b/web/pgadmin/static/js/sqleditor/query_tool_actions.js
index 44a674b8..01d19215 100644
--- a/web/pgadmin/static/js/sqleditor/query_tool_actions.js
+++ b/web/pgadmin/static/js/sqleditor/query_tool_actions.js
@@ -2,19 +2,19 @@ import $ from 'jquery';
 
 let queryToolActions = {
   _verbose: function () {
-    return $('.explain-verbose').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+    return !$('.explain-verbose').hasClass('visibility-hidden');
   },
 
   _costsEnabled: function () {
-    return $('.explain-costs').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+    return !$('.explain-costs').hasClass('visibility-hidden');
   },
 
   _buffers: function () {
-    return $('.explain-buffers').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+    return !$('.explain-buffers').hasClass('visibility-hidden');
   },
 
   _timing: function () {
-    return $('.explain-timing').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+    return !$('.explain-timing').hasClass('visibility-hidden');
   },
 
   _clearMessageTab: function () {
@@ -35,18 +35,35 @@ let queryToolActions = {
     let verbose = this._verbose();
     let buffers = this._buffers();
     let timing = this._timing();
-    let explainAnalyzeQuery = `EXPLAIN (FORMAT JSON, ANALYZE ON, VERBOSE ${verbose}, COSTS ${costEnabled}, BUFFERS ${buffers}, TIMING ${timing}) `;
+    const explainObject = {
+      format: 'json',
+      analyze: true,
+      verbose: verbose,
+      costs: costEnabled,
+      buffers: buffers,
+      timing: timing,
+      summary: false,
+    };
     this._clearMessageTab();
-    sqlEditorController.execute(explainAnalyzeQuery);
+    sqlEditorController.execute(explainObject);
   },
 
   explain: function (sqlEditorController) {
     let costEnabled = this._costsEnabled();
     let verbose = this._verbose();
 
-    let explainQuery = `EXPLAIN (FORMAT JSON, ANALYZE OFF, VERBOSE ${verbose}, COSTS ${costEnabled}, BUFFERS OFF, TIMING OFF) `;
+    // let explainQuery = `EXPLAIN (FORMAT JSON, ANALYZE OFF, VERBOSE ${verbose}, COSTS ${costEnabled}, BUFFERS OFF, TIMING OFF) `;
+    const explainObject = {
+      format: 'json',
+      analyze: false,
+      verbose: verbose,
+      costs: costEnabled,
+      buffers: false,
+      timing: false,
+      summary: false,
+    };
     this._clearMessageTab();
-    sqlEditorController.execute(explainQuery);
+    sqlEditorController.execute(explainObject);
   },
 
   download: function (sqlEditorController) {
diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py
index b1ca3b87..f8870036 100644
--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -8,28 +8,32 @@
 ##########################################################################
 
 """A blueprint module implementing the sqleditor frame."""
-import simplejson as json
+import codecs
 import os
 import pickle
 import random
-import codecs
 
-from flask import Response, url_for, render_template, session, request,\
+import simplejson as json
+from flask import Response, url_for, render_template, session, request, \
     current_app
 from flask_babel import gettext
 from flask_security import login_required
+
+from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT
+from pgadmin.misc.file_manager import Filemanager
 from pgadmin.tools.sqleditor.command import QueryToolCommand
+from pgadmin.tools.sqleditor.utils.constant_definition import ASYNC_OK, ASYNC_EXECUTION_ABORTED, \
+    CONNECTION_STATUS_MESSAGE_MAPPING, TX_STATUS_INERROR
+from pgadmin.tools.sqleditor.utils.start_running_query import StartRunningQuery
+from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import update_session_grid_transaction
 from pgadmin.utils import PgAdminModule
 from pgadmin.utils import get_storage_directory
 from pgadmin.utils.ajax import make_json_response, bad_request, \
     success_return, internal_server_error, unauthorized
 from pgadmin.utils.driver import get_driver
-from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete
-from pgadmin.misc.file_manager import Filemanager
 from pgadmin.utils.menu import MenuItem
 from pgadmin.utils.exception import ConnectionLost
-
-from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT
+from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete
 
 MODULE_NAME = 'sqleditor'
 
@@ -39,28 +43,6 @@ try:
 except ImportError:
     from urllib.parse import unquote
 
-# Async Constants
-ASYNC_OK = 1
-ASYNC_READ_TIMEOUT = 2
-ASYNC_WRITE_TIMEOUT = 3
-ASYNC_NOT_CONNECTED = 4
-ASYNC_EXECUTION_ABORTED = 5
-
-# Transaction status constants
-TX_STATUS_IDLE = 0
-TX_STATUS__ACTIVE = 1
-TX_STATUS_INTRANS = 2
-TX_STATUS_INERROR = 3
-
-# Connection status codes mapping
-CONNECTION_STATUS_MESSAGE_MAPPING = dict([
-    (0, 'The session is idle and there is no current transaction.'),
-    (1, 'A command is currently in progress.'),
-    (2, 'The session is idle in a valid transaction block.'),
-    (3, 'The session is idle in a failed transaction block.'),
-    (4, 'The connection with the server is bad.')
-])
-
 
 class SqlEditorModule(PgAdminModule):
     """
@@ -376,13 +358,6 @@ def index():
     )
 
 
-def update_session_grid_transaction(trans_id, data):
-    if 'gridData' in session:
-        grid_data = session['gridData']
-        grid_data[str(trans_id)] = data
-        session['gridData'] = grid_data
-
-
 def check_transaction_status(trans_id):
     """
     This function is used to check the transaction id
@@ -458,7 +433,7 @@ def start_view_data(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
@@ -486,7 +461,7 @@ def start_view_data(trans_id):
             )
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
         # set fetched row count to 0 as we are executing query again.
         trans_obj.update_fetched_row_cnt(0)
         session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
@@ -554,107 +529,19 @@ def start_query_tool(trans_id):
         trans_id: unique transaction id
     """
 
-    if request.data:
-        sql = json.loads(request.data, encoding='utf-8')
-    else:
-        sql = request.args or request.form
-
-    connect = True if 'connect' in request.args and \
-                      request.args['connect'] == '1' else False
-
-    if 'gridData' not in session:
-        return make_json_response(
-            success=0,
-            errormsg=gettext('Transaction ID not found in the session.'),
-            info='DATAGRID_TRANSACTION_REQUIRED', status=404)
-
-    grid_data = session['gridData']
-
-    # Return from the function if transaction id not found
-    if str(trans_id) not in grid_data:
-        return make_json_response(
-            success=0,
-            errormsg=gettext('Transaction ID not found in the session.'),
-            info='DATAGRID_TRANSACTION_REQUIRED',
-            status=404)
-
-    # Fetch the object for the specified transaction id.
-    # Use pickle.loads function to get the command object
-    session_obj = grid_data[str(trans_id)]
-    trans_obj = pickle.loads(session_obj['command_obj'])
-    # set fetched row count to 0 as we are executing query again.
-    trans_obj.update_fetched_row_cnt(0)
-
-    can_edit = False
-    can_filter = False
+    sql = extract_sql_from_network_parameters(request.data, request.args, request.form)
 
-    if trans_obj is not None and session_obj is not None:
-        conn_id = trans_obj.conn_id
-        try:
-            manager = get_driver(
-                PG_DEFAULT_DRIVER).connection_manager(trans_obj.sid)
-            conn = manager.connection(did=trans_obj.did, conn_id=conn_id,
-                                      auto_reconnect=False,
-                                      use_binary_placeholder=True,
-                                      array_to_string=True)
-        except ConnectionLost as e:
-            raise
-        except Exception as e:
-            current_app.logger.error(e)
-            return internal_server_error(errormsg=str(e))
+    return StartRunningQuery(blueprint, current_app).execute(sql, trans_id, session)
 
-        # Connect to the Server if not connected.
-        if connect and not conn.connected():
-            status, msg = conn.connect()
-            if not status:
-                current_app.logger.error(msg)
-                return internal_server_error(errormsg=str(msg))
-
-        # on successful connection set the connection id to the
-        # transaction object
-        trans_obj.set_connection_id(conn_id)
-
-        # As we changed the transaction object we need to
-        # restore it and update the session variable.
-        session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
-        update_session_grid_transaction(trans_id, session_obj)
-
-        # If auto commit is False and transaction status is Idle
-        # then call is_begin_not_required() function to check BEGIN
-        # is required or not.
-
-        if not trans_obj.auto_commit \
-                and conn.transaction_status() == TX_STATUS_IDLE \
-                and is_begin_required(sql):
-            conn.execute_void("BEGIN;")
-
-        # Execute sql asynchronously with params is None
-        # and formatted_error is True.
-        try:
-            status, result = conn.execute_async(sql)
-        except ConnectionLost as e:
-            raise
-        # If the transaction aborted for some reason and
-        # Auto RollBack is True then issue a rollback to cleanup.
-        trans_status = conn.transaction_status()
-        if trans_status == TX_STATUS_INERROR and trans_obj.auto_rollback:
-            conn.execute_void("ROLLBACK;")
-
-        can_edit = trans_obj.can_edit()
-        can_filter = trans_obj.can_filter()
 
+def extract_sql_from_network_parameters(request_data, request_arguments, request_form_data):
+    if request_data:
+        sql_parameters = json.loads(request_data, encoding='utf-8')
+        if type(sql_parameters) is str:
+            return dict(sql=sql_parameters, explain_plan=None)
+        return sql_parameters
     else:
-        status = False
-        result = gettext(
-            'Either transaction object or session object not found.')
-
-    return make_json_response(
-        data={
-            'status': status, 'result': result,
-            'can_edit': can_edit, 'can_filter': can_filter,
-            'info_notifier_timeout': blueprint.info_notifier_timeout.get()
-        }
-    )
+        return request_arguments or request_form_data
 
 
 @blueprint.route(
@@ -675,13 +562,13 @@ def preferences(trans_id):
             check_transaction_status(trans_id)
 
         if error_msg == gettext(
-                'Transaction ID not found in the session.'):
+            'Transaction ID not found in the session.'):
             return make_json_response(success=0, errormsg=error_msg,
                                       info='DATAGRID_TRANSACTION_REQUIRED',
                                       status=404)
 
         if status and conn is not None \
-                and trans_obj is not None and session_obj is not None:
+            and trans_obj is not None and session_obj is not None:
             # Call the set_auto_commit and set_auto_rollback method of
             # transaction object
             trans_obj.set_auto_commit(blueprint.auto_commit.get())
@@ -751,7 +638,7 @@ def poll(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
@@ -779,7 +666,7 @@ def poll(trans_id):
             if isinstance(trans_obj, QueryToolCommand):
                 trans_status = conn.transaction_status()
                 if (trans_status == TX_STATUS_INERROR and
-                        trans_obj.auto_rollback):
+                    trans_obj.auto_rollback):
                     conn.execute_void("ROLLBACK;")
 
             st, result = conn.async_fetchmany_2darray(ON_DEMAND_RECORD_COUNT)
@@ -854,15 +741,15 @@ def poll(trans_id):
                                         typname == 'character varying'
                                     ):
                                         typname = typname + '(' + \
-                                            str(col_info['internal_size']) + \
-                                            ')'
+                                                  str(col_info['internal_size']) + \
+                                                  ')'
                                     elif (
                                         typname == 'character[]' or
                                         typname == 'character varying[]'
                                     ):
                                         typname = typname[:-2] + '(' + \
-                                            str(col_info['internal_size']) + \
-                                            ')[]'
+                                                  str(col_info['internal_size']) + \
+                                                  ')[]'
 
                                 col_info['type_name'] = typname
 
@@ -913,7 +800,7 @@ def poll(trans_id):
     if status == 'Success' and result is None:
         result = conn.status_message()
         if (result != 'SELECT 1' or result != 'SELECT 0') \
-                and result is not None and additional_messages:
+            and result is not None and additional_messages:
             result = additional_messages + result
 
     return make_json_response(
@@ -954,7 +841,7 @@ def fetch(trans_id, fetch_all=None):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
@@ -1018,7 +905,7 @@ def fetch_pg_types(columns_info, trans_obj):
 
     if oids:
         status, res = default_conn.execute_dict(
-            u"SELECT oid, format_type(oid,null) as typname FROM pg_type "
+            u"SELECT oid, format_type(oid,NULL) AS typname FROM pg_type "
             u"WHERE oid IN %s ORDER BY oid;", [tuple(oids)]
         )
 
@@ -1077,17 +964,17 @@ def save(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         # If there is no primary key found then return from the function.
         if (len(session_obj['primary_keys']) <= 0 or len(changed_data) <= 0) \
-                and 'has_oids' not in session_obj:
+            and 'has_oids' not in session_obj:
             return make_json_response(
                 data={
                     'status': False,
@@ -1146,12 +1033,12 @@ def get_filter(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         res = trans_obj.get_filter()
     else:
@@ -1183,13 +1070,13 @@ def apply_filter(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         status, res = trans_obj.set_filter(filter_sql)
 
@@ -1226,13 +1113,13 @@ def append_filter_inclusive(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         res = None
         filter_sql = ''
@@ -1282,12 +1169,12 @@ def append_filter_exclusive(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         res = None
         filter_sql = ''
@@ -1335,13 +1222,13 @@ def remove_filter(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         res = None
 
@@ -1380,13 +1267,13 @@ def set_limit(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         res = None
 
@@ -1501,13 +1388,13 @@ def get_object_name(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
         res = trans_obj.object_name
     else:
         status = False
@@ -1538,13 +1425,13 @@ def set_auto_commit(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         res = None
 
@@ -1587,13 +1474,13 @@ def set_auto_rollback(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         res = None
 
@@ -1643,13 +1530,13 @@ def auto_complete(trans_id):
         check_transaction_status(trans_id)
 
     if error_msg == gettext(
-            'Transaction ID not found in the session.'):
+        'Transaction ID not found in the session.'):
         return make_json_response(success=0, errormsg=error_msg,
                                   info='DATAGRID_TRANSACTION_REQUIRED',
                                   status=404)
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         # Create object of SQLAutoComplete class and pass connection object
         auto_complete_obj = SQLAutoComplete(
@@ -1680,165 +1567,6 @@ def script():
     )
 
 
-def is_begin_required(query):
-    word_len = 0
-    query = query.strip()
-    query_len = len(query)
-
-    # Check word length (since "beginx" is not "begin").
-    while (word_len < query_len) and query[word_len].isalpha():
-        word_len += 1
-
-    # Transaction control commands.  These should include every keyword that
-    #  gives rise to a TransactionStmt in the backend grammar, except for the
-    #  savepoint-related commands.
-    #
-    #  (We assume that START must be START TRANSACTION, since there is
-    #  presently no other "START foo" command.)
-
-    keyword = query[0:word_len]
-
-    if word_len == 5 and keyword.lower() == "abort":
-        return False
-    if word_len == 5 and keyword.lower() == "begin":
-        return False
-    if word_len == 5 and keyword.lower() == "start":
-        return False
-    if word_len == 6:
-        # SELECT is protected from dirty reads hence don't require transaction
-        if keyword.lower() in ["select", "commit"]:
-            return False
-    if word_len == 3 and keyword.lower() == "end":
-        return False
-    if word_len == 8 and keyword.lower() == "rollback":
-        return False
-    if word_len == 7 and keyword.lower() == "prepare":
-        # PREPARE TRANSACTION is a TC command, PREPARE foo is not
-        query = query[word_len:query_len]
-        query = query.strip()
-        query_len = len(query)
-        word_len = 0
-
-        while (word_len < query_len) and query[word_len].isalpha():
-            word_len += 1
-
-        keyword = query[0:word_len]
-        if word_len == 11 and keyword.lower() == "transaction":
-            return False
-        return True
-
-    # Commands not allowed within transactions. The statements checked for
-    # here should be exactly those that call PreventTransactionChain() in the
-    # backend.
-    if word_len == 6 and keyword.lower() == "vacuum":
-        return False
-
-    if word_len == 7 and keyword.lower() == "cluster":
-        # CLUSTER with any arguments is allowed in transactions
-        query = query[word_len:query_len]
-        query = query.strip()
-
-        if query[0].isalpha():
-            return True  # has additional words
-        return False  # it's CLUSTER without arguments
-
-    if word_len == 6 and keyword.lower() == "create":
-        query = query[word_len:query_len]
-        query = query.strip()
-        query_len = len(query)
-        word_len = 0
-
-        while (word_len < query_len) and query[word_len].isalpha():
-            word_len += 1
-
-        keyword = query[0:word_len]
-        if word_len == 8 and keyword.lower() == "database":
-            return False
-        if word_len == 10 and keyword.lower() == "tablespace":
-            return False
-
-        # CREATE [UNIQUE] INDEX CONCURRENTLY isn't allowed in xacts
-        if word_len == 7 and keyword.lower() == "cluster":
-            query = query[word_len:query_len]
-            query = query.strip()
-            query_len = len(query)
-            word_len = 0
-
-            while (word_len < query_len) and query[word_len].isalpha():
-                word_len += 1
-
-            keyword = query[0:word_len]
-
-        if word_len == 5 and keyword.lower() == "index":
-            query = query[word_len:query_len]
-            query = query.strip()
-            query_len = len(query)
-            word_len = 0
-
-            while (word_len < query_len) and query[word_len].isalpha():
-                word_len += 1
-
-            keyword = query[0:word_len]
-            if word_len == 12 and keyword.lower() == "concurrently":
-                return False
-        return True
-
-    if word_len == 5 and keyword.lower() == "alter":
-        query = query[word_len:query_len]
-        query = query.strip()
-        query_len = len(query)
-        word_len = 0
-
-        while (word_len < query_len) and query[word_len].isalpha():
-            word_len += 1
-
-        keyword = query[0:word_len]
-
-        # ALTER SYSTEM isn't allowed in xacts
-        if word_len == 6 and keyword.lower() == "system":
-            return False
-        return True
-
-    # Note: these tests will match DROP SYSTEM and REINDEX TABLESPACE, which
-    # aren't really valid commands so we don't care much. The other four
-    # possible matches are correct.
-    if word_len == 4 and keyword.lower() == "drop" \
-            or word_len == 7 and keyword.lower() == "reindex":
-        query = query[word_len:query_len]
-        query = query.strip()
-        query_len = len(query)
-        word_len = 0
-
-        while (word_len < query_len) and query[word_len].isalpha():
-            word_len += 1
-
-        keyword = query[0:word_len]
-        if word_len == 8 and keyword.lower() == "database":
-            return False
-        if word_len == 6 and keyword.lower() == "system":
-            return False
-        if word_len == 10 and keyword.lower() == "tablespace":
-            return False
-        return True
-
-    # DISCARD ALL isn't allowed in xacts, but other variants are allowed.
-    if word_len == 7 and keyword.lower() == "discard":
-        query = query[word_len:query_len]
-        query = query.strip()
-        query_len = len(query)
-        word_len = 0
-
-        while (word_len < query_len) and query[word_len].isalpha():
-            word_len += 1
-
-        keyword = query[0:word_len]
-        if word_len == 3 and keyword.lower() == "all":
-            return False
-        return True
-
-    return True
-
-
 @blueprint.route('/load_file/', methods=["PUT", "POST"], endpoint='load_file')
 @login_required
 def load_file():
@@ -1865,9 +1593,9 @@ def load_file():
         )
 
     status, err_msg, is_binary, \
-        is_startswith_bom, enc = Filemanager.check_file_for_bom_and_binary(
-            file_path
-        )
+    is_startswith_bom, enc = Filemanager.check_file_for_bom_and_binary(
+        file_path
+    )
 
     if not status:
         return internal_server_error(
@@ -1960,10 +1688,10 @@ def save_file():
 def start_query_download_tool(trans_id):
     sync_conn = None
     status, error_msg, conn, trans_obj, \
-        session_obj = check_transaction_status(trans_id)
+    session_obj = check_transaction_status(trans_id)
 
     if status and conn is not None \
-            and trans_obj is not None and session_obj is not None:
+        and trans_obj is not None and session_obj is not None:
 
         data = request.args if request.args else None
         try:
@@ -2063,7 +1791,7 @@ def query_tool_status(trans_id):
         TRANSACTION_STATUS_UNKNOWN  = 4
     """
     status, error_msg, conn, trans_obj, \
-        session_obj = check_transaction_status(trans_id)
+    session_obj = check_transaction_status(trans_id)
 
     if not status and error_msg and type(error_msg) == str:
         return internal_server_error(
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index 52109ecd..349c9a00 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -12,6 +12,8 @@ define('tools.querytool', [
   'sources/selection/xcell_selection_model',
   'sources/selection/set_staged_rows',
   'sources/sqleditor_utils',
+  'sources/sqleditor/execute_query',
+  'sources/sqleditor/is_new_transaction_required',
   'sources/history/index.js',
   'sources/../jsx/history/query_history',
   'react', 'react-dom',
@@ -28,7 +30,8 @@ define('tools.querytool', [
 ], function(
   babelPollyfill, gettext, url_for, $, _, S, alertify, pgAdmin, Backbone, codemirror,
   pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent,
-  XCellSelectionModel, setStagedRows, SqlEditorUtils, HistoryBundle, queryHistory, React, ReactDOM,
+  XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, transaction,
+  HistoryBundle, queryHistory, React, ReactDOM,
   keyboardShortcuts, queryToolActions, Datagrid) {
   /* Return back, this has been called more than once */
   if (pgAdmin.SqlEditor)
@@ -43,12 +46,6 @@ define('tools.querytool', [
 
   var is_query_running = false;
 
-  var is_new_transaction_required = function(xhr) {
-    return xhr.status == 404 && xhr.responseJSON &&
-                xhr.responseJSON.info &&
-                xhr.responseJSON.info == 'DATAGRID_TRANSACTION_REQUIRED';
-  };
-
   // Defining Backbone view for the sql grid.
   var SQLEditorView = Backbone.View.extend({
     initialize: function(opts) {
@@ -485,7 +482,7 @@ define('tools.querytool', [
                 if (pgAdmin.Browser.UserManagement.is_pga_login_required(e)) {
                   return pgAdmin.Browser.UserManagement.pga_login();
                 }
-                if(is_new_transaction_required(e)) {
+                if(transaction.is_new_transaction_required(e)) {
                   return self.init_transaction();
                 }
               },
@@ -2188,7 +2185,7 @@ define('tools.querytool', [
               pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_run_query', []);
               self.init_transaction();
             }
@@ -2256,74 +2253,8 @@ define('tools.querytool', [
        * 'Success' then call the render method to render the data.
        */
       _poll: function() {
-        var self = this;
-
-        setTimeout(
-          function() {
-            $.ajax({
-              url: url_for('sqleditor.poll', {
-                'trans_id': self.transId,
-              }),
-              method: 'GET',
-              success: function(res) {
-                if (res.data.status === 'Success') {
-                  self.trigger(
-                    'pgadmin-sqleditor:loading-icon:message',
-                    gettext('Loading data from the database server and rendering...')
-                  );
-
-                  self.call_render_after_poll(res.data);
-                } else if (res.data.status === 'Busy') {
-                  // If status is Busy then poll the result by recursive call to the poll function
-                  self._poll();
-                  is_query_running = true;
-                  if (res.data.result) {
-                    self.update_msg_history(res.data.status, res.data.result, false);
-                  }
-                } else if (res.data.status === 'NotConnected') {
-                  self.trigger('pgadmin-sqleditor:loading-icon:hide');
-                  // Enable/Disable query tool button only if is_query_tool is true.
-                  if (self.is_query_tool) {
-                    self.disable_tool_buttons(false);
-                    $('#btn-cancel-query').prop('disabled', true);
-                  }
-                  self.update_msg_history(false, res.data.result, true);
-                } else if (res.data.status === 'Cancel') {
-                  self.trigger('pgadmin-sqleditor:loading-icon:hide');
-                  self.update_msg_history(false, 'Execution Cancelled!', true);
-                }
-              },
-              error: function(e) {
-                // Enable/Disable query tool button only if is_query_tool is true.
-                self.resetQueryHistoryObject(self);
-                self.trigger('pgadmin-sqleditor:loading-icon:hide');
-                if (self.is_query_tool) {
-                  self.disable_tool_buttons(false);
-                  $('#btn-cancel-query').prop('disabled', true);
-                }
-
-                if (e.readyState == 0) {
-                  self.update_msg_history(false,
-                    gettext('Not connected to the server or the connection to the server has been closed.')
-                  );
-                  return;
-                }
-
-                if (pgAdmin.Browser.UserManagement.is_pga_login_required(e)) {
-                  return pgAdmin.Browser.UserManagement.pga_login();
-                }
-
-                var msg = e.responseText;
-                if (e.responseJSON != undefined &&
-                  e.responseJSON.errormsg != undefined)
-                  msg = e.responseJSON.errormsg;
-
-                self.update_msg_history(false, msg);
-                // Highlight the error in the sql panel
-                self._highlight_error(msg);
-              },
-            });
-          }, self.POLL_FALLBACK_TIME());
+        const executeQuery = new ExecuteQuery.ExecuteQuery(this, pgAdmin.Browser.UserManagement);
+        executeQuery.delayedPoll(this);
       },
 
       /* This function is used to create the backgrid columns,
@@ -2941,7 +2872,7 @@ define('tools.querytool', [
                 pgAdmin.Browser.UserManagement.pga_login();
               }
 
-              if(is_new_transaction_required(e)) {
+              if(transaction.is_new_transaction_required(e)) {
                 self.save_state('_save', [view, controller, save_as]);
                 self.init_transaction();
               }
@@ -3267,7 +3198,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_show_filter', []);
               return self.init_transaction();
             }
@@ -3356,7 +3287,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_include_filter', []);
               return self.init_transaction();
             }
@@ -3447,7 +3378,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_exclude_filter', []);
               return self.init_transaction();
             }
@@ -3517,7 +3448,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_remove_filter', []);
               return self.init_transaction();
             }
@@ -3592,7 +3523,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_apply_filter', []);
               return self.init_transaction();
             }
@@ -3747,7 +3678,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_set_limit', []);
               return self.init_transaction();
             }
@@ -3795,10 +3726,7 @@ define('tools.querytool', [
       // and execute the query.
       execute: function(explain_prefix) {
         var self = this,
-          sql = '',
-          url = url_for('sqleditor.query_tool_start', {
-            'trans_id': self.transId,
-          });
+          sql = '';
 
         self.has_more_rows = false;
         self.fetching_rows = false;
@@ -3812,109 +3740,8 @@ define('tools.querytool', [
         else
           sql = self.gridView.query_tool_obj.getValue();
 
-        // If it is an empty query, do nothing.
-        if (sql.length <= 0) return;
-
-        self.trigger(
-          'pgadmin-sqleditor:loading-icon:show',
-          gettext('Initializing query execution...')
-        );
-
-        $('#btn-flash').prop('disabled', true);
-
-        if (explain_prefix != undefined &&
-          !S.startsWith(sql.trim().toUpperCase(), 'EXPLAIN')) {
-          sql = explain_prefix + ' ' + sql;
-        }
-
-        self.query_start_time = new Date();
-        self.query = sql;
-        self.rows_affected = 0;
-        self._init_polling_flags();
-        self.disable_tool_buttons(true);
-        $('#btn-cancel-query').prop('disabled', false);
-
-        if (arguments.length > 0 &&
-          arguments[arguments.length - 1] == 'connect') {
-          url += '?connect=1';
-        }
-
-        $.ajax({
-          url: url,
-          method: 'POST',
-          contentType: 'application/json',
-          data: JSON.stringify(sql),
-          success: function(res) {
-            // Remove marker
-            if (self.gridView.marker) {
-              self.gridView.marker.clear();
-              delete self.gridView.marker;
-              self.gridView.marker = null;
-
-              // Remove already existing marker
-              self.gridView.query_tool_obj.removeLineClass(self.marked_line_no, 'wrap', 'CodeMirror-activeline-background');
-            }
-
-            if (res.data.status) {
-              self.trigger(
-                'pgadmin-sqleditor:loading-icon:message',
-                gettext('Waiting for the query execution to complete...')
-              );
-
-              self.can_edit = res.data.can_edit;
-              self.can_filter = res.data.can_filter;
-              self.info_notifier_timeout = res.data.info_notifier_timeout;
-
-              // If status is True then poll the result.
-              self._poll();
-            } else {
-              self.trigger('pgadmin-sqleditor:loading-icon:hide');
-              self.disable_tool_buttons(false);
-              $('#btn-cancel-query').prop('disabled', true);
-              self.update_msg_history(false, res.data.result);
-
-              // Highlight the error in the sql panel
-              self._highlight_error(res.data.result);
-            }
-          },
-          error: function(e) {
-            self.trigger('pgadmin-sqleditor:loading-icon:hide');
-            self.disable_tool_buttons(false);
-            $('#btn-cancel-query').prop('disabled', true);
-
-            if (e.readyState == 0) {
-              self.update_msg_history(false,
-                gettext('Not connected to the server or the connection to the server has been closed.')
-              );
-              return;
-            }
-
-            if (pgAdmin.Browser.UserManagement.is_pga_login_required(e)) {
-              self.save_state('execute', [explain_prefix]);
-              pgAdmin.Browser.UserManagement.pga_login();
-            }
-
-            if(is_new_transaction_required(e)) {
-              self.save_state('execute', [explain_prefix]);
-              self.init_transaction();
-            }
-            var msg = e.responseText;
-            if (e.responseJSON != undefined) {
-              if(e.responseJSON.errormsg != undefined) {
-                msg = e.responseJSON.errormsg;
-              }
-
-              if(e.status == 503 && e.responseJSON.info != undefined &&
-                  e.responseJSON.info == 'CONNECTION_LOST') {
-                setTimeout(function() {
-                  self.save_state('execute', [explain_prefix]);
-                  self.handle_connection_lost(false, e);
-                });
-              }
-            }
-            self.update_msg_history(false, msg);
-          },
-        });
+        const executeQuery = new ExecuteQuery.ExecuteQuery(this, pgAdmin.Browser.UserManagement);
+        executeQuery.execute(sql, explain_prefix);
       },
 
       /* This function is used to highlight the error line and
@@ -4078,7 +3905,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_auto_rollback', []);
               self.init_transaction();
             }
@@ -4139,7 +3966,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_auto_commit', []);
               return self.init_transaction();
             }
@@ -4201,7 +4028,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_explain_verbose', []);
               return self.init_transaction();
             }
@@ -4250,7 +4077,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_explain_costs', []);
               return self.init_transaction();
             }
@@ -4298,7 +4125,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_explain_buffers', []);
               return self.init_transaction();
             }
@@ -4345,7 +4172,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('_explain_timing', []);
               return self.init_transaction();
             }
@@ -4379,6 +4206,10 @@ define('tools.querytool', [
         return is_query_running;
       },
 
+      setIsQueryRunning: function(value) {
+        is_query_running = value;
+      },
+
       /*
        * This function get explain options and auto rollback/auto commit
        * values from preferences
@@ -4455,7 +4286,7 @@ define('tools.querytool', [
               return pgAdmin.Browser.UserManagement.pga_login();
             }
 
-            if(is_new_transaction_required(e)) {
+            if(transaction.is_new_transaction_required(e)) {
               self.save_state('get_preferences', []);
               return self.init_transaction();
             }
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/10_plus/explain_plan.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/10_plus/explain_plan.sql
new file mode 100644
index 00000000..fc4a8d2d
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/10_plus/explain_plan.sql
@@ -0,0 +1,23 @@
+EXPLAIN (
+{% if format %}
+  FORMAT {{ format.upper() }},
+{% endif %}
+{% if analyze is defined %}
+  ANALYZE {{ analyze }},
+{% endif %}
+{% if verbose is defined %}
+  VERBOSE {{ verbose }},
+{% endif %}
+{% if costs is defined %}
+  COSTS {{ costs }},
+{% endif %}
+{% if timing is defined %}
+  TIMING {{ timing }},
+{% endif %}
+{% if summary is defined %}
+  SUMMARY {{ summary }},
+{% endif %}
+{% if buffers is defined %}
+  BUFFERS {{ buffers }}
+{% endif %}
+) {{ sql }}
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/9.2_plus/explain_plan.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/9.2_plus/explain_plan.sql
new file mode 100644
index 00000000..315174a6
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/9.2_plus/explain_plan.sql
@@ -0,0 +1,20 @@
+EXPLAIN (
+{% if format %}
+  FORMAT {{ format.upper() }},
+{% endif %}
+{% if analyze is defined %}
+  ANALYZE {{ analyze }},
+{% endif %}
+{% if verbose is defined %}
+  VERBOSE {{ verbose }},
+{% endif %}
+{% if costs is defined %}
+  COSTS {{ costs }},
+{% endif %}
+{% if timing is defined %}
+  TIMING {{ timing }},
+{% endif %}
+{% if buffers is defined %}
+  BUFFERS {{ buffers }}
+{% endif %}
+) {{ sql }}
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/explain_plan.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/explain_plan.sql
new file mode 100644
index 00000000..72b20c95
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/explain_plan.sql
@@ -0,0 +1,17 @@
+EXPLAIN (
+{% if format %}
+  FORMAT {{ format.upper() }},
+{% endif %}
+{% if analyze is defined %}
+  ANALYZE {{ analyze }},
+{% endif %}
+{% if verbose is defined %}
+  VERBOSE {{ verbose }},
+{% endif %}
+{% if costs is defined %}
+  COSTS {{ costs }},
+{% endif %}
+{% if buffers is defined %}
+  BUFFERS {{ buffers }}
+{% endif %}
+) {{ sql }}
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/gpdb_5.0_plus/explain_plan.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/gpdb_5.0_plus/explain_plan.sql
new file mode 100644
index 00000000..aacabe45
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/gpdb_5.0_plus/explain_plan.sql
@@ -0,0 +1,5 @@
+EXPLAIN
+{% if analyze %}
+ ANALYZE
+{% endif %}
+ {{ sql }}
diff --git a/web/pgadmin/tools/sqleditor/tests/__init__.py b/web/pgadmin/tools/sqleditor/tests/__init__.py
new file mode 100644
index 00000000..590026ad
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/tests/__init__.py
@@ -0,0 +1,8 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
diff --git a/web/pgadmin/tools/sqleditor/tests/test_explain_plan_templates.py b/web/pgadmin/tools/sqleditor/tests/test_explain_plan_templates.py
new file mode 100644
index 00000000..ab018788
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/tests/test_explain_plan_templates.py
@@ -0,0 +1,152 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import os
+
+from flask import Flask, render_template
+from jinja2 import FileSystemLoader
+
+from pgadmin import VersionedTemplateLoader
+from pgadmin.utils.route import BaseTestGenerator
+
+
+class TestExplainPlanTemplates(BaseTestGenerator):
+    scenarios = [
+        (
+            'When rendering Postgres 9.0 template, '
+            'when passing all parameters,'
+            'it returns the explain plan with all parameters',
+            dict(
+                template_path=os.path.join('sqleditor', 'sql', 'default', 'explain_plan.sql'),
+                input_parameters=dict(
+                    sql='select * from places',
+                    format='xml',
+                    analyze=True,
+                    verbose=True,
+                    costs=False,
+                    buffers=True
+                ),
+                sql_statement='select * from places',
+                expected_return_value='EXPLAIN '
+                                      '(  FORMAT XML,  ANALYZE True,  VERBOSE True,  '
+                                      'COSTS False,  BUFFERS True) select * from places'
+            )
+        ),
+        (
+            'When rendering Postgres 9.0 template, '
+            'when not all parameters are present,'
+            'it returns the explain plan with the present parameters',
+            dict(
+                template_path=os.path.join('sqleditor', 'sql', 'default', 'explain_plan.sql'),
+                input_parameters=dict(
+                    sql='select * from places',
+                    format='json',
+                    buffers=True
+                ),
+                sql_statement='select * from places',
+                expected_return_value='EXPLAIN '
+                                      '(  FORMAT JSON,  BUFFERS True) select * from places'
+            )
+        ),
+        (
+            'When rendering Postgres 9.2 template, '
+            'when timing is present,'
+            'it returns the explain plan with timing',
+            dict(
+                template_path=os.path.join('sqleditor', 'sql', '9.2_plus', 'explain_plan.sql'),
+                input_parameters=dict(
+                    sql='select * from places',
+                    format='json',
+                    buffers=True,
+                    timing=False
+                ),
+                sql_statement='select * from places',
+                expected_return_value='EXPLAIN '
+                                      '(  FORMAT JSON,  TIMING False,  BUFFERS True) select * from places'
+            )
+        ),
+        (
+            'When rendering Postgres 10 template, '
+            'when summary is present,'
+            'it returns the explain plan with summary',
+            dict(
+                template_path=os.path.join('sqleditor', 'sql', '10_plus', 'explain_plan.sql'),
+                input_parameters=dict(
+                    sql='select * from places',
+                    format='yaml',
+                    buffers=True,
+                    timing=False,
+                    summary=True
+                ),
+                sql_statement='select * from places',
+                expected_return_value='EXPLAIN '
+                                      '(  FORMAT YAML,  TIMING False,  SUMMARY True,  BUFFERS True) select * from places'
+            )
+        ),
+        (
+            'When rendering GreenPlum 5.3 template, '
+            'when all parameters are present,'
+            'it returns the explain without parameters',
+            dict(
+                template_path=os.path.join('sqleditor', 'sql', 'gpdb_5.0_plus', 'explain_plan.sql'),
+                input_parameters=dict(
+                    sql='select * from places',
+                    format='json',
+                    buffers=True
+                ),
+                sql_statement='select * from places',
+                expected_return_value='EXPLAIN select * from places'
+            )
+        ),
+        (
+            'When rendering GreenPlum 5.3 template, '
+            'when analyze is true,'
+            'it returns the explain analyze',
+            dict(
+                template_path=os.path.join('sqleditor', 'sql', 'gpdb_5.0_plus', 'explain_plan.sql'),
+                input_parameters=dict(
+                    sql='select * from places',
+                    analyze=True
+                ),
+                sql_statement='select * from places',
+                expected_return_value='EXPLAIN ANALYZE select * from places'
+            )
+        ),
+        (
+            'When rendering GreenPlum 5.3 template, '
+            'when analyze is false,'
+            'it returns the only explain',
+            dict(
+                template_path=os.path.join('sqleditor', 'sql', 'gpdb_5.0_plus', 'explain_plan.sql'),
+                input_parameters=dict(
+                    sql='select * from places',
+                    analyze=False
+                ),
+                sql_statement='select * from places',
+                expected_return_value='EXPLAIN select * from places'
+            )
+        ),
+    ]
+
+    def setUp(self):
+        self.loader = VersionedTemplateLoader(FakeApp())
+
+    def runTest(self):
+        with FakeApp().app_context():
+            result = render_template(self.template_path, **self.input_parameters)
+            self.assertEqual(
+                str(result).replace("\n", ""), self.expected_return_value)
+
+
+class FakeApp(Flask):
+    def __init__(self):
+        super(FakeApp, self).__init__("")
+        self.jinja_loader = FileSystemLoader(
+            os.path.dirname(os.path.realpath(__file__)) + "/../templates"
+        )
diff --git a/web/pgadmin/tools/sqleditor/tests/test_extract_sql_from_network_parameters.py b/web/pgadmin/tools/sqleditor/tests/test_extract_sql_from_network_parameters.py
new file mode 100644
index 00000000..c4e002cc
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/tests/test_extract_sql_from_network_parameters.py
@@ -0,0 +1,59 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from werkzeug.datastructures import ImmutableMultiDict
+
+from pgadmin.tools.sqleditor import extract_sql_from_network_parameters
+from pgadmin.utils.route import BaseTestGenerator
+
+
+class ExtractSQLFromNetworkParametersTest(BaseTestGenerator):
+    """
+    This class validates the change password functionality
+    by defining change password scenarios; where dict of
+    parameters describes the scenario appended by test name.
+    """
+
+    scenarios = [
+        ('Single string in the payload', dict(
+            request_strigified_data='"some sql"',
+            request_arguments=ImmutableMultiDict(),
+            request_form_data=ImmutableMultiDict(),
+
+            expected_result=dict(sql='some sql', explain_plan=None)
+        )),
+        ('Payload that requests explain plan using json', dict(
+            request_strigified_data='{"sql": "some sql", "explain_plan": {"format": "json", "analyze": false, "verbose": false, "costs": false, "buffers": false, "timing": false}}',
+            request_arguments=ImmutableMultiDict(),
+            request_form_data=ImmutableMultiDict(),
+
+            expected_result=dict(
+                sql='some sql',
+                explain_plan=dict(
+                    format='json',
+                    analyze=False,
+                    verbose=False,
+                    buffers=False,
+                    costs=False,
+                    timing=False
+                )
+            )
+        ))
+    ]
+
+    def runTest(self):
+        """Check correct function is called to handle to run query."""
+
+        result = extract_sql_from_network_parameters(
+            self.request_strigified_data,
+            self.request_arguments,
+            self.request_form_data
+        )
+
+        self.assertEquals(result, self.expected_result)
diff --git a/web/pgadmin/tools/sqleditor/tests/test_start_query_tool.py b/web/pgadmin/tools/sqleditor/tests/test_start_query_tool.py
new file mode 100644
index 00000000..5e2752ae
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/tests/test_start_query_tool.py
@@ -0,0 +1,38 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+import sys
+
+from pgadmin.utils.route import BaseTestGenerator
+from pgadmin.tools.sqleditor import StartRunningQuery
+
+if sys.version_info < (3, 3):
+    from mock import patch, ANY
+else:
+    from unittest.mock import patch, ANY
+
+
+class StartQueryTool(BaseTestGenerator):
+    """
+    Ensures that the call to the backend to start running a query
+    calls the needed functions
+    """
+
+    @patch('pgadmin.tools.sqleditor.extract_sql_from_network_parameters')
+    def runTest(self, extract_sql_from_network_parameters_mock):
+        """Check correct function is called to handle to run query."""
+
+        extract_sql_from_network_parameters_mock.return_value = 'transformed sql'
+
+        with patch.object(StartRunningQuery, 'execute', return_value='some result') as StartRunningQuery_execute_mock:
+            response = self.tester.post('/sqleditor/query_tool/start/1234', data='"some sql statement"')
+
+            self.assertEquals(response.status, '200 OK')
+            self.assertEquals(response.data, 'some result')
+            StartRunningQuery_execute_mock.assert_called_with('transformed sql', 1234, ANY)
+            extract_sql_from_network_parameters_mock.assert_called_with('"some sql statement"', ANY, ANY)
diff --git a/web/pgadmin/tools/sqleditor/utils/__init__.py b/web/pgadmin/tools/sqleditor/utils/__init__.py
new file mode 100644
index 00000000..3626b658
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/__init__.py
@@ -0,0 +1,14 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from .constant_definition import *
+from .is_begin_required import is_begin_required
+from .update_session_grid_transaction import update_session_grid_transaction
+from .start_running_query import *
+from .apply_explain_plan_wrapper import *
diff --git a/web/pgadmin/tools/sqleditor/utils/apply_explain_plan_wrapper.py b/web/pgadmin/tools/sqleditor/utils/apply_explain_plan_wrapper.py
new file mode 100644
index 00000000..84e478de
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/apply_explain_plan_wrapper.py
@@ -0,0 +1,24 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Apply Explain plan wrapper to sql object."""
+from flask import render_template
+
+from pgadmin.utils.compile_template_name import compile_template_name
+
+
+def apply_explain_plan_wrapper_if_needed(manager, sql):
+    if 'explain_plan' in sql and sql['explain_plan']:
+        explain_plan = sql['explain_plan']
+        ver = manager.version
+        server_type = manager.server_type
+        template_path = compile_template_name('sqleditor/sql', 'explain_plan.sql', server_type, ver)
+        return render_template(template_path, sql=sql['sql'], **explain_plan)
+    else:
+        return sql['sql']
diff --git a/web/pgadmin/tools/sqleditor/utils/constant_definition.py b/web/pgadmin/tools/sqleditor/utils/constant_definition.py
new file mode 100644
index 00000000..44c7ecb7
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/constant_definition.py
@@ -0,0 +1,32 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Definition of constants for SQLEditor."""
+
+# Async Constants
+ASYNC_OK = 1
+ASYNC_READ_TIMEOUT = 2
+ASYNC_WRITE_TIMEOUT = 3
+ASYNC_NOT_CONNECTED = 4
+ASYNC_EXECUTION_ABORTED = 5
+
+# Transaction status constants
+TX_STATUS_IDLE = 0
+TX_STATUS__ACTIVE = 1
+TX_STATUS_INTRANS = 2
+TX_STATUS_INERROR = 3
+
+# Connection status codes mapping
+CONNECTION_STATUS_MESSAGE_MAPPING = dict({
+    0: 'The session is idle and there is no current transaction.',
+    1: 'A command is currently in progress.',
+    2: 'The session is idle in a valid transaction block.',
+    3: 'The session is idle in a failed transaction block.',
+    4: 'The connection with the server is bad.'
+})
diff --git a/web/pgadmin/tools/sqleditor/utils/is_begin_required.py b/web/pgadmin/tools/sqleditor/utils/is_begin_required.py
new file mode 100644
index 00000000..8db7b954
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/is_begin_required.py
@@ -0,0 +1,169 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Check if requires BEGIN in the current query."""
+
+
+def is_begin_required(query):
+    word_len = 0
+    query = query.strip()
+    query_len = len(query)
+
+    # Check word length (since "beginx" is not "begin").
+    while (word_len < query_len) and query[word_len].isalpha():
+        word_len += 1
+
+    # Transaction control commands.  These should include every keyword that
+    #  gives rise to a TransactionStmt in the backend grammar, except for the
+    #  savepoint-related commands.
+    #
+    #  (We assume that START must be START TRANSACTION, since there is
+    #  presently no other "START foo" command.)
+
+    keyword = query[0:word_len]
+
+    if word_len == 5 and keyword.lower() == "abort":
+        return False
+    if word_len == 5 and keyword.lower() == "begin":
+        return False
+    if word_len == 5 and keyword.lower() == "start":
+        return False
+    if word_len == 6:
+        # SELECT is protected from dirty reads hence don't require transaction
+        if keyword.lower() in ["select", "commit"]:
+            return False
+    if word_len == 3 and keyword.lower() == "end":
+        return False
+    if word_len == 8 and keyword.lower() == "rollback":
+        return False
+    if word_len == 7 and keyword.lower() == "prepare":
+        # PREPARE TRANSACTION is a TC command, PREPARE foo is not
+        query = query[word_len:query_len]
+        query = query.strip()
+        query_len = len(query)
+        word_len = 0
+
+        while (word_len < query_len) and query[word_len].isalpha():
+            word_len += 1
+
+        keyword = query[0:word_len]
+        if word_len == 11 and keyword.lower() == "transaction":
+            return False
+        return True
+
+    # Commands not allowed within transactions. The statements checked for
+    # here should be exactly those that call PreventTransactionChain() in the
+    # backend.
+    if word_len == 6 and keyword.lower() == "vacuum":
+        return False
+
+    if word_len == 7 and keyword.lower() == "cluster":
+        # CLUSTER with any arguments is allowed in transactions
+        query = query[word_len:query_len]
+        query = query.strip()
+
+        if query[0].isalpha():
+            return True  # has additional words
+        return False  # it's CLUSTER without arguments
+
+    if word_len == 6 and keyword.lower() == "create":
+        query = query[word_len:query_len]
+        query = query.strip()
+        query_len = len(query)
+        word_len = 0
+
+        while (word_len < query_len) and query[word_len].isalpha():
+            word_len += 1
+
+        keyword = query[0:word_len]
+        if word_len == 8 and keyword.lower() == "database":
+            return False
+        if word_len == 10 and keyword.lower() == "tablespace":
+            return False
+
+        # CREATE [UNIQUE] INDEX CONCURRENTLY isn't allowed in xacts
+        if word_len == 7 and keyword.lower() == "cluster":
+            query = query[word_len:query_len]
+            query = query.strip()
+            query_len = len(query)
+            word_len = 0
+
+            while (word_len < query_len) and query[word_len].isalpha():
+                word_len += 1
+
+            keyword = query[0:word_len]
+
+        if word_len == 5 and keyword.lower() == "index":
+            query = query[word_len:query_len]
+            query = query.strip()
+            query_len = len(query)
+            word_len = 0
+
+            while (word_len < query_len) and query[word_len].isalpha():
+                word_len += 1
+
+            keyword = query[0:word_len]
+            if word_len == 12 and keyword.lower() == "concurrently":
+                return False
+        return True
+
+    if word_len == 5 and keyword.lower() == "alter":
+        query = query[word_len:query_len]
+        query = query.strip()
+        query_len = len(query)
+        word_len = 0
+
+        while (word_len < query_len) and query[word_len].isalpha():
+            word_len += 1
+
+        keyword = query[0:word_len]
+
+        # ALTER SYSTEM isn't allowed in xacts
+        if word_len == 6 and keyword.lower() == "system":
+            return False
+        return True
+
+    # Note: these tests will match DROP SYSTEM and REINDEX TABLESPACE, which
+    # aren't really valid commands so we don't care much. The other four
+    # possible matches are correct.
+    if word_len == 4 and keyword.lower() == "drop" \
+            or word_len == 7 and keyword.lower() == "reindex":
+        query = query[word_len:query_len]
+        query = query.strip()
+        query_len = len(query)
+        word_len = 0
+
+        while (word_len < query_len) and query[word_len].isalpha():
+            word_len += 1
+
+        keyword = query[0:word_len]
+        if word_len == 8 and keyword.lower() == "database":
+            return False
+        if word_len == 6 and keyword.lower() == "system":
+            return False
+        if word_len == 10 and keyword.lower() == "tablespace":
+            return False
+        return True
+
+    # DISCARD ALL isn't allowed in xacts, but other variants are allowed.
+    if word_len == 7 and keyword.lower() == "discard":
+        query = query[word_len:query_len]
+        query = query.strip()
+        query_len = len(query)
+        word_len = 0
+
+        while (word_len < query_len) and query[word_len].isalpha():
+            word_len += 1
+
+        keyword = query[0:word_len]
+        if word_len == 3 and keyword.lower() == "all":
+            return False
+        return True
+
+    return True
diff --git a/web/pgadmin/tools/sqleditor/utils/start_running_query.py b/web/pgadmin/tools/sqleditor/utils/start_running_query.py
new file mode 100644
index 00000000..f5e1feae
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/start_running_query.py
@@ -0,0 +1,172 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Start executing the query in async mode."""
+
+import pickle
+import random
+
+from flask import Response
+from flask_babel import gettext
+
+from config import PG_DEFAULT_DRIVER
+from pgadmin.tools.sqleditor.utils.apply_explain_plan_wrapper import apply_explain_plan_wrapper_if_needed
+from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import update_session_grid_transaction
+from pgadmin.tools.sqleditor.utils.is_begin_required import is_begin_required
+from pgadmin.tools.sqleditor.utils.constant_definition import TX_STATUS_IDLE, TX_STATUS_INERROR
+
+from pgadmin.utils.ajax import make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from pgadmin.utils.exception import ConnectionLost
+
+
+class StartRunningQuery:
+
+    def __init__(self, blueprint_object, logger):
+        self.http_session = None
+        self.blueprint_object = blueprint_object
+        self.connection_id = str(random.randint(1, 9999999))
+        self.logger = logger
+
+    def execute(self, sql, trans_id, http_session):
+        session_obj = StartRunningQuery.retrieve_session_information(http_session, trans_id)
+        if type(session_obj) is Response:
+            return session_obj
+
+        transaction_object = pickle.loads(session_obj['command_obj'])
+        can_edit = False
+        can_filter = False
+        if transaction_object is not None and session_obj is not None:
+            # set fetched row count to 0 as we are executing query again.
+            transaction_object.update_fetched_row_cnt(0)
+            self.__retrieve_connection_id(transaction_object)
+
+            try:
+                manager = get_driver(
+                    PG_DEFAULT_DRIVER).connection_manager(transaction_object.sid)
+                conn = manager.connection(did=transaction_object.did, conn_id=self.connection_id,
+                                          auto_reconnect=False,
+                                          use_binary_placeholder=True,
+                                          array_to_string=True)
+            except ConnectionLost:
+                raise
+            except Exception as e:
+                self.logger.error(e)
+                return internal_server_error(errormsg=str(e))
+
+            # Connect to the Server if not connected.
+            if not conn.connected():
+                status, msg = conn.connect()
+                if not status:
+                    self.logger.error(msg)
+                    return internal_server_error(errormsg=str(msg))
+
+            effective_sql_statement = apply_explain_plan_wrapper_if_needed(manager, sql)
+
+            result, status = self.__execute_query(
+                conn,
+                session_obj,
+                effective_sql_statement,
+                trans_id,
+                transaction_object
+            )
+
+            can_edit = transaction_object.can_edit()
+            can_filter = transaction_object.can_filter()
+
+        else:
+            status = False
+            result = gettext(
+                'Either transaction object or session object not found.')
+        return make_json_response(
+            data={
+                'status': status, 'result': result,
+                'can_edit': can_edit, 'can_filter': can_filter,
+                'info_notifier_timeout': self.blueprint_object.info_notifier_timeout.get()
+            }
+        )
+
+    def __retrieve_connection_id(self, trans_obj):
+        conn_id = trans_obj.conn_id
+        # if conn_id is None then we will have to create a new connection
+        if conn_id is not None:
+            self.connection_id = conn_id
+
+    def __execute_query(self, conn, session_obj, sql, trans_id, trans_obj):
+        if conn.connected():
+            # on successful connection set the connection id to the
+            # transaction object
+            trans_obj.set_connection_id(self.connection_id)
+
+            StartRunningQuery.save_transaction_in_session(session_obj, trans_id, trans_obj)
+
+            # If auto commit is False and transaction status is Idle
+            # then call is_begin_not_required() function to check BEGIN
+            # is required or not.
+
+            if StartRunningQuery.is_begin_required_for_sql_query(trans_obj, conn, sql):
+                conn.execute_void("BEGIN;")
+
+            # Execute sql asynchronously with params is None
+            # and formatted_error is True.
+            try:
+                status, result = conn.execute_async(sql)
+            except ConnectionLost:
+                raise
+
+            # If the transaction aborted for some reason and
+            # Auto RollBack is True then issue a rollback to cleanup.
+            if StartRunningQuery.is_rollback_statement_required(trans_obj, conn):
+                conn.execute_void("ROLLBACK;")
+        else:
+            status = False
+            result = gettext(
+                'Not connected to server or connection with the server has '
+                'been closed.')
+        return result, status
+
+    @staticmethod
+    def is_begin_required_for_sql_query(trans_obj, conn, sql):
+        return not trans_obj.auto_commit \
+               and conn.transaction_status() == TX_STATUS_IDLE \
+               and is_begin_required(sql)
+
+    @staticmethod
+    def is_rollback_statement_required(trans_obj, conn):
+        return conn.transaction_status() == TX_STATUS_INERROR and trans_obj.auto_rollback
+
+    @staticmethod
+    def save_transaction_in_session(session, transaction_id, transaction):
+        # As we changed the transaction object we need to
+        # restore it and update the session variable.
+        session['command_obj'] = pickle.dumps(transaction, -1)
+        update_session_grid_transaction(transaction_id, session)
+
+    @staticmethod
+    def retrieve_session_information(http_session, transaction_id):
+        if 'gridData' not in http_session:
+            return make_json_response(
+                success=0,
+                errormsg=gettext('Transaction ID not found in the session.'),
+                info='DATAGRID_TRANSACTION_REQUIRED', status=404
+            )
+        grid_data = http_session['gridData']
+        # Return from the function if transaction id not found
+        if str(transaction_id) not in grid_data:
+            return make_json_response(
+                success=0,
+                errormsg=gettext('Transaction ID not found in the session.'),
+                info='DATAGRID_TRANSACTION_REQUIRED',
+                status=404
+            )
+        # Fetch the object for the specified transaction id.
+        # Use pickle.loads function to get the command object
+        return grid_data[str(transaction_id)]
+
+
diff --git a/web/pgadmin/tools/sqleditor/utils/tests/__init__.py b/web/pgadmin/tools/sqleditor/utils/tests/__init__.py
new file mode 100644
index 00000000..590026ad
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/tests/__init__.py
@@ -0,0 +1,8 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
diff --git a/web/pgadmin/tools/sqleditor/utils/tests/test_apply_explain_plan_wrapper.py b/web/pgadmin/tools/sqleditor/utils/tests/test_apply_explain_plan_wrapper.py
new file mode 100644
index 00000000..6cf320eb
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/tests/test_apply_explain_plan_wrapper.py
@@ -0,0 +1,121 @@
+#######################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Apply Explain plan wrapper to sql object."""
+import sys
+
+from pgadmin.tools.sqleditor.utils import apply_explain_plan_wrapper_if_needed
+from pgadmin.utils.route import BaseTestGenerator
+
+if sys.version_info < (3, 3):
+    from mock import patch, MagicMock
+else:
+    from unittest.mock import patch, MagicMock
+
+
+class StartRunningQueryTest(BaseTestGenerator):
+    """
+    Check that the apply_explain_plan_weapper_if_needed method works as intended
+    """
+    scenarios = [
+        ('When explain_plan is none, it should return unaltered SQL', dict(
+            function_input_parameters={
+                'manager': MagicMock(),
+                'sql': {
+                    'sql': 'some sql',
+                    'explain_plan': None
+                }
+            },
+
+            expect_render_template_mock_parameters=None,
+
+            expected_return_value='some sql'
+        )),
+        ('When explain_plan is not present, it should return unaltered SQL', dict(
+            function_input_parameters={
+                'manager': MagicMock(),
+                'sql': {
+                    'sql': 'some sql'
+                }
+            },
+
+            expect_render_template_mock_parameters=None,
+
+            expected_return_value='some sql'
+        )),
+        ('When explain_plan is present for a Postgres server version 10, it should return SQL with explain plan', dict(
+            function_input_parameters={
+                'manager': MagicMock(version=10, server_type='pg'),
+                'sql': {
+                    'sql': 'some sql',
+                    'explain_plan': {
+                        'format': 'json',
+                        'analyze': False,
+                        'verbose': True,
+                        'buffers': False,
+                        'timing': True
+                    }
+                }
+            },
+
+            expect_render_template_mock_parameters=dict(
+                template_name_or_list='sqleditor/sql/#10#/explain_plan.sql',
+                named_parameters=dict(
+                    format='json',
+                    analyze=False,
+                    verbose=True,
+                    buffers=False,
+                    timing=True
+                )),
+
+            expected_return_value='EXPLAIN (FORMAT JSON, ANALYZE FALSE, VERBOSE TRUE, COSTS FALSE, BUFFERS FALSE, '
+                                  'TIMING TRUE) some sql'
+        )),
+        ('When explain_plan is present for a GreenPlum server version 5, it should return SQL with explain plan', dict(
+            function_input_parameters={
+                'manager': MagicMock(version=80323, server_type='gpdb'),
+                'sql': {
+                    'sql': 'some sql',
+                    'explain_plan': {
+                        'format': 'json',
+                        'analyze': False,
+                        'verbose': True,
+                        'buffers': False,
+                        'timing': True
+                    }
+                }
+            },
+
+            expect_render_template_mock_parameters=dict(
+                template_name_or_list='sqleditor/sql/#gpdb#80323#/explain_plan.sql',
+                named_parameters=dict(
+                    format='json',
+                    analyze=False,
+                    verbose=True,
+                    buffers=False,
+                    timing=True
+                )),
+
+            expected_return_value='EXPLAIN some sql'
+        ))
+    ]
+
+    def runTest(self):
+        with patch('pgadmin.tools.sqleditor.utils.apply_explain_plan_wrapper.render_template') as render_template_mock:
+            render_template_mock.return_value = self.expected_return_value
+            result = apply_explain_plan_wrapper_if_needed(**self.function_input_parameters)
+            self.assertEquals(result, self.expected_return_value)
+            if self.expect_render_template_mock_parameters:
+                render_template_mock.assert_called_with(
+                    self.expect_render_template_mock_parameters['template_name_or_list'],
+                    sql=self.function_input_parameters['sql']['sql'],
+                    **self.expect_render_template_mock_parameters['named_parameters']
+                )
+            else:
+                render_template_mock.assert_not_called()
diff --git a/web/pgadmin/tools/sqleditor/utils/tests/test_start_running_query.py b/web/pgadmin/tools/sqleditor/utils/tests/test_start_running_query.py
new file mode 100644
index 00000000..d53c0248
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/tests/test_start_running_query.py
@@ -0,0 +1,445 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+import sys
+
+from flask import Response
+import simplejson as json
+
+from pgadmin.tools.sqleditor.utils.start_running_query import StartRunningQuery
+from pgadmin.utils.exception import ConnectionLost
+from pgadmin.utils.route import BaseTestGenerator
+
+if sys.version_info < (3, 3):
+    from mock import patch, MagicMock
+else:
+    from unittest.mock import patch, MagicMock
+
+get_driver_exception = Exception('get_driver exception')
+
+
+class StartRunningQueryTest(BaseTestGenerator):
+    """
+    Check that the start_running_query method works as intended
+    """
+
+    scenarios = [
+        ('When gridData is not present in the session, it returns an error', dict(
+            function_parameters=dict(
+                sql=dict(sql='some sql', explain_plan=None),
+                trans_id=123,
+                http_session=dict()
+            ),
+            pickle_load_return=None,
+            get_driver_exception=False,
+            manager_connection_exception=None,
+
+            is_connected_to_server=False,
+            connection_connect_return=None,
+            execute_async_return_value=None,
+            is_begin_required=False,
+            is_rollback_required=False,
+            apply_explain_plan_wrapper_if_needed_return_value='some sql',
+
+            expect_make_json_response_to_have_been_called_with=dict(
+                success=0,
+                errormsg='Transaction ID not found in the session.',
+                info='DATAGRID_TRANSACTION_REQUIRED',
+                status=404,
+            ),
+            expect_internal_server_error_to_have_been_called_with=None,
+            expected_logger_error=None,
+            expect_execute_void_called_with='some sql',
+        )),
+        ('When transactionId is not present in the gridData, it returns an error', dict(
+            function_parameters=dict(
+                sql=dict(sql='some sql', explain_plan=None),
+                trans_id=123,
+                http_session=dict(gridData=dict())
+            ),
+            pickle_load_return=None,
+            get_driver_exception=False,
+            manager_connection_exception=None,
+
+            is_connected_to_server=False,
+            connection_connect_return=None,
+            execute_async_return_value=None,
+            is_begin_required=False,
+            is_rollback_required=False,
+            apply_explain_plan_wrapper_if_needed_return_value='some sql',
+
+            expect_make_json_response_to_have_been_called_with=dict(
+                success=0,
+                errormsg='Transaction ID not found in the session.',
+                info='DATAGRID_TRANSACTION_REQUIRED',
+                status=404,
+            ),
+            expect_internal_server_error_to_have_been_called_with=None,
+            expected_logger_error=None,
+            expect_execute_void_called_with='some sql',
+        )),
+        ('When the command information for the transaction cannot be retrieved, it returns an error', dict(
+            function_parameters=dict(
+                sql=dict(sql='some sql', explain_plan=None),
+                trans_id=123,
+                http_session=dict(gridData={'123': dict(command_obj='')})
+            ),
+            pickle_load_return=None,
+            get_driver_exception=False,
+            manager_connection_exception=None,
+
+            is_connected_to_server=False,
+            connection_connect_return=None,
+            execute_async_return_value=None,
+            is_begin_required=False,
+            is_rollback_required=False,
+            apply_explain_plan_wrapper_if_needed_return_value='some sql',
+
+            expect_make_json_response_to_have_been_called_with=dict(
+                data=dict(
+                    status=False,
+                    result='Either transaction object or session object not found.',
+                    can_edit=False,
+                    can_filter=False,
+                    info_notifier_timeout=5
+                )
+            ),
+            expect_internal_server_error_to_have_been_called_with=None,
+            expected_logger_error=None,
+            expect_execute_void_called_with='some sql',
+        )),
+        ('When exception happens while retrieving the database driver, it returns an error', dict(
+            function_parameters=dict(
+                sql=dict(sql='some sql', explain_plan=None),
+                trans_id=123,
+                http_session=dict(gridData={'123': dict(command_obj='')})
+            ),
+            pickle_load_return=MagicMock(conn_id=1, update_fetched_row_cnt=MagicMock()),
+            get_driver_exception=True,
+            manager_connection_exception=None,
+
+            is_connected_to_server=False,
+            connection_connect_return=None,
+            execute_async_return_value=None,
+            is_begin_required=False,
+            is_rollback_required=False,
+            apply_explain_plan_wrapper_if_needed_return_value='some sql',
+
+            expect_make_json_response_to_have_been_called_with=None,
+            expect_internal_server_error_to_have_been_called_with=dict(
+                errormsg='get_driver exception'
+            ),
+            expected_logger_error=get_driver_exception,
+            expect_execute_void_called_with='some sql',
+        )),
+        ('When ConnectionLost happens while retrieving the database connection, it returns an error', dict(
+            function_parameters=dict(
+                sql=dict(sql='some sql', explain_plan=None),
+                trans_id=123,
+                http_session=dict(gridData={'123': dict(command_obj='')})
+            ),
+            pickle_load_return=MagicMock(conn_id=1, update_fetched_row_cnt=MagicMock()),
+            get_driver_exception=False,
+            manager_connection_exception=ConnectionLost('1', '2', '3'),
+
+            is_connected_to_server=False,
+            connection_connect_return=None,
+            execute_async_return_value=None,
+            is_begin_required=False,
+            is_rollback_required=False,
+            apply_explain_plan_wrapper_if_needed_return_value='some sql',
+
+            expect_make_json_response_to_have_been_called_with=None,
+            expect_internal_server_error_to_have_been_called_with=None,
+            expected_logger_error=None,
+            expect_execute_void_called_with='some sql',
+        )),
+        ('When is not connected to the server and fails to connect, it returns an error', dict(
+            function_parameters=dict(
+                sql=dict(sql='some sql', explain_plan=None),
+                trans_id=123,
+                http_session=dict(gridData={'123': dict(command_obj='')})
+            ),
+            pickle_load_return=MagicMock(conn_id=1, update_fetched_row_cnt=MagicMock()),
+            get_driver_exception=False,
+            manager_connection_exception=None,
+
+            is_connected_to_server=False,
+            connection_connect_return=[False, 'Unable to connect to server'],
+            execute_async_return_value=None,
+            is_begin_required=False,
+            is_rollback_required=False,
+            apply_explain_plan_wrapper_if_needed_return_value='some sql',
+
+            expect_make_json_response_to_have_been_called_with=None,
+            expect_internal_server_error_to_have_been_called_with=dict(
+                errormsg='Unable to connect to server'
+            ),
+            expected_logger_error='Unable to connect to server',
+            expect_execute_void_called_with='some sql',
+        )),
+        ('When server is connected and start query async request, it returns an success message', dict(
+            function_parameters=dict(
+                sql=dict(sql='some sql', explain_plan=None),
+                trans_id=123,
+                http_session=dict(gridData={'123': dict(command_obj='')})
+            ),
+            pickle_load_return=MagicMock(
+                conn_id=1,
+                update_fetched_row_cnt=MagicMock(),
+                set_connection_id=MagicMock(),
+                auto_commit=True,
+                auto_rollback=False,
+                can_edit=lambda: True,
+                can_filter=lambda: True
+            ),
+            get_driver_exception=False,
+            manager_connection_exception=None,
+
+            is_connected_to_server=True,
+            connection_connect_return=None,
+            execute_async_return_value=[True, 'async function result output'],
+            is_begin_required=False,
+            is_rollback_required=False,
+            apply_explain_plan_wrapper_if_needed_return_value='some sql',
+
+            expect_make_json_response_to_have_been_called_with=dict(
+                data=dict(
+                    status=True,
+                    result='async function result output',
+                    can_edit=True,
+                    can_filter=True,
+                    info_notifier_timeout=5
+                )
+            ),
+            expect_internal_server_error_to_have_been_called_with=None,
+            expected_logger_error=None,
+            expect_execute_void_called_with='some sql',
+        )),
+        ('When server is connected and start query async request and begin is required, '
+         'it returns an success message', dict(
+            function_parameters=dict(
+                sql=dict(sql='some sql', explain_plan=None),
+                trans_id=123,
+                http_session=dict(gridData={'123': dict(command_obj='')})
+            ),
+            pickle_load_return=MagicMock(
+                conn_id=1,
+                update_fetched_row_cnt=MagicMock(),
+                set_connection_id=MagicMock(),
+                auto_commit=True,
+                auto_rollback=False,
+                can_edit=lambda: True,
+                can_filter=lambda: True
+            ),
+            get_driver_exception=False,
+            manager_connection_exception=None,
+
+            is_connected_to_server=True,
+            connection_connect_return=None,
+            execute_async_return_value=[True, 'async function result output'],
+            is_begin_required=True,
+            is_rollback_required=False,
+            apply_explain_plan_wrapper_if_needed_return_value='some sql',
+
+            expect_make_json_response_to_have_been_called_with=dict(
+                data=dict(
+                    status=True,
+                    result='async function result output',
+                    can_edit=True,
+                    can_filter=True,
+                    info_notifier_timeout=5
+                )
+            ),
+            expect_internal_server_error_to_have_been_called_with=None,
+            expected_logger_error=None,
+            expect_execute_void_called_with='some sql',
+        )),
+        ('When server is connected and start query async request and rollback is required, '
+         'it returns an success message', dict(
+            function_parameters=dict(
+                sql=dict(sql='some sql', explain_plan=None),
+                trans_id=123,
+                http_session=dict(gridData={'123': dict(command_obj='')})
+            ),
+            pickle_load_return=MagicMock(
+                conn_id=1,
+                update_fetched_row_cnt=MagicMock(),
+                set_connection_id=MagicMock(),
+                auto_commit=True,
+                auto_rollback=False,
+                can_edit=lambda: True,
+                can_filter=lambda: True
+            ),
+            get_driver_exception=False,
+            manager_connection_exception=None,
+
+            is_connected_to_server=True,
+            connection_connect_return=None,
+            execute_async_return_value=[True, 'async function result output'],
+            is_begin_required=False,
+            is_rollback_required=True,
+            apply_explain_plan_wrapper_if_needed_return_value='some sql',
+
+            expect_make_json_response_to_have_been_called_with=dict(
+                data=dict(
+                    status=True,
+                    result='async function result output',
+                    can_edit=True,
+                    can_filter=True,
+                    info_notifier_timeout=5
+                )
+            ),
+            expect_internal_server_error_to_have_been_called_with=None,
+            expected_logger_error=None,
+            expect_execute_void_called_with='some sql',
+        )),
+        ('When server is connected and start query async request with explain plan wrapper, '
+         'it returns an success message', dict(
+            function_parameters=dict(
+                sql=dict(sql='some sql', explain_plan=None),
+                trans_id=123,
+                http_session=dict(gridData={'123': dict(command_obj='')})
+            ),
+            pickle_load_return=MagicMock(
+                conn_id=1,
+                update_fetched_row_cnt=MagicMock(),
+                set_connection_id=MagicMock(),
+                auto_commit=True,
+                auto_rollback=False,
+                can_edit=lambda: True,
+                can_filter=lambda: True
+            ),
+            get_driver_exception=False,
+            manager_connection_exception=None,
+
+            is_connected_to_server=True,
+            connection_connect_return=None,
+            execute_async_return_value=[True, 'async function result output'],
+            is_begin_required=False,
+            is_rollback_required=True,
+            apply_explain_plan_wrapper_if_needed_return_value='EXPLAIN PLAN some sql',
+
+            expect_make_json_response_to_have_been_called_with=dict(
+                data=dict(
+                    status=True,
+                    result='async function result output',
+                    can_edit=True,
+                    can_filter=True,
+                    info_notifier_timeout=5
+                )
+            ),
+            expect_internal_server_error_to_have_been_called_with=None,
+            expected_logger_error=None,
+            expect_execute_void_called_with='EXPLAIN PLAN some sql',
+        )),
+    ]
+
+    @patch('pgadmin.tools.sqleditor.utils.start_running_query.apply_explain_plan_wrapper_if_needed')
+    @patch('pgadmin.tools.sqleditor.utils.start_running_query.make_json_response')
+    @patch('pgadmin.tools.sqleditor.utils.start_running_query.pickle')
+    @patch('pgadmin.tools.sqleditor.utils.start_running_query.get_driver')
+    @patch('pgadmin.tools.sqleditor.utils.start_running_query.internal_server_error')
+    @patch('pgadmin.tools.sqleditor.utils.start_running_query.update_session_grid_transaction')
+    def runTest(self, update_session_grid_transaction_mock,
+                internal_server_error_mock, get_driver_mock, pickle_mock,
+                make_json_response_mock, apply_explain_plan_wrapper_if_needed_mock):
+        """Check correct function is called to handle to run query."""
+        self.connection = None
+
+        self.loggerMock = MagicMock(error=MagicMock())
+        expected_response = Response(response=json.dumps({'errormsg': 'some value'}))
+        make_json_response_mock.return_value = expected_response
+        if self.expect_internal_server_error_to_have_been_called_with is not None:
+            internal_server_error_mock.return_value = expected_response
+        pickle_mock.loads.return_value = self.pickle_load_return
+        blueprint_mock = MagicMock(info_notifier_timeout=MagicMock(get=lambda: 5))
+
+        if self.is_begin_required:
+            StartRunningQuery.is_begin_required_for_sql_query = MagicMock(return_value=True)
+        else:
+            StartRunningQuery.is_begin_required_for_sql_query = MagicMock(return_value=False)
+        if self.is_rollback_required:
+            StartRunningQuery.is_rollback_statement_required = MagicMock(return_value=True)
+        else:
+            StartRunningQuery.is_rollback_statement_required = MagicMock(return_value=False)
+
+        apply_explain_plan_wrapper_if_needed_mock.return_value = self.apply_explain_plan_wrapper_if_needed_return_value
+
+        manager = self.__create_manager()
+        if self.get_driver_exception:
+            get_driver_mock.side_effect = get_driver_exception
+        else:
+            get_driver_mock.return_value = MagicMock(connection_manager=lambda session_id: manager)
+
+        try:
+            result = StartRunningQuery(
+                blueprint_mock,
+                self.loggerMock
+            ).execute(
+                **self.function_parameters
+            )
+            if self.manager_connection_exception is not None:
+                self.fail('Exception: "' + str(self.manager_connection_exception) + '" excepted but not raised')
+
+            self.assertEquals(result, expected_response)
+
+        except AssertionError:
+            raise
+        except Exception as exception:
+            self.assertEquals(self.manager_connection_exception, exception)
+
+        self.__mock_assertions(internal_server_error_mock, make_json_response_mock)
+        if self.is_connected_to_server:
+            apply_explain_plan_wrapper_if_needed_mock.assert_called_with(manager, self.function_parameters['sql'])
+
+    def __create_manager(self):
+        self.connection = MagicMock(
+            connected=lambda: self.is_connected_to_server,
+            connect=MagicMock(),
+            execute_async=MagicMock(),
+            execute_void=MagicMock(),
+        )
+        self.connection.connect.return_value = self.connection_connect_return
+        self.connection.execute_async.return_value = self.execute_async_return_value
+        if self.manager_connection_exception is None:
+            manager = MagicMock(
+                connection=lambda did, conn_id, use_binary_placeholder, array_to_string, auto_reconnect: self.connection
+            )
+        else:
+            manager = MagicMock()
+            manager.connection.side_effect = self.manager_connection_exception
+        return manager
+
+    def __mock_assertions(self, internal_server_error_mock, make_json_response_mock):
+        if self.expect_make_json_response_to_have_been_called_with is not None:
+            make_json_response_mock.assert_called_with(**self.expect_make_json_response_to_have_been_called_with)
+        else:
+            make_json_response_mock.assert_not_called()
+        if self.expect_internal_server_error_to_have_been_called_with is not None:
+            internal_server_error_mock.assert_called_with(**self.expect_internal_server_error_to_have_been_called_with)
+        else:
+            internal_server_error_mock.assert_not_called()
+        if self.execute_async_return_value is not None:
+            self.connection.execute_async.assert_called_with(self.expect_execute_void_called_with)
+        else:
+            self.connection.execute_async.assert_not_called()
+
+        if self.expected_logger_error is not None:
+            self.loggerMock.error.assert_called_with(self.expected_logger_error)
+        else:
+            self.loggerMock.error.assert_not_called()
+
+        if self.is_begin_required:
+            self.connection.execute_void.assert_called_with('BEGIN;')
+        elif not self.is_rollback_required:
+            self.connection.execute_void.assert_not_called()
+        if self.is_rollback_required:
+            self.connection.execute_void.assert_called_with('ROLLBACK;')
+        elif not self.is_begin_required:
+            self.connection.execute_void.assert_not_called()
diff --git a/web/pgadmin/tools/sqleditor/utils/update_session_grid_transaction.py b/web/pgadmin/tools/sqleditor/utils/update_session_grid_transaction.py
new file mode 100644
index 00000000..aef78f23
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/update_session_grid_transaction.py
@@ -0,0 +1,18 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Update session with gridData."""
+from flask import session
+
+
+def update_session_grid_transaction(trans_id, data):
+    if 'gridData' in session:
+        grid_data = session['gridData']
+        grid_data[str(trans_id)] = data
+        session['gridData'] = grid_data
diff --git a/web/pgadmin/utils/compile_template_name.py b/web/pgadmin/utils/compile_template_name.py
new file mode 100644
index 00000000..6eb2431f
--- /dev/null
+++ b/web/pgadmin/utils/compile_template_name.py
@@ -0,0 +1,17 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+import os
+
+
+def compile_template_name(template_prefix, template_file_name, server_type, version):
+    if server_type == 'gpdb':
+        version_path = '#{0}#{1}#'.format(server_type, version)
+    else:
+        version_path = '#{0}#'.format(version)
+    return os.path.join(template_prefix, version_path, template_file_name)
diff --git a/web/pgadmin/utils/tests/test_compile_template_name.py b/web/pgadmin/utils/tests/test_compile_template_name.py
new file mode 100644
index 00000000..97f1b05c
--- /dev/null
+++ b/web/pgadmin/utils/tests/test_compile_template_name.py
@@ -0,0 +1,34 @@
+#######################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+from pgadmin.utils.compile_template_name import compile_template_name
+from pgadmin.utils.route import BaseTestGenerator
+
+
+class StartRunningQueryTest(BaseTestGenerator):
+    """
+    Check that the apply_explain_plan_weapper_if_needed method works as intended
+    """
+    scenarios = [
+        ('When server is Postgres and version is 10, it returns the path to the postgres template', dict(
+            server_type='pg',
+            version=100000,
+
+            expected_return_value='some/prefix/#100000#/some_file.sql'
+        )),
+        ('When server is GreenPlum and version is 5, it returns the path to the GreenPlum template', dict(
+            server_type='gpdb',
+            version=80323,
+
+            expected_return_value='some/prefix/#gpdb#80323#/some_file.sql'
+        )),
+    ]
+
+    def runTest(self):
+        result = compile_template_name('some/prefix', 'some_file.sql', self.server_type, self.version)
+        self.assertEquals(result, self.expected_return_value)
diff --git a/web/pgadmin/utils/versioned_template_loader.py b/web/pgadmin/utils/versioned_template_loader.py
index 19693012..8e8259fe 100644
--- a/web/pgadmin/utils/versioned_template_loader.py
+++ b/web/pgadmin/utils/versioned_template_loader.py
@@ -6,7 +6,6 @@
 # This software is released under the PostgreSQL Licence
 #
 ##########################################################################
-
 from flask.templating import DispatchingJinjaLoader
 from jinja2 import TemplateNotFound
 
@@ -54,6 +53,7 @@ class VersionedTemplateLoader(DispatchingJinjaLoader):
 
             template_path = path_start + '/' + \
                 server_version['name'] + '/' + file_name
+
             try:
                 return super(VersionedTemplateLoader, self).get_source(
                     environment, template_path
diff --git a/web/regression/javascript/fake_endpoints.js b/web/regression/javascript/fake_endpoints.js
index 0250a0df..63ab05dc 100644
--- a/web/regression/javascript/fake_endpoints.js
+++ b/web/regression/javascript/fake_endpoints.js
@@ -8,5 +8,9 @@
 //////////////////////////////////////////////////////////////////////////
 
 define(function () {
-  return {'static': '/base/pgadmin/static/<path:filename>'};
+  return {
+    'static': '/base/pgadmin/static/<path:filename>',
+    'sqleditor.poll': '/sqleditor/query_tool/poll/<path:trans_id>',
+    'sqleditor.query_tool_start': '/sqleditor/query_tool/start/<path:trans_id>'
+  };
 });
diff --git a/web/regression/javascript/sqleditor/execute_query_spec.js b/web/regression/javascript/sqleditor/execute_query_spec.js
new file mode 100644
index 00000000..98faed7d
--- /dev/null
+++ b/web/regression/javascript/sqleditor/execute_query_spec.js
@@ -0,0 +1,1702 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+
+import * as subject from 'sources/sqleditor/execute_query';
+import * as transaction from 'sources/sqleditor/is_new_transaction_required';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
+
+const context = describe;
+
+describe('ExecuteQuery', () => {
+  let sqlEditorMock;
+  let networkMock;
+  let executeQuery;
+  let userManagementMock;
+  let isNewTransactionRequiredMock;
+
+  const startTime = new Date(2018, 1, 29, 12, 15, 52);
+  beforeEach(() => {
+    networkMock = new MockAdapter(axios);
+    jasmine.addMatchers({jQuerytoHaveBeenCalledWith: jQuerytoHaveBeenCalledWith});
+    userManagementMock = jasmine.createSpyObj('UserManagement', [
+      'is_pga_login_required',
+      'pga_login',
+    ]);
+
+    sqlEditorMock = jasmine.createSpyObj('SqlEditor', [
+      'call_render_after_poll',
+      'disable_tool_buttons',
+      'resetQueryHistoryObject',
+      'setIsQueryRunning',
+      'trigger',
+      'update_msg_history',
+      '_highlight_error',
+      '_init_polling_flags',
+      'save_state',
+      'init_transaction',
+    ]);
+    sqlEditorMock.transId = 123;
+    sqlEditorMock.rows_affected = 1000;
+    executeQuery = new subject.ExecuteQuery(sqlEditorMock, userManagementMock);
+    isNewTransactionRequiredMock = spyOn(transaction, 'is_new_transaction_required');
+  });
+
+  describe('#poll', () => {
+    let cancelButtonSpy;
+    let response;
+
+    beforeEach(() => {
+      sqlEditorMock.POLL_FALLBACK_TIME = () => {
+        return 0;
+      };
+
+      cancelButtonSpy = spyOn($.fn, 'prop');
+      executeQuery.delayedPoll = jasmine.createSpy('ExecuteQuery.delayedPoll');
+    });
+
+    afterEach(() => {
+    });
+
+    context('when SQLEditor is the query tool', () => {
+      beforeEach(() => {
+        sqlEditorMock.is_query_tool = true;
+      });
+
+      describe('when server answer with success', () => {
+        describe('when query was successful', () => {
+          beforeEach(() => {
+            response = {
+              data: {status: 'Success'},
+            };
+            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
+
+            executeQuery.poll();
+          });
+
+          it('should update the loading icon message', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.trigger)
+                .toHaveBeenCalledWith(
+                  'pgadmin-sqleditor:loading-icon:message',
+                  'Loading data from the database server and rendering...'
+                );
+              done();
+            }, 0);
+          });
+
+          it('should render the results', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.call_render_after_poll)
+                .toHaveBeenCalledWith({status: 'Success'});
+              done();
+            }, 0);
+          });
+        });
+
+        describe('when query is still running', () => {
+          context('when no additional information is returned', () => {
+            beforeEach(() => {
+              response = {
+                data: {status: 'Busy'},
+              };
+
+              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
+              executeQuery.poll();
+            });
+
+            it('should set the flag to inform SQLEditor a query is running', (done) => {
+              setTimeout(() => {
+                expect(sqlEditorMock.setIsQueryRunning)
+                  .toHaveBeenCalledWith(true);
+                done();
+              }, 0);
+            });
+
+            it('should does not update history', (done) => {
+              setTimeout(() => {
+                expect(sqlEditorMock.update_msg_history).not
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+            });
+
+            it('should recursively call polling', (done) => {
+              setTimeout(() => {
+                expect(executeQuery.delayedPoll)
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+            });
+          });
+
+          context('when additional information is returned', () => {
+            beforeEach(() => {
+              response = {
+                data: {
+                  status: 'Busy',
+                  result: 'Some important result',
+                },
+              };
+
+              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
+              executeQuery.poll();
+            });
+
+            it('should set the flag to inform SQLEditor a query is running', (done) => {
+              setTimeout(() => {
+                expect(sqlEditorMock.setIsQueryRunning)
+                  .toHaveBeenCalledWith(true);
+                done();
+              }, 0);
+            });
+
+            it('should update history message', (done) => {
+              setTimeout(() => {
+                expect(sqlEditorMock.update_msg_history)
+                  .toHaveBeenCalledWith('Busy', 'Some important result', false);
+                done();
+              }, 0);
+            });
+
+            it('should recursively call polling', (done) => {
+              setTimeout(() => {
+                expect(executeQuery.delayedPoll)
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+            });
+          });
+        });
+
+        describe('when the application lost connection with the database', () => {
+          beforeEach(() => {
+            response = {
+              data: {
+                status: 'NotConnected',
+                result: 'Some interesting result',
+              },
+            };
+            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
+
+            executeQuery.poll();
+          });
+
+          it('should hide the loading icon', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.trigger)
+                .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+              done();
+            }, 0);
+          });
+
+          it('should add new entry to history and update the Messages tab and clear the result grid', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.update_msg_history)
+                .toHaveBeenCalledWith(
+                  false,
+                  'Some interesting result',
+                  true
+                );
+              done();
+            }, 0);
+          });
+
+          it('should enable the tool buttons', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.disable_tool_buttons)
+                  .toHaveBeenCalledWith(false);
+                done();
+              }, 0);
+          });
+
+          it('should disable the cancel button', (done) => {
+            setTimeout(
+              () => {
+                expect(cancelButtonSpy)
+                  .toHaveBeenCalledWith('disabled', true);
+                done();
+              }, 0);
+          });
+        });
+
+        describe('when query was cancelled', () => {
+          beforeEach(() => {
+            response = {
+              data: {status: 'Cancel'},
+            };
+            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
+
+            executeQuery.poll();
+          });
+
+          it('should hide the loading icon', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.trigger)
+                .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+              done();
+            }, 0);
+          });
+
+          it('should add new entry to history, add cancellation message to Messages tab and clear the result grid', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.update_msg_history)
+                .toHaveBeenCalledWith(
+                  false,
+                  'Execution Cancelled!',
+                  true
+                );
+              done();
+            }, 0);
+          });
+        });
+      });
+
+      describe('when an error occur', () => {
+        let errorMessageJson = {
+          errormsg: 'Some error in JSON',
+        };
+        let errorMessageText = 'Some plain text error';
+
+        describe('when the connection to the server was lost', () => {
+          describe('when JSON response is available', () => {
+            describe('when login is not required', () => {
+              beforeEach(() => {
+                userManagementMock.is_pga_login_required.and.returnValue(false);
+                response = {responseJSON: errorMessageJson};
+                networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);
+
+                executeQuery.poll();
+              });
+
+              it('should hide the loading icon', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.trigger)
+                      .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                    done();
+                  }, 0);
+              });
+
+              it('should reset last query information', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.resetQueryHistoryObject)
+                      .toHaveBeenCalledWith(sqlEditorMock);
+                    done();
+                  }, 0);
+              });
+
+              it('should highlight the error in the SQL panel', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock._highlight_error)
+                      .toHaveBeenCalledWith('Some error in JSON');
+                    done();
+                  }, 0);
+              });
+
+              it('should add new entry to history and update the Messages tab', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.update_msg_history)
+                      .toHaveBeenCalledWith(false, 'Some error in JSON');
+                    done();
+                  }, 0);
+              });
+
+              it('should enable the tool buttons', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.disable_tool_buttons)
+                      .toHaveBeenCalledWith(false);
+                    done();
+                  }, 0);
+              });
+
+              it('should disable the cancel button', (done) => {
+                setTimeout(
+                  () => {
+                    expect(cancelButtonSpy)
+                      .toHaveBeenCalledWith('disabled', true);
+                    done();
+                  }, 0);
+              });
+
+              it('should not login is displayed', (done) => {
+                setTimeout(
+                  () => {
+                    expect(userManagementMock.pga_login).not
+                      .toHaveBeenCalled();
+                    done();
+                  }, 0);
+              });
+            });
+
+            describe('when login is required', () => {
+              beforeEach(() => {
+                userManagementMock.is_pga_login_required.and.returnValue(true);
+                response = {responseJSON: errorMessageJson};
+                networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);
+
+                executeQuery.poll();
+              });
+
+              it('should hide the loading icon', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.trigger)
+                      .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                    done();
+                  }, 0);
+              });
+
+              it('should reset last query information', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.resetQueryHistoryObject)
+                      .toHaveBeenCalledWith(sqlEditorMock);
+                    done();
+                  }, 0);
+              });
+
+              it('should not highlight the error in the SQL panel', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock._highlight_error).not
+                      .toHaveBeenCalled();
+                    done();
+                  }, 0);
+              });
+
+              it('should not add new entry to history and update the Messages tab', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.update_msg_history).not
+                      .toHaveBeenCalled();
+                    done();
+                  }, 0);
+              });
+
+              it('should enable the tool buttons', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.disable_tool_buttons)
+                      .toHaveBeenCalledWith(false);
+                    done();
+                  }, 0);
+              });
+
+              it('should disable the cancel button', (done) => {
+                setTimeout(
+                  () => {
+                    expect(cancelButtonSpy)
+                      .toHaveBeenCalledWith('disabled', true);
+                    done();
+                  }, 0);
+              });
+
+              it('should login is displayed', (done) => {
+                setTimeout(
+                  () => {
+                    expect(userManagementMock.pga_login)
+                      .toHaveBeenCalled();
+                    done();
+                  }, 0);
+              });
+            });
+          });
+
+          describe('when no JSON response is available', () => {
+            describe('when login is not required', () => {
+              beforeEach(() => {
+                userManagementMock.is_pga_login_required.and.returnValue(false);
+                response = {
+                  errormsg: errorMessageText,
+                };
+                networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);
+
+                executeQuery.poll();
+              });
+
+              it('should hide the loading icon', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.trigger)
+                      .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                    done();
+                  }, 0);
+              });
+
+              it('should reset last query information', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.resetQueryHistoryObject)
+                      .toHaveBeenCalledWith(sqlEditorMock);
+                    done();
+                  }, 0);
+              });
+
+              it('should highlight the error in the SQL panel', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock._highlight_error)
+                      .toHaveBeenCalledWith('Some plain text error');
+                    done();
+                  }, 0);
+              });
+
+              it('should add new entry to history and update the Messages tab', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.update_msg_history)
+                      .toHaveBeenCalledWith(false, 'Some plain text error');
+                    done();
+                  }, 0);
+              });
+
+              it('should enable the tool buttons', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.disable_tool_buttons)
+                      .toHaveBeenCalledWith(false);
+                    done();
+                  }, 0);
+              });
+
+              it('should disable the cancel button', (done) => {
+                setTimeout(
+                  () => {
+                    expect(cancelButtonSpy)
+                      .toHaveBeenCalledWith('disabled', true);
+                    done();
+                  }, 0);
+              });
+
+              it('should login is not displayed', (done) => {
+                setTimeout(
+                  () => {
+                    expect(userManagementMock.pga_login).not
+                      .toHaveBeenCalled();
+                    done();
+                  }, 0);
+              });
+            });
+
+            describe('when login is required', () => {
+              beforeEach(() => {
+                userManagementMock.is_pga_login_required.and.returnValue(true);
+                response = {
+                  errormsg: errorMessageText,
+                };
+                networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);
+
+                executeQuery.poll();
+              });
+
+              it('should hide the loading icon', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.trigger)
+                      .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                    done();
+                  }, 0);
+              });
+
+              it('should reset last query information', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.resetQueryHistoryObject)
+                      .toHaveBeenCalledWith(sqlEditorMock);
+                    done();
+                  }, 0);
+              });
+
+              it('should not highlight the error in the SQL panel', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock._highlight_error).not
+                      .toHaveBeenCalled();
+                    done();
+                  }, 0);
+              });
+
+              it('should not add new entry to history and update the Messages tab', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.update_msg_history).not
+                      .toHaveBeenCalled();
+                    done();
+                  }, 0);
+              });
+
+              it('should enable the tool buttons', (done) => {
+                setTimeout(
+                  () => {
+                    expect(sqlEditorMock.disable_tool_buttons)
+                      .toHaveBeenCalledWith(false);
+                    done();
+                  }, 0);
+              });
+
+              it('should disable the cancel button', (done) => {
+                setTimeout(
+                  () => {
+                    expect(cancelButtonSpy)
+                      .toHaveBeenCalledWith('disabled', true);
+                    done();
+                  }, 0);
+              });
+
+              it('should login is displayed', (done) => {
+                setTimeout(
+                  () => {
+                    expect(userManagementMock.pga_login)
+                      .toHaveBeenCalled();
+                    done();
+                  }, 0);
+              });
+            });
+          });
+
+          describe('when cannot reach the Python Server', () => {
+            beforeEach(() => {
+              response = {readyState: 0};
+              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);
+
+              executeQuery.poll();
+            });
+
+            it('should hide the loading icon', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.trigger)
+                    .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                  done();
+                }, 0);
+            });
+
+            it('should reset last query information', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.resetQueryHistoryObject)
+                    .toHaveBeenCalledWith(sqlEditorMock);
+                  done();
+                }, 0);
+            });
+
+            it('should not highlight the error in the SQL panel', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock._highlight_error).not
+                    .toHaveBeenCalled();
+                  done();
+                }, 0);
+            });
+
+            it('should add new entry to history and update the Messages tab', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.update_msg_history)
+                    .toHaveBeenCalledWith(false, 'Not connected to the server or the connection to the server has been closed.');
+                  done();
+                }, 0);
+            });
+
+            it('should enable the tool buttons', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.disable_tool_buttons)
+                    .toHaveBeenCalledWith(false);
+                  done();
+                }, 0);
+            });
+
+            it('should disable the cancel button', (done) => {
+              setTimeout(
+                () => {
+                  expect(cancelButtonSpy)
+                    .toHaveBeenCalledWith('disabled', true);
+                  done();
+                }, 0);
+            });
+
+            it('should login is not displayed', (done) => {
+              setTimeout(
+                () => {
+                  expect(userManagementMock.pga_login).not
+                    .toHaveBeenCalled();
+                  done();
+                }, 0);
+            });
+          });
+        });
+      });
+    });
+
+    context('when SQLEditor is NOT the query tool', () => {
+      beforeEach(() => {
+        sqlEditorMock.is_query_tool = false;
+      });
+
+      describe('when server answer with success', () => {
+        describe('when query was successful', () => {
+          beforeEach(() => {
+            response = {
+              data: {status: 'Success'},
+            };
+            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
+
+            executeQuery.poll();
+          });
+
+          it('should update the loading icon message', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.trigger)
+                .toHaveBeenCalledWith(
+                  'pgadmin-sqleditor:loading-icon:message',
+                  'Loading data from the database server and rendering...'
+                );
+              done();
+            }, 0);
+          });
+
+          it('should render the results', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.call_render_after_poll)
+                .toHaveBeenCalledWith({status: 'Success'});
+              done();
+            }, 0);
+          });
+        });
+
+        describe('when query is still running', () => {
+          context('when no additional information is returned', () => {
+            beforeEach(() => {
+              response = {
+                data: {status: 'Busy'},
+              };
+
+              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
+              executeQuery.poll();
+            });
+
+            it('should set the flag to inform SQLEditor a query is running', (done) => {
+              setTimeout(() => {
+                expect(sqlEditorMock.setIsQueryRunning)
+                  .toHaveBeenCalledWith(true);
+                done();
+              }, 0);
+            });
+
+            it('should does not update history', (done) => {
+              setTimeout(() => {
+                expect(sqlEditorMock.update_msg_history).not
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+            });
+
+            it('should recursively call polling', (done) => {
+              setTimeout(() => {
+                expect(executeQuery.delayedPoll)
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+            });
+          });
+
+          context('when additional information is returned', () => {
+            beforeEach(() => {
+              response = {
+                data: {
+                  status: 'Busy',
+                  result: 'Some important result',
+                },
+              };
+
+              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
+              executeQuery.poll();
+            });
+
+            it('should set the flag to inform SQLEditor a query is running', (done) => {
+              setTimeout(() => {
+                expect(sqlEditorMock.setIsQueryRunning)
+                  .toHaveBeenCalledWith(true);
+                done();
+              }, 0);
+            });
+
+            it('should update history message', (done) => {
+              setTimeout(() => {
+                expect(sqlEditorMock.update_msg_history)
+                  .toHaveBeenCalledWith('Busy', 'Some important result', false);
+                done();
+              }, 0);
+            });
+
+            it('should recursively call polling', (done) => {
+              setTimeout(() => {
+                expect(executeQuery.delayedPoll)
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+            });
+          });
+        });
+
+        describe('when the application lost connection with the database', () => {
+          beforeEach(() => {
+            response = {
+              data: {
+                status: 'NotConnected',
+                result: 'Some interesting result',
+              },
+            };
+            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
+
+            executeQuery.poll();
+          });
+
+          it('should hide the loading icon', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.trigger)
+                .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+              done();
+            }, 0);
+          });
+
+          it('should add new entry to history and update the Messages tab and clear the result grid', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.update_msg_history)
+                .toHaveBeenCalledWith(
+                  false,
+                  'Some interesting result',
+                  true
+                );
+              done();
+            }, 0);
+          });
+
+          it('should NOT enable the tool buttons', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.disable_tool_buttons).not
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+          });
+
+          it('should NOT disable the cancel button', (done) => {
+            setTimeout(
+              () => {
+                expect(cancelButtonSpy).not
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+          });
+        });
+
+        describe('when query was cancelled', () => {
+          beforeEach(() => {
+            response = {
+              data: {status: 'Cancel'},
+            };
+            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
+
+            executeQuery.poll();
+          });
+
+          it('should hide the loading icon', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.trigger)
+                .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+              done();
+            }, 0);
+          });
+
+          it('should add new entry to history, add cancellation message to Messages tab and clear the result grid', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.update_msg_history)
+                .toHaveBeenCalledWith(
+                  false,
+                  'Execution Cancelled!',
+                  true
+                );
+              done();
+            }, 0);
+          });
+        });
+      });
+
+      describe('when an error occur', () => {
+        let errorMessageJson = {
+          errormsg: 'Some error in JSON',
+        };
+        let errorMessageText = 'Some plain text error';
+        let response;
+
+        describe('when the connection to the server was lost', () => {
+          describe('when JSON response is available', () => {
+            beforeEach(() => {
+              response = {responseJSON: errorMessageJson};
+              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);
+
+              executeQuery.poll();
+            });
+
+            it('should hide the loading icon', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.trigger)
+                    .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                  done();
+                }, 0);
+            });
+
+            it('should reset last query information', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.resetQueryHistoryObject)
+                    .toHaveBeenCalledWith(sqlEditorMock);
+                  done();
+                }, 0);
+            });
+
+            it('should highlight the error in the SQL panel', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock._highlight_error)
+                    .toHaveBeenCalledWith('Some error in JSON');
+                  done();
+                }, 0);
+            });
+
+            it('should add new entry to history and update the Messages tab', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.update_msg_history)
+                    .toHaveBeenCalledWith(false, 'Some error in JSON');
+                  done();
+                }, 0);
+            });
+
+            it('should enable the tool buttons', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.disable_tool_buttons).not
+                    .toHaveBeenCalled();
+                  done();
+                }, 0);
+            });
+
+            it('should disable the cancel button', (done) => {
+              setTimeout(
+                () => {
+                  expect(cancelButtonSpy).not
+                    .toHaveBeenCalled();
+                  done();
+                }, 0);
+            });
+          });
+          describe('when no JSON response is available', () => {
+            beforeEach(() => {
+              response = {errormsg: errorMessageText};
+              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);
+
+              executeQuery.poll();
+            });
+
+            it('should hide the loading icon', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.trigger)
+                    .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                  done();
+                }, 0);
+            });
+
+            it('should reset last query information', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.resetQueryHistoryObject)
+                    .toHaveBeenCalledWith(sqlEditorMock);
+                  done();
+                }, 0);
+            });
+
+            it('should highlight the error in the SQL panel', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock._highlight_error)
+                    .toHaveBeenCalledWith('Some plain text error');
+                  done();
+                }, 0);
+            });
+
+            it('should add new entry to history and update the Messages tab', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.update_msg_history)
+                    .toHaveBeenCalledWith(false, 'Some plain text error');
+                  done();
+                }, 0);
+            });
+
+            it('should not enable the tool buttons', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.disable_tool_buttons).not
+                    .toHaveBeenCalled();
+                  done();
+                }, 0);
+            });
+
+            it('should not disable the cancel button', (done) => {
+              setTimeout(
+                () => {
+                  expect(cancelButtonSpy).not
+                    .toHaveBeenCalled();
+                  done();
+                }, 0);
+            });
+          });
+
+          describe('when cannot reach the Python Server', () => {
+            beforeEach(() => {
+              response = {readyState: 0};
+              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);
+
+              executeQuery.poll();
+            });
+
+            it('should hide the loading icon', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.trigger)
+                    .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                  done();
+                }, 0);
+            });
+
+            it('should reset last query information', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.resetQueryHistoryObject)
+                    .toHaveBeenCalledWith(sqlEditorMock);
+                  done();
+                }, 0);
+            });
+
+            it('should not highlight the error in the SQL panel', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock._highlight_error).not
+                    .toHaveBeenCalled();
+                  done();
+                }, 0);
+            });
+
+            it('should add new entry to history and update the Messages tab', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.update_msg_history)
+                    .toHaveBeenCalledWith(false, 'Not connected to the server or the connection to the server has been closed.');
+                  done();
+                }, 0);
+            });
+
+            it('should enable the tool buttons', (done) => {
+              setTimeout(
+                () => {
+                  expect(sqlEditorMock.disable_tool_buttons).not
+                    .toHaveBeenCalled();
+                  done();
+                }, 0);
+            });
+
+            it('should disable the cancel button', (done) => {
+              setTimeout(
+                () => {
+                  expect(cancelButtonSpy).not
+                    .toHaveBeenCalled();
+                  done();
+                }, 0);
+            });
+          });
+        });
+      });
+    });
+  });
+
+  describe('#execute', () => {
+    let response;
+
+    beforeEach(() => {
+      response = {
+        'info': '',
+        'errormsg': '',
+        'data': {
+          'status': true,
+          'can_edit': false,
+          'info_notifier_timeout': 5,
+          'result': '2',
+          'can_filter': false,
+        },
+        'result': null,
+        'success': 1,
+      };
+    });
+
+    context('when the SQL statement is empty', () => {
+      it('should return without executing', (done) => {
+        let wasNetworkCalled = false;
+        networkMock.onAny('/sqleditor/query_tool/start/123').reply(() => {
+          wasNetworkCalled = true;
+        });
+
+        executeQuery.execute('', {});
+
+        setTimeout(() => {
+          expect(wasNetworkCalled).toBe(false);
+          done();
+        }, 0);
+      });
+    });
+
+    context('when the SQL statement is not empty', () => {
+      let pollSpy;
+      let jqueryPropSpy;
+
+      beforeEach(() => {
+        jqueryPropSpy = spyOn($.fn, 'prop');
+        sqlEditorMock.gridView = {};
+        sqlEditorMock.gridView.query_tool_obj = jasmine.createSpyObj(
+          'QueryToolObject',
+          ['removeLineClass']
+        );
+      });
+
+      describe('before the backend request', () => {
+        beforeEach(() => {
+          jasmine.clock().install();
+          jasmine.clock().mockDate(startTime);
+          jasmine.clock().tick(50);
+          networkMock.onAny('/sqleditor/query_tool/start/123').reply(200, response);
+          pollSpy = spyOn(executeQuery, 'poll');
+          executeQuery.execute('some sql query', '');
+        });
+
+        afterEach(function () {
+          jasmine.clock().uninstall();
+        });
+
+        it('should update loading text to "Initializing query execution"', () => {
+          expect(sqlEditorMock.trigger)
+            .toHaveBeenCalledWith(
+              'pgadmin-sqleditor:loading-icon:show',
+              'Initializing query execution...'
+            );
+        });
+
+        it('disables the run query button', () => {
+          const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-flash');
+
+          expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
+        });
+
+        it('enable the cancel query button', () => {
+          const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');
+
+          expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', false);
+        });
+
+        it('disable the query tool buttons', () => {
+          expect(sqlEditorMock.disable_tool_buttons).toHaveBeenCalledWith(true);
+        });
+
+        it('initializes the polling flags', () => {
+          expect(sqlEditorMock._init_polling_flags).toHaveBeenCalled();
+        });
+
+        it('save the query', () => {
+          expect(sqlEditorMock.query).toEqual('some sql query');
+        });
+
+        it('reset the number of rows that were affected', () => {
+          expect(sqlEditorMock.rows_affected).toBe(0);
+        });
+
+        it('reset query start time', () => {
+          expect(sqlEditorMock.query_start_time.getTime()).toEqual(startTime.getTime() + 50);
+        });
+      });
+
+      describe('when HTTP return 200', () => {
+        describe('when backend informs that query started successfully', () => {
+          beforeEach(() => {
+            networkMock.onAny('/sqleditor/query_tool/start/123').reply(200, response);
+            pollSpy = spyOn(executeQuery, 'delayedPoll');
+            executeQuery.execute('some sql query', '');
+          });
+
+          it('should changes the loading message to "Waiting for the query execution to complete"', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.trigger).toHaveBeenCalledWith(
+                'pgadmin-sqleditor:loading-icon:message',
+                'Waiting for the query execution to complete...'
+              );
+              done();
+            }, 0);
+          });
+
+          it('should update the can edit flag', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.can_edit).toBe(false);
+              done();
+            }, 0);
+          });
+
+          it('should update the can filter flag', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.can_filter).toBe(false);
+              done();
+            }, 0);
+          });
+
+          it('should update information notifier timeout', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.info_notifier_timeout).toBe(5);
+              done();
+            }, 0);
+          });
+
+          it('should start polling', (done) => {
+            setTimeout(() => {
+              expect(pollSpy).toHaveBeenCalled();
+              done();
+            }, 0);
+          });
+        });
+
+        describe('when explain plan is not empty', () => {
+          it('should send the explain plan informatioon through the wire', (done) => {
+            networkMock.onAny('/sqleditor/query_tool/start/123').reply((config) => {
+              setTimeout(() => {
+                expect(config.data).toEqual(JSON.stringify({
+                  sql: 'some sql query',
+                  explain_plan: {
+                    buffers: true,
+                    analyze: false,
+                    timing: true,
+                    summary: false,
+                  },
+                }));
+
+                done();
+              }, 0);
+              return [200, ''];
+            });
+            pollSpy = spyOn(executeQuery, 'delayedPoll');
+            executeQuery.execute('some sql query', {
+              buffers: true,
+              analyze: false,
+              timing: true,
+              summary: false,
+            });
+          });
+
+
+        });
+
+        describe('when backend informs that there was a problem with the query', () => {
+          beforeEach(() => {
+            response.data.status = false;
+            response.data.result = 'something went wrong';
+            networkMock.onAny('/sqleditor/query_tool/start/123').reply(200, response);
+            pollSpy = spyOn(executeQuery, 'poll');
+            executeQuery.execute('some sql query', '');
+          });
+
+          it('hide the loading screen', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.trigger).toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+              done();
+            }, 0);
+          });
+
+          it('disable the cancel query button', (done) => {
+            setTimeout(() => {
+              const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');
+
+              expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
+              done();
+            }, 0);
+          });
+
+          it('enable the query tool buttons', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.disable_tool_buttons).toHaveBeenCalledWith(false);
+              done();
+            }, 0);
+          });
+
+          it('update the history tab with the result message', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.update_msg_history).toHaveBeenCalledWith(false, 'something went wrong');
+              done();
+            }, 0);
+          });
+
+          it('highlight the error in the editor', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock._highlight_error).toHaveBeenCalledWith('something went wrong');
+              done();
+            }, 0);
+          });
+
+          it('should not start polling', (done) => {
+            setTimeout(() => {
+              expect(pollSpy).not.toHaveBeenCalled();
+              done();
+            }, 0);
+          });
+        });
+
+        describe('when there is a marker set in the grid', () => {
+          let markerClearSpy;
+          beforeEach(() => {
+            sqlEditorMock.gridView.marker = jasmine.createSpyObj(
+              'GridViewMarker',
+              ['clear']
+            );
+            markerClearSpy = sqlEditorMock.gridView.marker.clear;
+            networkMock.onAny('/sqleditor/query_tool/start/123').reply(200, response);
+            pollSpy = spyOn(executeQuery, 'poll');
+            executeQuery.execute('some sql query', '');
+          });
+
+          it('should call clear function on marker', (done) => {
+            setTimeout(() => {
+              expect(markerClearSpy).toHaveBeenCalled();
+              done();
+            }, 0);
+          });
+
+          it('should removes the marker', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.gridView.marker).toEqual(null);
+              done();
+            }, 0);
+          });
+
+          it('should remove CSS classes from the editor', (done) => {
+            setTimeout(() => {
+              expect(sqlEditorMock.gridView.query_tool_obj.removeLineClass)
+                .toHaveBeenCalledWith(sqlEditorMock.marked_line_no, 'wrap', 'CodeMirror-activeline-background');
+              done();
+            }, 0);
+          });
+        });
+      });
+
+      describe('when cannot reach the Python Server', () => {
+        beforeEach(() => {
+          response = {
+            readyState: 0,
+          };
+          networkMock.onAny('/sqleditor/query_tool/start/123').reply(500, response);
+
+
+          executeQuery.execute('some sql query', '');
+        });
+
+        it('should hide the loading icon', (done) => {
+          setTimeout(
+            () => {
+              expect(sqlEditorMock.trigger)
+                .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+              done();
+            }, 0);
+        });
+
+        it('should not highlight the error in the SQL panel', (done) => {
+          setTimeout(
+            () => {
+              expect(sqlEditorMock._highlight_error).not
+                .toHaveBeenCalled();
+              done();
+            }, 0);
+        });
+
+        it('should add new entry to history and update the Messages tab', (done) => {
+          setTimeout(
+            () => {
+              expect(sqlEditorMock.update_msg_history)
+                .toHaveBeenCalledWith(false, 'Not connected to the server or the connection to the server has been closed.');
+              done();
+            }, 0);
+        });
+
+        it('should enable the tool buttons', (done) => {
+          setTimeout(
+            () => {
+              expect(sqlEditorMock.disable_tool_buttons)
+                .toHaveBeenCalledWith(false);
+              done();
+            }, 0);
+        });
+
+        it('should disable the cancel button', (done) => {
+          setTimeout(
+            () => {
+              const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');
+
+              expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
+              done();
+            }, 0);
+        });
+      });
+
+      describe('when error is returned by the server', () => {
+        describe('when login is not required', () => {
+          beforeEach(() => {
+            userManagementMock.is_pga_login_required.and.returnValue(false);
+            response.errormsg = 'some error message';
+            networkMock.onAny('/sqleditor/query_tool/start/123').reply(500, response);
+
+
+            executeQuery.execute('some sql query', '');
+          });
+
+          it('should hide the loading icon', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.trigger)
+                  .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                done();
+              }, 0);
+          });
+
+          it('should not highlight the error in the SQL panel', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock._highlight_error).not
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+          });
+
+          it('should add new entry to history and update the Messages tab', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.update_msg_history)
+                  .toHaveBeenCalledWith(false, 'some error message');
+                done();
+              }, 0);
+          });
+
+          it('should enable the tool buttons', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.disable_tool_buttons)
+                  .toHaveBeenCalledWith(false);
+                done();
+              }, 0);
+          });
+
+          it('should disable the cancel button', (done) => {
+            setTimeout(
+              () => {
+                const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');
+
+                expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
+                done();
+              }, 0);
+          });
+
+          it('should not save the state', () => {
+            setTimeout(() => {
+              expect(sqlEditorMock.save_state).not.toHaveBeenCalled();
+            }, 0);
+          });
+
+          it('should not display pga login', () => {
+            setTimeout(() => {
+              expect(userManagementMock.pga_login).not.toHaveBeenCalled();
+            }, 0);
+          });
+        });
+        describe('when login is required', () => {
+          beforeEach(() => {
+            userManagementMock.is_pga_login_required.and.returnValue(true);
+            response.errormsg = 'some error message';
+            networkMock.onAny('/sqleditor/query_tool/start/123').reply(500, response);
+
+
+            executeQuery.execute('some sql query', '');
+          });
+
+          it('should hide the loading icon', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.trigger)
+                  .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                done();
+              }, 0);
+          });
+
+          it('should not highlight the error in the SQL panel', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock._highlight_error).not
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+          });
+
+          it('should add new entry to history and update the Messages tab', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.update_msg_history)
+                  .toHaveBeenCalledWith(false, 'some error message');
+                done();
+              }, 0);
+          });
+
+          it('should enable the tool buttons', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.disable_tool_buttons)
+                  .toHaveBeenCalledWith(false);
+                done();
+              }, 0);
+          });
+
+          it('should disable the cancel button', (done) => {
+            setTimeout(
+              () => {
+                const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');
+
+                expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
+                done();
+              }, 0);
+          });
+
+          it('should save the state', () => {
+            setTimeout(() => {
+              expect(sqlEditorMock.save_state).toHaveBeenCalledWith(
+                'execute',
+                ['']
+              );
+            }, 0);
+          });
+
+          it('should display pga login', () => {
+            setTimeout(() => {
+              expect(userManagementMock.pga_login).toHaveBeenCalled();
+            }, 0);
+          });
+        });
+        describe('when a new transaction is not required', () => {
+          beforeEach(() => {
+            isNewTransactionRequiredMock.and.returnValue(false);
+            response.errormsg = 'some error message';
+            networkMock.onAny('/sqleditor/query_tool/start/123').reply(500, response);
+
+
+            executeQuery.execute('some sql query', '');
+          });
+
+          it('should hide the loading icon', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.trigger)
+                  .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                done();
+              }, 0);
+          });
+
+          it('should not highlight the error in the SQL panel', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock._highlight_error).not
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+          });
+
+          it('should add new entry to history and update the Messages tab', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.update_msg_history)
+                  .toHaveBeenCalledWith(false, 'some error message');
+                done();
+              }, 0);
+          });
+
+          it('should enable the tool buttons', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.disable_tool_buttons)
+                  .toHaveBeenCalledWith(false);
+                done();
+              }, 0);
+          });
+
+          it('should disable the cancel button', (done) => {
+            setTimeout(
+              () => {
+                const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');
+
+                expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
+                done();
+              }, 0);
+          });
+
+          it('should not save the state', () => {
+            setTimeout(() => {
+              expect(sqlEditorMock.save_state).not.toHaveBeenCalled();
+            }, 0);
+          });
+
+          it('should not display pga login', () => {
+            setTimeout(() => {
+              expect(userManagementMock.pga_login).not.toHaveBeenCalled();
+            }, 0);
+          });
+
+          it('should not initialize a new transaction', () => {
+            setTimeout(() => {
+              expect(sqlEditorMock.init_transaction).not.toHaveBeenCalled();
+            }, 0);
+          });
+        });
+
+        describe('when a new transaction is required', () => {
+          beforeEach(() => {
+            isNewTransactionRequiredMock.and.returnValue(true);
+            response.errormsg = 'some error message';
+            networkMock.onAny('/sqleditor/query_tool/start/123').reply(500, response);
+
+            executeQuery.execute('some sql query', '');
+          });
+
+          it('should hide the loading icon', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.trigger)
+                  .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
+                done();
+              }, 0);
+          });
+
+          it('should not highlight the error in the SQL panel', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock._highlight_error).not
+                  .toHaveBeenCalled();
+                done();
+              }, 0);
+          });
+
+          it('should add new entry to history and update the Messages tab', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.update_msg_history)
+                  .toHaveBeenCalledWith(false, 'some error message');
+                done();
+              }, 0);
+          });
+
+          it('should enable the tool buttons', (done) => {
+            setTimeout(
+              () => {
+                expect(sqlEditorMock.disable_tool_buttons)
+                  .toHaveBeenCalledWith(false);
+                done();
+              }, 0);
+          });
+
+          it('should disable the cancel button', (done) => {
+            setTimeout(
+              () => {
+                const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');
+
+                expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
+                done();
+              }, 0);
+          });
+
+          it('should save the state', () => {
+            setTimeout(() => {
+              expect(sqlEditorMock.save_state).toHaveBeenCalledWith(
+                'execute',
+                ['']
+              );
+            }, 0);
+          });
+
+          it('should not display pga login', () => {
+            setTimeout(() => {
+              expect(userManagementMock.pga_login).not.toHaveBeenCalled();
+            }, 0);
+          });
+
+          it('should initialize a new transaction', () => {
+            setTimeout(() => {
+              expect(sqlEditorMock.init_transaction).toHaveBeenCalled();
+            }, 0);
+          });
+        });
+      });
+    });
+  });
+
+  let findJQueryCallWithSelector = (jquerySpy, selector) => {
+    let result = undefined;
+
+    jquerySpy.calls.all().forEach((call) => {
+      if (call.object.selector === selector) {
+        result = call;
+      }
+    });
+    return result;
+  };
+});
+
+const jQuerytoHaveBeenCalledWith = function (util) {
+  return {
+    compare: function (actual) {
+      let result = {};
+      let expectedArgs = jasmine.util.argsToArray(arguments).slice(1);
+      if (actual.object === undefined || actual.object.selector === undefined) {
+        throw new Error('Expected a JQuery object, but got ' + jasmine.pp(actual) + '.');
+      }
+
+      result.pass = util.equals(actual.args, expectedArgs, '');
+      if (result.pass) {
+        result.message = 'larifo';
+      } else {
+        result.message =
+          'Expected jquery with selector "' +
+          actual.object.selector +
+          '" to have been called with ' +
+          jasmine.pp(expectedArgs) +
+          ' but was called with ' +
+          jasmine.pp(actual.args);
+      }
+
+      return result;
+    },
+  };
+};
+
diff --git a/web/regression/javascript/sqleditor/is_new_transaction_required_spec.js b/web/regression/javascript/sqleditor/is_new_transaction_required_spec.js
new file mode 100644
index 00000000..d323700d
--- /dev/null
+++ b/web/regression/javascript/sqleditor/is_new_transaction_required_spec.js
@@ -0,0 +1,65 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+
+import {is_new_transaction_required} from '../../../pgadmin/static/js/sqleditor/is_new_transaction_required';
+
+describe('#is_new_transaction_required', () => {
+  describe('when status is not 404', () => {
+    it('should return false', () => {
+      expect(is_new_transaction_required({
+        status: 300,
+      })).toBe(false);
+    });
+  });
+
+  describe('when status is 404', () => {
+    describe('when responseJSON is not present', () => {
+      it('should return false', () => {
+        expect(is_new_transaction_required({
+          status: 404,
+        })).toBeFalsy();
+      });
+    });
+
+    describe('when responseJSON is present', () => {
+      describe('when info is not present inside responseJSON', () => {
+        it('should return false', () => {
+          expect(is_new_transaction_required({
+            status: 404,
+            responseJSON: {},
+          })).toBeFalsy();
+        });
+      });
+
+      describe('when info is present inside responseJSON', () => {
+        describe('when info value is not "DATAGRID_TRANSACTION_REQUIRED"', () => {
+          it('should return false', () => {
+            expect(is_new_transaction_required({
+              status: 404,
+              responseJSON: {
+                info: 'some information',
+              },
+            })).toBe(false);
+          });
+        });
+
+        describe('when info value is "DATAGRID_TRANSACTION_REQUIRED"', () => {
+          it('should return false', () => {
+            expect(is_new_transaction_required({
+              status: 404,
+              responseJSON: {
+                info: 'DATAGRID_TRANSACTION_REQUIRED',
+              },
+            })).toBe(true);
+          });
+        });
+      });
+    });
+  });
+});
diff --git a/web/regression/javascript/sqleditor/query_tool_actions_spec.js b/web/regression/javascript/sqleditor/query_tool_actions_spec.js
index c46b4a24..f3ac7815 100644
--- a/web/regression/javascript/sqleditor/query_tool_actions_spec.js
+++ b/web/regression/javascript/sqleditor/query_tool_actions_spec.js
@@ -43,7 +43,6 @@ describe('queryToolActions', () => {
 
         expect(sqlEditorController.execute_data_query).toHaveBeenCalled();
       });
-
     });
   });
 
@@ -51,60 +50,100 @@ describe('queryToolActions', () => {
     describe('when verbose and costs are not selected and buffers and timing are not selected', () => {
       beforeEach(() => {
         setUpSpies('', '');
-        spyOn(queryToolActions, '_verbose').and.returnValue('OFF');
-        spyOn(queryToolActions, '_costsEnabled').and.returnValue('OFF');
-        spyOn(queryToolActions, '_buffers').and.returnValue('OFF');
-        spyOn(queryToolActions, '_timing').and.returnValue('OFF');
+        spyOn(queryToolActions, '_verbose').and.returnValue(false);
+        spyOn(queryToolActions, '_costsEnabled').and.returnValue(false);
+        spyOn(queryToolActions, '_buffers').and.returnValue(false);
+        spyOn(queryToolActions, '_timing').and.returnValue(false);
       });
+
       it('calls the execute function', () => {
         queryToolActions.explainAnalyze(sqlEditorController);
-        let explainAnalyzeQuery = 'EXPLAIN (FORMAT JSON, ANALYZE ON, VERBOSE OFF, COSTS OFF, BUFFERS OFF, TIMING OFF) ';
-        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainAnalyzeQuery);
+
+        // let explainAnalyzeQuery = 'EXPLAIN (FORMAT JSON, ANALYZE ON, VERBOSE OFF, COSTS OFF, BUFFERS OFF, TIMING OFF) ';
+        const explainObject = {
+          format: 'json',
+          analyze: true,
+          verbose: false,
+          costs: false,
+          buffers: false,
+          timing: false,
+          summary: false,
+        };
+
+        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
       });
     });
 
     describe('when verbose and costs and buffers and timing are all selected', () => {
       beforeEach(() => {
         setUpSpies('', '');
-        spyOn(queryToolActions, '_verbose').and.returnValue('ON');
-        spyOn(queryToolActions, '_costsEnabled').and.returnValue('ON');
-        spyOn(queryToolActions, '_buffers').and.returnValue('ON');
-        spyOn(queryToolActions, '_timing').and.returnValue('ON');
+        spyOn(queryToolActions, '_verbose').and.returnValue(true);
+        spyOn(queryToolActions, '_costsEnabled').and.returnValue(true);
+        spyOn(queryToolActions, '_buffers').and.returnValue(true);
+        spyOn(queryToolActions, '_timing').and.returnValue(true);
       });
       it('calls the execute function', () => {
         queryToolActions.explainAnalyze(sqlEditorController);
-        let explainAnalyzeQuery = 'EXPLAIN (FORMAT JSON, ANALYZE ON, VERBOSE ON, COSTS ON, BUFFERS ON, TIMING ON) ';
-        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainAnalyzeQuery);
+        const explainObject = {
+          format: 'json',
+          analyze: true,
+          verbose: true,
+          costs: true,
+          buffers: true,
+          timing: true,
+          summary: false,
+        };
+        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
       });
     });
 
     describe('when verbose is selected and costs is not selected and buffer is selected and timing is not selected', () => {
       beforeEach(() => {
         setUpSpies('', '');
-        spyOn(queryToolActions, '_verbose').and.returnValue('ON');
-        spyOn(queryToolActions, '_costsEnabled').and.returnValue('OFF');
-        spyOn(queryToolActions, '_buffers').and.returnValue('ON');
-        spyOn(queryToolActions, '_timing').and.returnValue('OFF');
+        spyOn(queryToolActions, '_verbose').and.returnValue(true);
+        spyOn(queryToolActions, '_costsEnabled').and.returnValue(false);
+        spyOn(queryToolActions, '_buffers').and.returnValue(true);
+        spyOn(queryToolActions, '_timing').and.returnValue(false);
       });
       it('calls the execute function', () => {
         queryToolActions.explainAnalyze(sqlEditorController);
-        let explainAnalyzeQuery = 'EXPLAIN (FORMAT JSON, ANALYZE ON, VERBOSE ON, COSTS OFF, BUFFERS ON, TIMING OFF) ';
-        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainAnalyzeQuery);
+
+        const explainObject = {
+          format: 'json',
+          analyze: true,
+          verbose: true,
+          costs: false,
+          buffers: true,
+          timing: false,
+          summary: false,
+        };
+
+        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
       });
     });
 
     describe('when verbose is  not selected and costs is selected and buffer is not selected and timing is  selected', () => {
       beforeEach(() => {
         setUpSpies('', '');
-        spyOn(queryToolActions, '_verbose').and.returnValue('OFF');
-        spyOn(queryToolActions, '_costsEnabled').and.returnValue('ON');
-        spyOn(queryToolActions, '_buffers').and.returnValue('OFF');
-        spyOn(queryToolActions, '_timing').and.returnValue('ON');
+        spyOn(queryToolActions, '_verbose').and.returnValue(false);
+        spyOn(queryToolActions, '_costsEnabled').and.returnValue(true);
+        spyOn(queryToolActions, '_buffers').and.returnValue(false);
+        spyOn(queryToolActions, '_timing').and.returnValue(true);
       });
       it('calls the execute function', () => {
         queryToolActions.explainAnalyze(sqlEditorController);
-        let explainAnalyzeQuery = 'EXPLAIN (FORMAT JSON, ANALYZE ON, VERBOSE OFF, COSTS ON, BUFFERS OFF, TIMING ON) ';
-        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainAnalyzeQuery);
+
+        const explainObject = {
+          format: 'json',
+          analyze: true,
+          verbose: false,
+          costs: true,
+          buffers: false,
+          timing: true,
+          summary: false,
+        };
+
+        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
       });
     });
   });
@@ -113,39 +152,67 @@ describe('queryToolActions', () => {
     describe('when verbose and costs are selected', () => {
       beforeEach(() => {
         setUpSpies('', '');
-        spyOn(queryToolActions, '_verbose').and.returnValue('ON');
-        spyOn(queryToolActions, '_costsEnabled').and.returnValue('ON');
+        spyOn(queryToolActions, '_verbose').and.returnValue(true);
+        spyOn(queryToolActions, '_costsEnabled').and.returnValue(true);
       });
+
       it('calls the execute function', () => {
         queryToolActions.explain(sqlEditorController);
-        let explainQuery = 'EXPLAIN (FORMAT JSON, ANALYZE OFF, VERBOSE ON, COSTS ON, BUFFERS OFF, TIMING OFF) ';
-        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainQuery);
+        const explainObject = {
+          format: 'json',
+          analyze: false,
+          verbose: true,
+          costs: true,
+          buffers: false,
+          timing: false,
+          summary: false,
+        };
+        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
       });
     });
 
     describe('when verbose and costs are not selected', () => {
       beforeEach(() => {
         setUpSpies('', '');
-        spyOn(queryToolActions, '_verbose').and.returnValue('OFF');
-        spyOn(queryToolActions, '_costsEnabled').and.returnValue('OFF');
+        spyOn(queryToolActions, '_verbose').and.returnValue(false);
+        spyOn(queryToolActions, '_costsEnabled').and.returnValue(false);
       });
+
       it('calls the execute function', () => {
         queryToolActions.explain(sqlEditorController);
-        let explainQuery = 'EXPLAIN (FORMAT JSON, ANALYZE OFF, VERBOSE OFF, COSTS OFF, BUFFERS OFF, TIMING OFF) ';
-        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainQuery);
+        const explainObject = {
+          format: 'json',
+          analyze: false,
+          verbose: false,
+          costs: false,
+          buffers: false,
+          timing: false,
+          summary: false,
+        };
+
+        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
       });
     });
 
     describe('when verbose is selected and costs is not selected', () => {
       beforeEach(() => {
         setUpSpies('', '');
-        spyOn(queryToolActions, '_verbose').and.returnValue('ON');
-        spyOn(queryToolActions, '_costsEnabled').and.returnValue('OFF');
+        spyOn(queryToolActions, '_verbose').and.returnValue(true);
+        spyOn(queryToolActions, '_costsEnabled').and.returnValue(false);
       });
+
       it('calls the execute function', () => {
         queryToolActions.explain(sqlEditorController);
-        let explainQuery = 'EXPLAIN (FORMAT JSON, ANALYZE OFF, VERBOSE ON, COSTS OFF, BUFFERS OFF, TIMING OFF) ';
-        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainQuery);
+        const explainObject = {
+          format: 'json',
+          analyze: false,
+          verbose: true,
+          costs: false,
+          buffers: false,
+          timing: false,
+          summary: false,
+        };
+        expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
       });
     });
   });


  [application/octet-stream] update-javascript-packages.diff (13.0K, 4-update-javascript-packages.diff)
  download | inline diff:
diff --git a/web/karma.conf.js b/web/karma.conf.js
index e9b0e062..9f04cb48 100644
--- a/web/karma.conf.js
+++ b/web/karma.conf.js
@@ -1,6 +1,6 @@
 // Karma configuration
-// Generated on Wed Mar 01 2017 14:19:28 GMT-0500 (EST)
 const webpackConfig = require('./webpack.test.config.js');
+const isDocker = require('is-docker')();
 
 module.exports = function (config) {
   config.set({
@@ -8,7 +8,7 @@ module.exports = function (config) {
     reporters: ['progress', 'kjhtml'],
     plugins: [
       'karma-webpack',
-      'karma-phantomjs-launcher',
+      'karma-chrome-launcher',
       'karma-jasmine',
       'karma-jasmine-html-reporter',
     ],
@@ -56,7 +56,15 @@ module.exports = function (config) {
 
     // start these browsers
     // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
-    browsers: ['PhantomJS'],
+    customLaunchers: {
+      ChromeCustom: {
+        base: 'ChromeHeadless',
+        // We must disable the Chrome sandbox when running Chrome inside Docker (Chrome's sandbox needs
+        // more permissions than Docker allows by default)
+        flags: isDocker ? ['--no-sandbox'] : []
+      }
+    },
+    browsers: ['ChromeCustom'],
 
 
     // Continuous Integration mode
diff --git a/web/package.json b/web/package.json
index 01a55ec4..a6c9659b 100644
--- a/web/package.json
+++ b/web/package.json
@@ -1,8 +1,9 @@
 {
   "license": "PostgreSQL",
   "devDependencies": {
+    "axios-mock-adapter": "^1.11.0",
     "babel-core": "~6.24.0",
-    "babel-loader": "~6.4.1",
+    "babel-loader": "~7.1.2",
     "babel-preset-es2015": "~6.24.0",
     "babel-preset-react": "~6.23.0",
     "cross-env": "^5.0.1",
@@ -11,14 +12,16 @@
     "enzyme-matchers": "^3.1.0",
     "eslint": "3.19.0",
     "eslint-plugin-react": "^6.10.3",
-    "extract-text-webpack-plugin": "^2.1.2",
+    "extract-text-webpack-plugin": "^3.0.2",
     "file-loader": "^0.11.2",
     "image-webpack-loader": "^3.3.1",
-    "jasmine-core": "~2.5.2",
-    "jasmine-enzyme": "^3.1.0",
+    "is-docker": "^1.1.0",
+    "jasmine-core": "~2.9.0",
+    "jasmine-enzyme": "~4.0.0",
     "karma": "~1.5.0",
     "karma-babel-preprocessor": "^6.0.1",
     "karma-browserify": "~5.1.1",
+    "karma-chrome-launcher": "^2.2.0",
     "karma-jasmine": "~1.1.0",
     "karma-jasmine-html-reporter": "^0.2.2",
     "karma-phantomjs-launcher": "~1.0.2",
@@ -69,7 +72,7 @@
     "jquery": "1.11.2",
     "jquery-contextmenu": "^2.5.0",
     "jquery-ui": "^1.12.1",
-    "moment": "^2.18.1",
+    "moment": "^2.20.1",
     "mousetrap": "^1.6.1",
     "prop-types": "^15.5.10",
     "react": "file:../web/pgadmin/static/vendor/react",
diff --git a/web/yarn.lock b/web/yarn.lock
index b57a80ca..4464b140 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -283,6 +283,12 @@ async@^2.1.2, async@^2.1.5:
   dependencies:
     lodash "^4.14.0"
 
+async@^2.4.1:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
+  dependencies:
+    lodash "^4.14.0"
+
 async@~0.9.0:
   version "0.9.2"
   resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
@@ -314,6 +320,12 @@ aws4@^1.2.1, aws4@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
 
+axios-mock-adapter@^1.11.0:
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.11.0.tgz#96e4bb2702cf6900f2ae5f9bdbef6e5dc86669e2"
+  dependencies:
+    deep-equal "^1.0.1"
+
 axios@^0.16.1:
   version "0.16.2"
   resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
@@ -473,14 +485,13 @@ babel-helpers@^6.24.1:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-loader@~6.4.1:
-  version "6.4.1"
-  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-6.4.1.tgz#0b34112d5b0748a8dcdbf51acf6f9bd42d50b8ca"
+babel-loader@~7.1.2:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.2.tgz#f6cbe122710f1aa2af4d881c6d5b54358ca24126"
   dependencies:
-    find-cache-dir "^0.1.1"
-    loader-utils "^0.2.16"
+    find-cache-dir "^1.0.0"
+    loader-utils "^1.0.2"
     mkdirp "^0.5.1"
-    object-assign "^4.0.1"
 
 babel-messages@^6.23.0:
   version "6.23.0"
@@ -1445,6 +1456,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
 
+circular-json-es6@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/circular-json-es6/-/circular-json-es6-2.0.2.tgz#e4f4a093e49fb4b6aba1157365746112a78bd344"
+
 circular-json@^0.3.1:
   version "0.3.3"
   resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
@@ -2017,6 +2032,10 @@ deep-equal-ident@^1.1.1:
   dependencies:
     lodash.isequal "^3.0"
 
+deep-equal@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
+
 deep-extend@~0.4.0:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
@@ -2363,12 +2382,19 @@ entities@^1.1.1, entities@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
 
-enzyme-matchers@^3.1.0, enzyme-matchers@^3.8.3:
+enzyme-matchers@^3.1.0:
   version "3.8.3"
   resolved "https://registry.yarnpkg.com/enzyme-matchers/-/enzyme-matchers-3.8.3.tgz#6269d47b0d81d5222745da503f27ac003ba208d2"
   dependencies:
     deep-equal-ident "^1.1.1"
 
+enzyme-matchers@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/enzyme-matchers/-/enzyme-matchers-4.0.2.tgz#3f4457d0d0da3e268af4bee9f222439dfca26214"
+  dependencies:
+    circular-json-es6 "^2.0.1"
+    deep-equal-ident "^1.1.1"
+
 enzyme@~2.8.2:
   version "2.8.2"
   resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.8.2.tgz#6c8bcb05012abc4aa4bc3213fb23780b9b5b1714"
@@ -2701,12 +2727,12 @@ extglob@^0.3.1:
   dependencies:
     is-extglob "^1.0.0"
 
-extract-text-webpack-plugin@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-2.1.2.tgz#756ef4efa8155c3681833fbc34da53b941746d6c"
+extract-text-webpack-plugin@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7"
   dependencies:
-    async "^2.1.2"
-    loader-utils "^1.0.2"
+    async "^2.4.1"
+    loader-utils "^1.1.0"
     schema-utils "^0.3.0"
     webpack-sources "^1.0.1"
 
@@ -2834,13 +2860,13 @@ [email protected]:
     statuses "~1.3.1"
     unpipe "~1.0.0"
 
-find-cache-dir@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9"
+find-cache-dir@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
   dependencies:
     commondir "^1.0.1"
-    mkdirp "^0.5.1"
-    pkg-dir "^1.0.0"
+    make-dir "^1.0.0"
+    pkg-dir "^2.0.0"
 
 find-up@^1.0.0:
   version "1.1.2"
@@ -2849,7 +2875,7 @@ find-up@^1.0.0:
     path-exists "^2.0.0"
     pinkie-promise "^2.0.0"
 
-find-up@^2.0.0:
+find-up@^2.0.0, find-up@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
   dependencies:
@@ -2943,6 +2969,12 @@ form-data@~2.3.1:
     combined-stream "^1.0.5"
     mime-types "^2.1.12"
 
+fs-access@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a"
+  dependencies:
+    null-check "^1.0.0"
+
 fs-extra@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950"
@@ -2984,11 +3016,11 @@ function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
 
 function.prototype.name@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.0.3.tgz#0099ae5572e9dd6f03c97d023fd92bcc5e639eac"
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
   dependencies:
     define-properties "^1.1.2"
-    function-bind "^1.1.0"
+    function-bind "^1.1.1"
     is-callable "^1.1.3"
 
 gauge@~2.7.3:
@@ -3778,6 +3810,10 @@ is-date-object@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
 
+is-docker@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-1.1.0.tgz#f04374d4eee5310e9a8e113bf1495411e46176a1"
+
 is-dotfile@^1.0.0:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
@@ -4033,15 +4069,15 @@ isurl@^1.0.0-alpha5:
     has-to-string-tag-x "^1.2.0"
     is-object "^1.0.1"
 
-jasmine-core@~2.5.2:
-  version "2.5.2"
-  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.5.2.tgz#6f61bd79061e27f43e6f9355e44b3c6cab6ff297"
+jasmine-core@~2.9.0:
+  version "2.9.1"
+  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.9.1.tgz#b6bbc1d8e65250d56f5888461705ebeeeb88f22f"
 
-jasmine-enzyme@^3.1.0:
-  version "3.8.3"
-  resolved "https://registry.yarnpkg.com/jasmine-enzyme/-/jasmine-enzyme-3.8.3.tgz#c9c04f82e7a47d2aead6d2f09c84722fc408ee20"
+jasmine-enzyme@~4.0.0:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/jasmine-enzyme/-/jasmine-enzyme-4.0.2.tgz#d5d3453164d9fe982144eb621147edaba85914be"
   dependencies:
-    enzyme-matchers "^3.8.3"
+    enzyme-matchers "^4.0.2"
 
 [email protected]:
   version "1.6.7"
@@ -4191,6 +4227,13 @@ karma-browserify@~5.1.1:
     minimatch "^3.0.0"
     os-shim "^0.1.3"
 
+karma-chrome-launcher@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz#cf1b9d07136cc18fe239327d24654c3dbc368acf"
+  dependencies:
+    fs-access "^1.0.0"
+    which "^1.2.1"
+
 karma-jasmine-html-reporter@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz#48a8e5ef18807617ee2b5e33c1194c35b439524c"
@@ -4417,7 +4460,7 @@ loader-runner@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
 
-loader-utils@^0.2.16, loader-utils@^0.2.5, loader-utils@~0.2.2:
+loader-utils@^0.2.5, loader-utils@~0.2.2:
   version "0.2.17"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
   dependencies:
@@ -4891,10 +4934,14 @@ moment-timezone@^0.4.0:
   dependencies:
     moment ">= 2.6.0"
 
-"moment@>= 2.6.0", moment@^2.10, moment@^2.18.1:
+"moment@>= 2.6.0", moment@^2.10:
   version "2.18.1"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
 
+moment@^2.20.1:
+  version "2.20.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd"
+
 mousetrap@^1.6.1:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.1.tgz#2a085f5c751294c75e7e81f6ec2545b29cbf42d9"
@@ -5123,6 +5170,10 @@ nth-check@~1.0.1:
   dependencies:
     boolbase "~1.0.0"
 
+null-check@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd"
+
 num2fraction@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
@@ -5506,11 +5557,11 @@ pinkie@^2.0.0:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
 
-pkg-dir@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
+pkg-dir@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
   dependencies:
-    find-up "^1.0.0"
+    find-up "^2.1.0"
 
 pluralize@^1.2.1:
   version "1.2.1"
@@ -7405,7 +7456,7 @@ which-module@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
 
-which@1, which@^1.2.9:
+which@1, which@^1.2.1, which@^1.2.9:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
   dependencies:


view thread (19+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected]
  Subject: Re: [pgadmin][patch] [GreenPlum] When user press Explain Plan and Explain analyze plan an error is displayed
  In-Reply-To: <CAE+jjakOhMJBMqsUaQuL9w4an0V4Q4nvY51K3GqWzm8jbj9cog@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox