/* 
 * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "stdafx.h"

#include "wb_sql_editor_form.h"

#include "sqlide/recordset_be.h"
#include "sqlide/recordset_cdbc_storage.h"
#include "grtsqlparser/sql_facade.h"
#include "grtui/confirm_save_dialog.h"
#include "sqlide/wb_sql_editor_snippets.h"
#include "base/string_utilities.h"
#include "base/notifications.h"

#include "objimpl/db.query/db_query_Resultset.h"

#include "workbench/wb_context_ui.h"

#include "sqlide/sql_script_run_wizard.h"

#include "base/file_functions.h"
#include "base/log.h"

// TODO: check if this inclusion is still necessary.
// this doesn't belong here, but there's no other workaround for having access to mysql_info
// because of that we also need to link wbprivate directly to the connector, making their
// dynamic loading pretty much useless
#include <cppconn/../driver/mysql_connection.h>

#include "base/boost_smart_ptr_helpers.h"

#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/signals2/connection.hpp>
#include "grt/common.h"

#include "query_side_palette.h"

#include "mforms/menubar.h"
#include "mforms/grttreeview.h"
#include "mforms/hypertext.h" // needed for d-tor
#include "mforms/tabview.h" // needed for d-tor
#include "mforms/splitter.h" // needed for d-tor
#include "mforms/toolbar.h"

using namespace bec;
using namespace grt;
using namespace wb;
using namespace base;

using boost::signals2::scoped_connection;

DEFAULT_LOG_DOMAIN("SqlEditor")

static const char *SQL_EXCEPTION_MSG_FORMAT= _("Error Code: %i\n%s");
static const char *EXCEPTION_MSG_FORMAT= _("Error: %s");

#define CATCH_SQL_EXCEPTION_AND_DISPATCH(statement, log_message_index, duration) \
catch (sql::SQLException &e)\
{\
  set_log_message(log_message_index, DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), statement, duration);\
}

#define CATCH_EXCEPTION_AND_DISPATCH(statement) \
catch (std::exception &e)\
{\
  add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(EXCEPTION_MSG_FORMAT, e.what()), statement, "");\
}

#define CATCH_ANY_EXCEPTION_AND_DISPATCH(statement) \
catch (sql::SQLException &e)\
{\
  add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), statement, "");\
}\
CATCH_EXCEPTION_AND_DISPATCH(statement)

#define CATCH_ANY_EXCEPTION_AND_DISPATCH_TO_DEFAULT_LOG(statement) \
catch (sql::SQLException &e)\
{\
  _grtm->get_grt()->send_error(strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), statement);\
}\
catch (std::exception &e)\
{\
  _grtm->get_grt()->send_error(strfmt(EXCEPTION_MSG_FORMAT, e.what()), statement);\
}


class Timer
{
public:
  Timer(bool run_immediately) : _is_running(false), _start_timestamp(0), _duration(0)
  {
    if (run_immediately)
      run();
  }
  void reset()
  {
    _is_running= false;
    _start_timestamp= 0;
    _duration= 0;
  }
  void run()
  {
    if (_is_running)
      return;
    _is_running= true;
    _start_timestamp= timestamp();
  }
  void stop()
  {
    if (!_is_running)
      return;
    _is_running= false;
    _duration+= timestamp() - _start_timestamp;
  }
  time_t duration()
  {
    return _is_running ? (_duration + timestamp() - _start_timestamp) : _duration;
  }
  std::string duration_formatted()
  {
    std::div_t s;
    s= std::div((int)duration(), 1000);
    return strfmt(_("%i.%.3i sec"), s.quot, s.rem);
  }
private:
  bool _is_running;
  time_t _start_timestamp;
  time_t _duration;
};

/*
std::string & strip_sql_ident(std::string &text)
{
  std::locale loc;

  size_t size= text.size();

  size_t start= 0;
  for (; start < size; ++start)
    if (!std::isspace(text[start], loc) && (text[start] != '`') && (text[start] != '\'') && (text[start] != '"'))
      break;

  size_t end= size;
  for (; end > 0; --end)
    if (!std::isspace(text[end-1], loc) && (text[end-1] != '`') && (text[end-1] != '\'') && (text[end-1] != '"'))
      break;

  return text= text.substr(start, end-start);
}*/


SqlEditorForm::Ref SqlEditorForm::create(wb::WBContextSQLIDE *wbsql, const db_mgmt_ConnectionRef &conn)
{
  SqlEditorForm::Ref instance(new SqlEditorForm(wbsql, conn)); 
  
  instance->get_base_schema_tree()->set_delegate(instance);
  instance->get_base_schema_tree()->set_fetch_delegate(instance);
  
  instance->get_filtered_schema_tree()->set_delegate(instance);
  instance->get_filtered_schema_tree()->set_fetch_delegate(instance);
  instance->get_filtered_schema_tree()->set_base(instance->get_base_schema_tree());

  if (conn.is_valid())
  {
    //instance->connect();
    //instance->finish_startup();
  }
  return instance;
}


SqlEditorForm::SqlEditorForm(wb::WBContextSQLIDE *wbsql, const db_mgmt_ConnectionRef &conn)
  :
  _wbsql(wbsql),
  _context_ui(_wbsql->get_wbui()),
  _grtm(wbsql->get_grt_manager()),
  _menu(NULL),  // Please use NULL where pointers are assigned, not 0, to avoid confusing the param with ctor flags or similar!
  _toolbar(NULL),
  _autosave_lock(NULL),
  _autosave_disabled(false),
  _loading_workspace(false),
  _close_done(false),
  _sql_editors_serial(0),
  _scratch_editors_serial(0),
  _active_sql_editor_index(0),
  _updating_sql_editor(0),
  _keep_alive_thread(NULL),
  _connection(conn),
  _aux_dbc_conn(new sql::Dbc_connection_handler()),
  _usr_dbc_conn(new sql::Dbc_connection_handler()),
  exec_sql_task(GrtThreadedTask::create(wbsql->get_grt_manager())),
  _is_running_query(false),
  _schema_tree(&_base_schema_tree),
  _base_schema_tree(wbsql->get_grt_manager()->get_grt()),
  _filtered_schema_tree(wbsql->get_grt_manager()->get_grt()),
  live_schema_fetch_task(GrtThreadedTask::create(wbsql->get_grt_manager())),
  _side_bar(NULL),
  _side_splitter(NULL), _info_tabview(NULL), _object_info(NULL), _session_info(NULL),
  _side_palette_host(NULL),
  _side_palette(NULL),
  _is_refreshing_schema_tree(false),
  live_schemata_refresh_task(GrtThreadedTask::create(wbsql->get_grt_manager())),
  _log(DbSqlEditorLog::create(wbsql->get_grt_manager())),
  _history(DbSqlEditorHistory::create(wbsql->get_grt_manager()))
{  
  _options= DictRef::cast_from(_grtm->get_grt()->get("/wb/options/options"));

  //scoped_connect(_wbsql->get_wbui()->signal_main_form_change(),boost::bind(&SqlEditorForm::main_form_changed, this, _1));
  NotificationCenter::get()->add_observer(this, "GNMainFormChanged");
  NotificationCenter::get()->add_observer(this, "GNFormTitleDidChange");  

  _sql_editors_mutex= g_mutex_new();
  _aux_dbc_conn_mutex= g_mutex_new();
  _usr_dbc_conn_mutex= g_mutex_new();
  _keep_alive_thread_mutex= g_mutex_new();
  _schema_contents_mutex= g_mutex_new();

  _base_schema_tree.is_schema_contents_enabled(_options.get_int("DbSqlEditor:ShowSchemaTreeSchemaContents", 1) != 0);
  _filtered_schema_tree.is_schema_contents_enabled(_options.get_int("DbSqlEditor:ShowSchemaTreeSchemaContents", 1) != 0);

  _base_schema_tree.sql_editor_text_insert_signal.connect(boost::bind(&SqlEditorForm::call_sql_editor_text_insert_signal,this,_1));
  _filtered_schema_tree.sql_editor_text_insert_signal.connect(boost::bind(&SqlEditorForm::call_sql_editor_text_insert_signal,this,_1));

  _base_schema_tree.object_plugin_items_slot= boost::bind(&SqlEditorForm::get_live_catalog_menu_items, this, _1, _2, _3);
  _filtered_schema_tree.object_plugin_items_slot= boost::bind(&SqlEditorForm::get_live_catalog_menu_items, this, _1, _2, _3);
  _base_schema_tree.call_object_plugin_items_slot= boost::bind(&SqlEditorForm::call_live_catalog_menu_item, this, _1, _2, _3, _4);
  _filtered_schema_tree.call_object_plugin_items_slot= boost::bind(&SqlEditorForm::call_live_catalog_menu_item, this, _1, _2, _3, _4);

  live_schemata_refresh_task->send_task_res_msg(false);
  live_schemata_refresh_task->msg_cb(boost::bind(&SqlEditorForm::add_log_message, this, _1, _2, _3, ""));

  live_schema_fetch_task->send_task_res_msg(false);
  live_schema_fetch_task->msg_cb(boost::bind(&SqlEditorForm::add_log_message, this, _1, _2, _3, ""));

  exec_sql_task->send_task_res_msg(false);
  exec_sql_task->msg_cb(boost::bind(&SqlEditorForm::add_log_message, this, _1, _2, _3, ""));

  _last_log_message_timestamp= timestamp();

  _progress_status_update_interval= _options.get_int("DbSqlEditor:ProgressStatusUpdateInterval", 500);

  int keep_alive_interval= _options.get_int("DbSqlEditor:KeepAliveInterval", 600);
  if (keep_alive_interval != 0)
  {
    _keep_alive_thread= TimerActionThread::create(boost::bind(&SqlEditorForm::send_message_keep_alive, this), keep_alive_interval*1000);
    _keep_alive_thread->on_exit.connect(boost::bind(&SqlEditorForm::reset_keep_alive_thread, this));
  }

  _continue_on_error= (_options.get_int("DbSqlEditor:ContinueOnError", 0) != 0);
}


void SqlEditorForm::finish_startup()
{
  // this part was in do_connect(), but moved here since they need to be called
  // from main thread anyway
  //>
  //live_schemata_refresh_task->execute_in_main_thread(
  //  boost::bind(&LiveSchemaTree::set_active_schema, _schema_tree, 
  //             _connection->parameterValues().get_string("schema")), false, true);
  if (_usr_dbc_conn && !_usr_dbc_conn->active_schema.empty())
    _base_schema_tree.set_active_schema(_usr_dbc_conn->active_schema);

  //live_schemata_refresh_task->execute_in_main_thread(
  //  boost::bind((void(SqlEditorForm::*)(bool))&SqlEditorForm::refresh_schema_tree, this),
  //  false,
  //  true);
  refresh_schema_tree(true);
  update_menu_and_toolbar();
  //<

  setup_sidebars();

  std::string name = _connection->name();
  if (name.empty())
    name = _connection->hostIdentifier();
  if (!name.empty())
    load_workspace(sanitize_file_name(name));
  
  if (_sql_editors.empty())
      add_sql_editor();
  
  if (_side_bar)
  {
    // update the info box
    schema_row_selected();
  }
  
  // Gets the title for a NEW editor
  _title = get_title(true);
  title_changed();
}

void SqlEditorForm::title_changed()
{
  base::NotificationInfo info;
  info["form"] = form_id();
  info["title"] = _title;
  info["hostName"] = _connection->parameterValues().get_string("hostName");
  base::NotificationCenter::get()->send("GNFormTitleDidChange", this, info);
}

SqlEditorForm::~SqlEditorForm()
{
  NotificationCenter::get()->remove_observer(this);

  delete _autosave_lock;
  _autosave_lock = 0;
  delete _info_tabview;
  delete _side_bar;

  // Destructor can be called before the startup was finished.
  if (_side_palette_host != NULL)
    _side_palette_host->release();
  if (_side_palette != NULL)
    _side_palette->release();
  if (_side_splitter != NULL)
    _side_splitter->release();

  delete _session_info;
  delete _object_info;

  delete _toolbar;
  delete _menu;
  reset();

  reset_keep_alive_thread();

  g_mutex_free(_schema_contents_mutex);
  g_mutex_free(_keep_alive_thread_mutex);
  g_mutex_free(_usr_dbc_conn_mutex);
  g_mutex_free(_aux_dbc_conn_mutex);
  g_mutex_free(_sql_editors_mutex);
}


void SqlEditorForm::handle_notification(const std::string &name, void *sender, base::NotificationInfo &info)
{
  if (name == "GNMainFormChanged")
  {
    if (_side_palette)
      _side_palette->close_popover();
    if (_wbsql->get_wbui()->get_active_main_form() == this)
      update_menu_and_toolbar();
  }
  else if (name == "GNFormTitleDidChange")
  {
    // Validates only if another editor to the same connection has sent the notification
    if (info["form"] != form_id() && info["hostName"] == _connection->parameterValues().get_string("hostName"))
    {
      // This code is reached when at least 2 editors to the same host
      // have been opened, so the label of the old editor (which may not
      // contain the schema name should be updated with it)
      std::string temp_title = get_title(true);
      
      if (temp_title != _title)
      {
        _title = temp_title;
        title_changed();
      }
    }
  }
}


void SqlEditorForm::reset_keep_alive_thread()
{
  GMutexLock keep_alive_thread_lock(_keep_alive_thread_mutex);
  if (_keep_alive_thread)
  {
    _keep_alive_thread->stop(true);
    _keep_alive_thread= NULL;
  }
}

grt::StringRef SqlEditorForm::do_disconnect(grt::GRT *grt)
{
  if (_usr_dbc_conn->ref.get())
  {
    sql::Driver *dbc_driver= _usr_dbc_conn->ref->getDriver();

    close_connection(_usr_dbc_conn);
    close_connection(_aux_dbc_conn);
    _aux_dbc_conn->ref.reset();
    _usr_dbc_conn->ref.reset();
    
    dbc_driver->threadEnd();
  }
  return grt::StringRef();
}

bool SqlEditorForm::close()
{
  if (_close_done)
    return true;
  _close_done = true;
  
  if (exec_sql_task && exec_sql_task->is_busy())
  {
    _grtm->replace_status_text("Cannot close SQL Editor while busy");
    return false;
  }

  bool res= bec::UIForm::close();
  if (res)
  {
    grt::ValueRef option(_grtm->get_app_option("workbench:SaveSQLWorkspaceOnClose"));
    if (option.is_valid() && *grt::IntegerRef::cast_from(option))
    {
      _grtm->replace_status_text("Saving workspace state...");
      if (_autosave_path.empty())
      {
        save_workspace(sanitize_file_name(_connection->name()), false);
        delete _autosave_lock;
      }
      else
      {
        auto_save();
        
        // Remove auto lock first or renaming the folder will fail.
        delete _autosave_lock;
        std::string new_name(base::strip_extension(_autosave_path)+".workspace");
        int try_count = 0;

        // Rename our temporary workspace if one exists to make it a persistent one.
        if (base::file_exists(_autosave_path))
        {
          for (;;)
          {
            try
            {
              if (base::file_exists(new_name))
                base::remove_recursive(new_name);
              base::rename(_autosave_path, new_name);
            }
            catch (base::file_error &err)
            {
              std::string path(dirname(_autosave_path));
              do
              {
                ++try_count;
                new_name = make_path(path, sanitize_file_name(_connection->name()).append(strfmt("-%i.workspace", try_count)));
              } while (file_exists(new_name));

              if (err.code() == base::already_exists)
                continue;
              log_warning("Could not rename autosave directory: %s\n", 
                _autosave_path.c_str(), err.what());
            }

            break;
          }
        }
      }
      _autosave_lock = 0;
    }
    else
    {
      delete _autosave_lock;
      _autosave_lock = 0;
      if (!_autosave_path.empty())
        base_rmdir_recursively(_autosave_path.c_str());
    }

    _grtm->replace_status_text("Closing SQL Editor...");
    wbsql()->editor_will_close(this);

    exec_sql_task->exec(true, boost::bind(&SqlEditorForm::do_disconnect, this, _1));
    exec_sql_task->disconnect_callbacks();
    reset_keep_alive_thread();
   // close_connection(_usr_dbc_conn);
  //  close_connection(_aux_dbc_conn);
  //  _aux_dbc_conn->ref.reset();
   // _usr_dbc_conn->ref.reset();
    _grtm->replace_status_text("SQL Editor closed");
  }
  return res;
}


std::string SqlEditorForm::get_form_context_name() const
{
  return WB_CONTEXT_QUERY;
}


bool SqlEditorForm::get_session_variable(sql::Dbc_connection_handler::Ref &dbc_conn, const std::string &name, std::string &value)
{
  if (dbc_conn && dbc_conn->ref.get())
  {
    SqlFacade::Ref sql_facade= SqlFacade::instance_for_rdbms(rdbms());
    Sql_specifics::Ref sql_specifics= sql_facade->sqlSpecifics();
    std::string query= sql_specifics->query_variable(name);
    if (query.empty())
      return false;
    boost::scoped_ptr<sql::Statement> statement(dbc_conn->ref->createStatement());
    boost::scoped_ptr<sql::ResultSet> rs(statement->executeQuery(query));
    if (rs->next())
    {
      value= rs->getString(2);
      return true;
    }
  }
  return false;
}


void SqlEditorForm::cache_sql_mode()
{
  std::string sql_mode;
  if (get_session_variable(_usr_dbc_conn, "sql_mode", sql_mode))
  {
    if (sql_mode != _sql_mode)
    {
      _sql_mode= sql_mode;
      GMutexLock sql_editors_mutex(_sql_editors_mutex);
      BOOST_FOREACH (Sql_editor_info::Ref sql_editor_info, _sql_editors)
        sql_editor_info->editor->sql_mode(sql_mode);
    }
  }
}


int SqlEditorForm::run_sql_in_scratch_tab(const std::string &sql, bool reuse_if_possible, bool start_collapsed)
{
  if (_active_sql_editor_index < 0 || !reuse_if_possible || !sql_editor_is_scratch(_active_sql_editor_index))
    new_sql_scratch_area(start_collapsed);
  set_sql_editor_text(sql);
  run_sql();
  sql_editor_dirty(_active_sql_editor_index, false);
  
  return _active_sql_editor_index;
}


void SqlEditorForm::reset()
{
  //_log->reset();
}


RowId SqlEditorForm::add_log_message(int msg_type, const std::string &msg, const std::string &context, const std::string &duration)
{
  RowId new_log_message_index= _log->add_message(msg_type, context, msg, duration);
  _has_pending_log_messages= true;
  refresh_log_messages(false);
  if (msg_type == DbSqlEditorLog::ErrorMsg || msg_type == DbSqlEditorLog::WarningMsg)
    _exec_sql_error_count++;
  return new_log_message_index;
}


void SqlEditorForm::set_log_message(RowId log_message_index, int msg_type, const std::string &msg, const std::string &context, const std::string &duration)
{
  if (log_message_index != (RowId)-1)
  {
    _log->set_message(log_message_index, msg_type, context, msg, duration);
    _has_pending_log_messages= true;
    if (msg_type == DbSqlEditorLog::ErrorMsg || msg_type == DbSqlEditorLog::WarningMsg)
      _exec_sql_error_count++;
    bool force_refresh = true;
    if (msg_type == DbSqlEditorLog::BusyMsg)
      force_refresh = false;
    refresh_log_messages(force_refresh);
  }
}


void SqlEditorForm::refresh_log_messages(bool ignore_last_message_timestamp)
{
  if (_has_pending_log_messages)
  {
    bool is_refresh_needed= ignore_last_message_timestamp;
    if (!ignore_last_message_timestamp)
    {
      time_t now= timestamp();
      if (_last_log_message_timestamp + _progress_status_update_interval < now)
        is_refresh_needed= true;
      _last_log_message_timestamp= now;
    }
    if (is_refresh_needed)
    {
      exec_sql_task->send_progress(0.f, std::string(), std::string());
      _has_pending_log_messages= false;
    }
  }
}


void SqlEditorForm::get_log_event_details(int log_event_no, int &log_event_type_code, std::string &log_event_time, std::string &log_event_action, std::string &log_event_message)
{
  NodeId node(log_event_no);
  _log->get_field(node, 0, log_event_type_code);
  _log->get_field(node, 2, log_event_time);
  _log->get_field(node, 3, log_event_action);
  _log->get_field(node, 4, log_event_message);
}

std::string SqlEditorForm::get_text_for_actions(const std::list<int> &indexes, bool query_only)
{
  std::string sql;
  
  for (std::list<int>::const_iterator end = indexes.end(), it = indexes.begin(); it != end; ++it)
  {
    std::string s;
    
    if (!sql.empty())
      sql.append("\n");
    
    if (query_only)
    {
      _log->get_field(*it, 3, s);
      sql.append(s).append("\n");
    }
    else
    {
      _log->get_field(*it, 0, s);
      sql.append(s).append("\t");
      _log->get_field(*it, 2, s);
      sql.append(s).append("\t");
      _log->get_field(*it, 3, s);
      sql.append(s).append("\t");
      _log->get_field(*it, 4, s);
      sql.append(s).append("\n");
    }
  }
  return sql;
}

int SqlEditorForm::recordset_count(int editor)
{
  if (editor >= 0 && editor < (int)_sql_editors.size())
    return sql_editor_recordsets(editor)->size();
  return 0;
}


Recordset::Ref SqlEditorForm::recordset(int editor, int new_index)
{
  if (editor >= 0 && editor < (int)_sql_editors.size())
    return sql_editor_recordsets(editor)->at(new_index);
  return Recordset::Ref();
}


void SqlEditorForm::init_connection(sql::Connection* dbc_conn_ref, const db_mgmt_ConnectionRef& connectionProperties, sql::Dbc_connection_handler::Ref& dbc_conn)
{
  db_mgmt_RdbmsRef rdbms= db_mgmt_RdbmsRef::cast_from(_connection->driver()->owner());
  SqlFacade::Ref sql_facade= SqlFacade::instance_for_rdbms(rdbms);
  Sql_specifics::Ref sql_specifics= sql_facade->sqlSpecifics();

  // connection startup script
  {
    std::list<std::string> sql_script;
    {
      sql_specifics->get_connection_startup_script(sql_script);
      bool use_ansi_quotes= (connectionProperties->parameterValues().get_int("useAnsiQuotes", 0) != 0);
      if (use_ansi_quotes)
      {
        std::string sql= sql_specifics->setting_ansi_quotes();
        if (!sql.empty())
          sql_script.push_back(sql);
      }
    }
    
    // check if SQL_SAFE_UPDATES should be enabled
    if (_options.get_int("DbSqlEditor:SafeUpdates", 1))
      sql_script.push_back("SET SQL_SAFE_UPDATES=1");
    
    std::auto_ptr<sql::Statement> stmt(dbc_conn_ref->createStatement());
    sql::SqlBatchExec sql_batch_exec;
    sql_batch_exec(stmt.get(), sql_script);
  }

  // remember connection id
  {
    std::string query_connection_id= sql_specifics->query_connection_id();
    if (!query_connection_id.empty())
    {
      std::auto_ptr<sql::Statement> stmt(dbc_conn_ref->createStatement());
      stmt->execute(query_connection_id);
      boost::shared_ptr<sql::ResultSet> rs(stmt->getResultSet());
      rs->next();
      dbc_conn->id= rs->getInt(1);
    }
  }
}


void SqlEditorForm::create_connection(sql::Dbc_connection_handler::Ref &dbc_conn, db_mgmt_ConnectionRef db_mgmt_conn, 
                                      sql::AuthenticationSet *authset, bool autocommit_mode)
{
  dbc_conn->is_stop_query_requested= false;

  sql::DriverManager *dbc_drv_man= sql::DriverManager::getDriverManager();

  db_mgmt_ConnectionRef temp_connection = db_mgmt_ConnectionRef::cast_from(CopyContext(db_mgmt_conn.get_grt()).copy(db_mgmt_conn));

  int read_timeout = _options.get_int("DbSqlEditor:ReadTimeOut");
  if (read_timeout > 0)
    temp_connection->parameterValues().set("OPT_READ_TIMEOUT", grt::IntegerRef(read_timeout));

  // we could either always pick last used schema or only pick last used schema if the connection doesn't have a default schema
  // since customer from bug #11758776 wants the 1st, we do that for now...
  //if (temp_connection->parameterValues().get_string("schema") == "")
  // don't override the default schema used to connect, because the schema may have been deleted and the connection will fail
  // further, the schema can't be changed from the connection editor. Default schema is set once connected
//  temp_connection->parameterValues().gset("schema", temp_connection->parameterValues().get_string("DbSqlEditor:LastDefaultSchema"));
  
  if (!authset)
    dbc_conn->ref= dbc_drv_man->getConnection(temp_connection, 
                                              boost::bind(&SqlEditorForm::init_connection, this, _1, _2, dbc_conn));
  else
    dbc_conn->ref= dbc_drv_man->getConnection(temp_connection, *authset,
                                            boost::bind(&SqlEditorForm::init_connection, this, _1, _2, dbc_conn));
  //! dbms-specific code
  if (dbc_conn->ref->getMetaData()->getDatabaseMajorVersion() < 5)
  {
    throw std::runtime_error("MySQL Server version is older than 5.0, which is not supported");
  }

  if (dbc_conn->active_schema.empty())
  {
    std::string last_default = temp_connection->parameterValues().get_string("DbSqlEditor:LastDefaultSchema");
    try
    {
      if (!last_default.empty())
        dbc_conn->ref->setSchema(last_default);
    }
    catch (std::exception &exc)
    {
      log_error("Can't restore LastDefaultSchema (%s): %s", last_default.c_str(), exc.what());
      temp_connection->parameterValues().gset("DbSqlEditor:LastDefaultSchema", "");
    }
 
    dbc_conn->active_schema= dbc_conn->ref->getSchema();
  }
  else
    dbc_conn->ref->setSchema(dbc_conn->active_schema);
  dbc_conn->ref->setAutoCommit(autocommit_mode);
  dbc_conn->autocommit_mode= dbc_conn->ref->getAutoCommit();
}


void SqlEditorForm::connect()
{
  sql::AuthenticationSet authset;

  for (;;)
  {
    // if an auth error happens in the worker thread, this ptr will be set
    sql::AuthenticationError *auth_error_ptr = 0;
    
    // connection must happen in the worker thread
    try
    {
      exec_sql_task->exec(true, boost::bind(&SqlEditorForm::do_connect, this, _1, authset, boost::ref(auth_error_ptr)));
    }
    catch (grt::grt_runtime_error)
    {
      if (!auth_error_ptr)
        throw;
      if (auth_error_ptr->authentication() && !auth_error_ptr->authentication()->is_valid())
        authset.insert(auth_error_ptr->authentication());
      else
        throw;
      delete auth_error_ptr;
      
      for (sql::AuthenticationSet::iterator auth = authset.begin(); auth != authset.end(); ++auth)
      {
        if (!(*auth)->is_valid())
        {
          std::string pwd = sql::DriverManager::getDriverManager()->requestPassword((*auth)->connectionProperties(), true);
          (*auth)->set_password(pwd.c_str());
          break;
        }
      }
      continue;
    }
    break;
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Little helper to create a single html line used for info output.
 */
std::string create_html_line(const std::string& name, const std::string& value)
{
  return "<div style=\"padding-left: 15px\"><span style=\"color: #717171\">" + name + "</span> <i>" +
    value + "</i></div>";
}

//--------------------------------------------------------------------------------------------------

grt::StringRef SqlEditorForm::do_connect(grt::GRT *grt, sql::AuthenticationSet &authset, sql::AuthenticationError *&autherr_ptr)
{
  try
  {
    GMutexLock aux_dbc_conn_mutex(_aux_dbc_conn_mutex);
    GMutexLock usr_dbc_conn_mutex(_usr_dbc_conn_mutex);

    reset();

    _aux_dbc_conn->ref.reset();
    _usr_dbc_conn->ref.reset();
    create_connection(_aux_dbc_conn, _connection, &authset, _aux_dbc_conn->autocommit_mode);
    create_connection(_usr_dbc_conn, _connection, &authset, _usr_dbc_conn->autocommit_mode);

    cache_sql_mode();

    // connection info
    try
    {
      _connection_details["name"] = _connection->name();
      _connection_details["hostName"] = _connection->parameterValues().get_string("hostName");
      _connection_details["port"] = strfmt("%li\n", _connection->parameterValues().get_int("port"));
      _connection_details["socket"] = _connection->parameterValues().get_string("socket");
      _connection_details["driverName"] = _connection->driver()->name();
      _connection_details["dbmsProductName"] = _usr_dbc_conn->ref->getMetaData()->getDatabaseProductName();
      _connection_details["dbmsProductVersion"] = _usr_dbc_conn->ref->getMetaData()->getDatabaseProductVersion();
      _connection_details["userName"] = _connection->parameterValues().get_string("userName");
  
      // Connection:
      _connection_info= "<div style=\"color=#3b3b3b; font-weight:bold\">Connection:</div>";
      _connection_info.append(create_html_line("Name: ", _connection->name()));
      // Host:
      if (_connection->driver()->name() == "MysqlNativeSocket")
      {
#ifdef _WIN32
        std::string name = _connection->parameterValues().get_string("socket", "");
        if (name.empty())
          name = "pipe";
#else
        std::string name = _connection->parameterValues().get_string("socket", "");
        if (name.empty())
          name = "UNIX socket";
#endif
        _connection_info.append(create_html_line("Host:", "localhost (" + name + ")"));
      }
      else
      {
        _connection_info.append(create_html_line("Host:", _connection->parameterValues().get_string("hostName")));
        _connection_info.append(create_html_line("Port:", strfmt("%i", (int)_connection->parameterValues().get_int("port"))));
      }

      // Server:
      _connection_info.append(create_html_line("Server:", _connection_details["dbmsProductName"]));
      _connection_info.append(create_html_line("Version:",  _connection_details["dbmsProductVersion"]));
      // User:
      _connection_info.append(create_html_line("Login User:", _connection->parameterValues().get_string("userName")));
      
      // check the actual user we're logged in as
      if (_usr_dbc_conn && _usr_dbc_conn->ref.get())
      {
        boost::scoped_ptr<sql::Statement> statement(_usr_dbc_conn->ref->createStatement());
        boost::scoped_ptr<sql::ResultSet> rs(statement->executeQuery("SELECT current_user()"));
        if (rs->next())
          _connection_info.append(create_html_line("Current User:", rs->getString(1)));
      }
    }
    CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Get connection information"));
  }
  catch (sql::AuthenticationError &exc)
  {
    autherr_ptr = new sql::AuthenticationError(exc);
    throw;
  }

// not sure why this was making a copy of rdbms
//  db_mgmt_RdbmsRef rdbms= db_mgmt_RdbmsRef::cast_from(grt::copy_object(_grtm->get_grt(), this->rdbms()));
  db_mgmt_RdbmsRef rdbms= this->rdbms();
  std::string database_package= *rdbms->databaseObjectPackage();

  workbench_physical_ModelRef model(_grtm->get_grt());
  rdbms->owner(model);
  model->rdbms(rdbms);
  db_CatalogRef catalog= _grtm->get_grt()->create_object<db_Catalog>(database_package + ".Catalog");
  {
    catalog->owner(model);
    catalog->name("default");
    catalog->oldName("default");
    catalog->version(rdbms->version());
    grt::replace_contents(catalog->simpleDatatypes(), rdbms->simpleDatatypes());

    // setup list of synonyms
    grt::Module *module= _grtm->get_grt()->get_module("DbMySQL");
    if (module)
    {
      grt::BaseListRef args(_grtm->get_grt());
      args.ginsert(rdbms);
      grt::ListRef<db_UserDatatype> user_types(grt::ListRef<db_UserDatatype>::cast_from(module->call_function("getDefaultUserDatatypes", args)));
      
      if (user_types.is_valid())
      {
        GRTLIST_FOREACH(db_UserDatatype, user_types, ut)
        {
          (*ut)->owner(catalog);
          catalog->userDatatypes().insert(*ut);
        }
      }
    }    
    
    model->catalog(catalog);
  }
   
  return grt::StringRef();
}


bool SqlEditorForm::connected() const
{
  return (_usr_dbc_conn && _usr_dbc_conn->ref.get_ptr() && !_usr_dbc_conn->ref->isClosed());
}


GMutexLock SqlEditorForm::ensure_valid_aux_connection()
{
  return ensure_valid_dbc_connection(_aux_dbc_conn, _aux_dbc_conn_mutex);
}


GMutexLock SqlEditorForm::ensure_valid_usr_connection()
{
  return ensure_valid_dbc_connection(_usr_dbc_conn, _usr_dbc_conn_mutex);
}


void SqlEditorForm::close_connection(sql::Dbc_connection_handler::Ref &dbc_conn)
{
  if (dbc_conn && dbc_conn->ref.get_ptr())
  {
    try
    {
      dbc_conn->ref->close();
    }
    catch (sql::SQLException &)
    {
      // ignore if the connection is already closed
    }
  }
}


GMutexLock SqlEditorForm::ensure_valid_dbc_connection(sql::Dbc_connection_handler::Ref &dbc_conn, GMutex *dbc_conn_mutex)
{
  GMutexLock mutex_lock(dbc_conn_mutex);
  bool valid= false;
  if (dbc_conn && dbc_conn->ref.get_ptr())
  {
    if (dbc_conn->ref->isClosed())
    {
      if (dbc_conn->autocommit_mode)
      {
        sql::AuthenticationSet authset;
        create_connection(dbc_conn, _connection, 0, dbc_conn->autocommit_mode);
        if (!dbc_conn->ref->isClosed())
          valid= true;
      }
    }
    else
      valid= true;
  }
  if (!valid)
    throw std::runtime_error("DBMS connection is not available");

  return mutex_lock;
}


bool SqlEditorForm::auto_commit()
{
  if (_usr_dbc_conn)
    return _usr_dbc_conn->autocommit_mode;
  return false;
}


void SqlEditorForm::auto_commit(bool value)
{
  if (!_usr_dbc_conn)
    return;
  {
    const char *STATEMENT= value ? "AUTOCOMMIT=1" : "AUTOCOMMIT=0";
    try
    {
      GMutexLock usr_dbc_conn_mutex= ensure_valid_usr_connection();
      _usr_dbc_conn->ref->setAutoCommit(value);
      _usr_dbc_conn->autocommit_mode= _usr_dbc_conn->ref->getAutoCommit();
    }
    CATCH_ANY_EXCEPTION_AND_DISPATCH(STATEMENT)
  }
  update_menu_and_toolbar();
}


void SqlEditorForm::toggle_autocommit()
{
  auto_commit(!auto_commit());
  update_menu_and_toolbar();
}


void SqlEditorForm::cancel_query()
{
  std::string query_kill_query;
  {
    db_mgmt_RdbmsRef rdbms= db_mgmt_RdbmsRef::cast_from(_connection->driver()->owner());
    SqlFacade::Ref sql_facade= SqlFacade::instance_for_rdbms(rdbms);
    Sql_specifics::Ref sql_specifics= sql_facade->sqlSpecifics();
    query_kill_query= sql_specifics->query_kill_query(_usr_dbc_conn->id);
  }
  if (query_kill_query.empty())
    return;

  const char *STATEMENT= "INTERRUPT";
  RowId log_message_index= add_log_message(DbSqlEditorLog::BusyMsg, _("Running..."), STATEMENT, "");
  Timer timer(false);

  try
  {
    {
      GMutexLock aux_dbc_conn_mutex= ensure_valid_aux_connection();
      std::auto_ptr<sql::Statement> stmt(_aux_dbc_conn->ref->createStatement());
      {
        ScopeExitTrigger schedule_timer_stop(boost::bind(&Timer::stop, &timer));
        timer.run();
        stmt->execute(query_kill_query);
        
        // this can potentially cause threading issues, since connector driver isn't thread-safe
        //close_connection(_usr_dbc_conn);

        // connection drop doesn't interrupt fetching stage (surprisingly)
        // to workaround that we set special flag and check it periodically during fetching
        _usr_dbc_conn->is_stop_query_requested= is_running_query();
      }
    }

    if (_usr_dbc_conn->is_stop_query_requested)
    {
      _grtm->replace_status_text("Query Cancelled");
      set_log_message(log_message_index, DbSqlEditorLog::NoteMsg, _("OK - Query cancelled"), STATEMENT, timer.duration_formatted());
    }
    else
      set_log_message(log_message_index, DbSqlEditorLog::NoteMsg, _("OK - Query already completed"), STATEMENT, timer.duration_formatted());

    // reconnect but only if in autocommit mode
    if (_usr_dbc_conn->autocommit_mode)
    {
      // this will restore connection if it was established previously
      exec_sql_task->execute_in_main_thread(
        boost::bind(&SqlEditorForm::send_message_keep_alive, this),
        false,
        true);
    }
  }
  CATCH_SQL_EXCEPTION_AND_DISPATCH(STATEMENT, log_message_index, "")
}


void SqlEditorForm::commit()
{
  exec_sql_retaining_editor_contents("COMMIT", Sql_editor::Ref(), false);
}


void SqlEditorForm::rollback()
{
  exec_sql_retaining_editor_contents("ROLLBACK", Sql_editor::Ref(), false);
}


void SqlEditorForm::explain_sql()
{
  int start, end;
  Sql_editor::Ref sql_editor_= active_sql_editor();
  if (sql_editor_)
  {
    sql_editor_->selected_range(start, end);
    std::string sql= sql_editor_->sql();
    if (start != end)
      sql= sql.substr(start, end-start);

    do_explain_sql(sql);
  }
}


void SqlEditorForm::explain_current_statement()
{
  Sql_editor::Ref sql_editor_= active_sql_editor();
  if (sql_editor_)
    do_explain_sql(sql_editor_->current_statement());
}


void SqlEditorForm::do_explain_sql(const std::string &sql)
{
  SqlFacade::Ref sql_facade= SqlFacade::instance_for_rdbms(rdbms());
  std::list<std::string> statements;
  sql_facade->splitSqlScript(sql, statements);
  Sql_syntax_check::Ref sql_syntax_check= sql_facade->sqlSyntaxCheck();
  std::string sql_script;
  for (std::list<std::string>::iterator i= statements.begin(), i_end= statements.end(); i != i_end; ++i)
    if (Sql_syntax_check::sql_select == sql_syntax_check->determine_statement_type(*i))
      sql_script+= "EXPLAIN " + *i + ";\n";

  exec_sql_retaining_editor_contents(sql_script, active_sql_editor(), false);
}


void SqlEditorForm::exec_sql_retaining_editor_contents(const std::string &sql_script, Sql_editor::Ref editor, bool sync, bool dont_add_limit_clause)
{
  auto_save();
  
  RecordsetsRef recordsets;
  int i = sql_editor_index(editor);
  if (i >= 0)
    recordsets = sql_editor_recordsets(i);
  else
    recordsets = RecordsetsRef(new Recordsets());  
  
  exec_sql_task->exec(sync,
    boost::bind(&SqlEditorForm::do_exec_sql, this, _1,
               weak_ptr_from(this), sql_script, Sql_editor::Ref(),
               (ExecFlags)(Retaining | (dont_add_limit_clause?Dont_add_limit_clause:0)),
               recordsets));
}



void SqlEditorForm::run_sql()
{
  do_partial_ui_refresh(SqlEditorForm::RunCurrentScript);
}



RecordsetsRef SqlEditorForm::exec_sql_returning_results(const std::string &sql_script, bool dont_add_limit_clause)
{
  RecordsetsRef rsets(new Recordsets());
  std::string sql(sql_script);
  
  do_exec_sql(_grtm->get_grt(), weak_ptr_from(this), sql, Sql_editor::Ref(), 
              (ExecFlags)(dont_add_limit_clause?Dont_add_limit_clause:0),
              rsets);
  
  return rsets;
}

void SqlEditorForm::exec_sql(std::string &sql, Sql_editor::Ref editor, bool sync, bool wrap_with_non_std_delimiter, bool dont_add_limit_clause)
{
  ExecFlags flags = (ExecFlags)0;
  
  if (wrap_with_non_std_delimiter)
    flags = (ExecFlags)(flags | Wrap_with_non_std_delimiter);
  if (dont_add_limit_clause)
    flags = (ExecFlags)(flags | Dont_add_limit_clause);
  if (_options.get_int("DbSqlEditor:ShowWarnings", 1))
    flags = (ExecFlags)(flags | Show_warnings);
  auto_save();
  
  RecordsetsRef recordsets;

  int i = sql_editor_index(editor);
  if (i >= 0)
    recordsets = sql_editor_recordsets(i);
  else
    recordsets = RecordsetsRef(new Recordsets());

  exec_sql_task->exec(sync,
    boost::bind(&SqlEditorForm::do_exec_sql, this, _1,
               weak_ptr_from(this), sql, editor, 
               flags,
               recordsets));
}

struct GuardBoolFlag
{
  bool *flag;
  GuardBoolFlag(bool *ptr) : flag(ptr) { if (flag) *flag = true; }
  ~GuardBoolFlag() { if (flag) *flag = false; }
};

grt::StringRef SqlEditorForm::do_exec_sql(grt::GRT *grt, Ptr self_ptr, std::string &sql, Sql_editor::Ref editor, ExecFlags flags, RecordsetsRef result_list)
{
  bool retaining = (flags & Retaining) != 0;
  bool wrap_with_non_std_delimiter = (flags & Wrap_with_non_std_delimiter) != 0;
  bool dont_add_limit_clause = (flags & Dont_add_limit_clause) != 0;

  int max_query_size_to_log = _options.get_int("DbSqlEditor:MaxQuerySizeToHistory", 0);
  
  _grtm->replace_status_text(_("Executing Query..."));

  RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR (SqlEditorForm, self_ptr, self, grt::StringRef(""))

  // add_log_message() will increment this variable on errors or warnings
  _exec_sql_error_count = 0;

  GMutex *result_list_mutex = NULL;
  sql::Driver *dbc_driver= NULL;
  std::string statement;
  int editor_index = -1;
  try
  {/*
    if (!retaining)
    {
      if (!_recordsets_are_pinned_by_default)
      {
        GMutexLock recordsets_mutex(_recordsets_mutex);
        Recordsets recordsets;
        recordsets.reserve(result_list->size());
        BOOST_FOREACH (Recordset::Ref rs, *result_list)
        {
          if (rs->pinned() || !rs->can_close(false))
            recordsets.push_back(rs);
          else
            recordset_list_changed.emit(rs, false);
        }
        result_list->swap(recordsets);        
      }
    }*/
    
    int default_seq = 0;
    int *rs_sequence = &default_seq;
    bool *busy_flag = 0;

    if (editor && !retaining)
    {
      Sql_editor_info::Ref info;
      editor_index = 0;
      for (Sql_editors::iterator info_ = _sql_editors.begin(); info_ != _sql_editors.end(); ++info_, ++editor_index)
      {
        if ((*info_)->editor == editor)
        {
          info = *info_;
          rs_sequence = &info->rs_sequence;
          busy_flag = &info->busy;
          break;
        }
      }
      
      GuardBoolFlag guard_busy_flag(busy_flag);

      // close all recordsets for the same editor
      if (info)
      {
        GMutexTryLock recordsets_mutex(info->recordset_mutex);
        if (!recordsets_mutex.locked())
          throw std::runtime_error("The editor is busy and cannot execute the query now. Please try again later.");
            
        RecordsetsRef recordsets(new Recordsets());
        recordsets->reserve(result_list->size());
        int index = result_list->size();
        while (--index >= 0)
        {
          if (!(*result_list)[index]->can_close(false))
            recordsets->push_back((*result_list)[index]);
          else
          {
            // This must perform the same as on_close_recordset(), but without the redundant mutex locking.
            RecordsetsRef rsets = info->recordsets;
            Recordset::Ref current_recordset = (*result_list)[index];
            Recordsets::iterator iter = std::find(rsets->begin(), rsets->end(), current_recordset);
            if (iter != rsets->end())
            {
              if (info->active_recordset == *iter)
                info->active_recordset.reset();
              rsets->erase(iter);
            }
            if (editor_index >= 0)
              recordset_list_changed(editor_index, current_recordset, false);
          }
        }
        result_list->swap(*recordsets.get());
        result_list_mutex = info->recordset_mutex;
      }
    }

    GMutexLock use_dbc_conn_mutex= ensure_valid_usr_connection();

    GuardBoolFlag guard_busy_flag(busy_flag);
    
    dbc_driver= _usr_dbc_conn->ref->getDriver();
    dbc_driver->threadInit();

    bool is_running_query= true;
    AutoSwap<bool> is_running_query_keeper(_is_running_query, is_running_query);
    update_menu_and_toolbar();

    _has_pending_log_messages= false;
    bec::TimerActionThread *log_messages_refresh_thread= TimerActionThread::create(
      boost::bind(&SqlEditorForm::refresh_log_messages, this, true),
      _progress_status_update_interval);
    ScopeExitTrigger schedule_log_messages_refresh_thread_stop(boost::bind(
      &bec::TimerActionThread::stop, log_messages_refresh_thread, false));
    ScopeExitTrigger schedule_log_messages_refresh(boost::bind(
      &SqlEditorForm::refresh_log_messages, this, true));

    SqlFacade::Ref sql_facade= SqlFacade::instance_for_rdbms(rdbms());
    Sql_syntax_check::Ref sql_syntax_check= sql_facade->sqlSyntaxCheck();
    Sql_specifics::Ref sql_specifics= sql_facade->sqlSpecifics();

    if (wrap_with_non_std_delimiter)
      sql= sql_specifics->setting_non_std_sql_delimiter() + sql;

    bool ran_set_sql_mode = false;
    //XXX this will create a copy of the whole script, should work with ranges instead
    std::list<std::string> statements;
    sql_facade->splitSqlScript(sql, statements);

    if (!max_query_size_to_log || max_query_size_to_log >= (int)sql.size() )
      _history->add_entry(statements);

    //std::list<Recordset::Ref> added_recordsets;
    BOOST_FOREACH (statement, statements)
    {
      std::list<std::string> sub_statements;
      sql_facade->splitSqlScript(statement, sub_statements);
      int multiple_statement_count= sub_statements.size();
      bool is_multiple_statement= (1 < multiple_statement_count);
      {
        statement= strip_text(statement, false, true);
        if (statement.empty())
          continue;

        Sql_syntax_check::Statement_type statement_type= sql_syntax_check->determine_statement_type(statement);
        if (Sql_syntax_check::sql_empty == statement_type)
          continue;

        std::string schema_name;
        std::string table_name;
        std::string editable_query_tail;
        if (!is_multiple_statement && (Sql_syntax_check::sql_edit == statement_type))
        {
          // to open table contents in edit mode the following syntax is used: EDIT [SCHEMA.]TABLE ...
          // this type of query needs special preprocessing: rewriting using 'select' statement
          statement_type= Sql_syntax_check::sql_select;
          sql_syntax_check->parse_edit_statement(statement, schema_name, table_name, editable_query_tail);
          if (schema_name.empty())
            schema_name= active_schema();
          if (schema_name.empty())
            schema_name= _usr_dbc_conn->ref->getSchema();
        }

        Recordset_cdbc_storage::Ref data_storage;

        // for select queries add limit clause if specified by global option
        if (!is_multiple_statement && (Sql_syntax_check::sql_select == statement_type))
        {
          data_storage= Recordset_cdbc_storage::create(_grtm);
          data_storage->rdbms(rdbms());
          data_storage->dbms_conn(_usr_dbc_conn);
          
          std::string error = "";
          
          if(!table_name.empty() || is_editable_select(statement, schema_name, table_name, error))
          {
            data_storage->schema_name(schema_name);
            data_storage->table_name(table_name);
          }
          else
            data_storage->readonly_reason(error);

          data_storage->sql_query(statement);

          data_storage->additional_clauses(editable_query_tail);
          {
            bool limit_rows= !dont_add_limit_clause && (0 != _options.get_int("SqlEditor:LimitRows", 0));
            data_storage->limit_rows(limit_rows);

            int limit_rows_count= _options.get_int("SqlEditor:LimitRowsCount");
            if (0 != limit_rows_count)
              data_storage->limit_rows_count(limit_rows_count);
          }
          statement= data_storage->decorated_sql_query();
        }

        {
          RowId log_message_index= add_log_message(DbSqlEditorLog::BusyMsg, _("Running..."), statement,
            ((Sql_syntax_check::sql_select == statement_type) ? "? / ?" : "?"));

          bool statement_failed= false;
          long long updated_rows_count= -1;
          Timer statement_exec_timer(false);
          Timer statement_fetch_timer(false);
          boost::shared_ptr<sql::Statement> dbc_statement(_usr_dbc_conn->ref->createStatement());
          bool is_result_set_first= false;

          if (_usr_dbc_conn->is_stop_query_requested)
            throw std::runtime_error(_("Query execution has been stopped, the connection to the DB server was not restarted, any open transaction remains open"));

          try
          {
            {
              ScopeExitTrigger schedule_statement_exec_timer_stop(boost::bind(&Timer::stop, &statement_exec_timer));
              statement_exec_timer.run();
              is_result_set_first= dbc_statement->execute(statement);
            }
            updated_rows_count= dbc_statement->getUpdateCount();
            if (Sql_syntax_check::sql_use == statement_type)
              cache_active_schema_name();
            if (Sql_syntax_check::sql_set == statement_type && statement.find("@sql_mode") != std::string::npos)
              ran_set_sql_mode= true;
          }
          catch (sql::SQLException &e)
          {
            std::string err_msg;
            // safe mode
            switch (e.getErrorCode())
            {
              case 1046: // not default DB selected
                err_msg= strfmt(_("Error Code: %i. %s\nSelect the default DB to be used by double-clicking its name in the SCHEMAS list in the sidebar."), e.getErrorCode(), e.what());
                break;
              case 1175: // safe mode
                err_msg= strfmt(_("Error Code: %i. %s\nTo disable safe mode, toggle the option in Preferences -> SQL Editor -> Query Editor and reconnect."), e.getErrorCode(), e.what());
                break;
              default:
                err_msg= strfmt(_("Error Code: %i. %s"), e.getErrorCode(), e.what());
                break;
            }
            set_log_message(log_message_index, DbSqlEditorLog::ErrorMsg, err_msg, statement, statement_exec_timer.duration_formatted());
            statement_failed= true;
          }
          catch (std::exception &e)
          {
            std::string err_msg= strfmt(_("Error: %s"), e.what());
            set_log_message(log_message_index, DbSqlEditorLog::ErrorMsg, err_msg, statement, statement_exec_timer.duration_formatted());
            statement_failed= true;
          }
          if (statement_failed)
          {
            if (_continue_on_error)
              continue; // goto next statement
            else
              goto stop_processing_sql_script;
          }

          sql::mysql::MySQL_Connection* mysql_connection = dynamic_cast<sql::mysql::MySQL_Connection*>(dbc_statement->getConnection());
          sql::SQLString last_statement_info;
          if (mysql_connection != NULL)
            last_statement_info = mysql_connection->getLastStatementInfo();
          if (updated_rows_count >= 0)
          {
            std::string message = strfmt(_("%lli row(s) affected"), updated_rows_count);
            bool has_warning = false;
            if (flags & Show_warnings)
            {
              std::string warnings_message;
              const sql::SQLWarning *warnings= dbc_statement->getWarnings();
              if (warnings)
              {
                int count= 0;
                const sql::SQLWarning *w = warnings;
                while (w) 
                {
                  warnings_message.append(strfmt("\n%i %s", w->getErrorCode(), w->getMessage().c_str()));
                  count++; 
                  w= w->getNextWarning();
                }
                message.append(strfmt(_(", %i warning(s):"), count));
                has_warning = true;
              }
              if (!warnings_message.empty())
                message.append(warnings_message);
            }
            if (!last_statement_info->empty())
              message.append("\n").append(last_statement_info);
            set_log_message(log_message_index, has_warning ? DbSqlEditorLog::WarningMsg : DbSqlEditorLog::OKMsg, message, statement, statement_exec_timer.duration_formatted());            
          }

          int resultset_count= 0;
          bool more_results= is_result_set_first;
          bool reuse_log_msg= false;
          if ((updated_rows_count < 0) || is_multiple_statement)
          {
            for (int processed_substatements_count= 0; processed_substatements_count < multiple_statement_count; ++processed_substatements_count)
            {
              do
              {
                if (more_results)
                {
                  if (!reuse_log_msg && ((updated_rows_count >= 0) || (resultset_count)))
                    log_message_index= add_log_message(DbSqlEditorLog::BusyMsg, _("Fetching..."), statement, "- / ?");
                  else
                    set_log_message(log_message_index, DbSqlEditorLog::BusyMsg, _("Fetching..."), statement, statement_exec_timer.duration_formatted() + " / ?");
                  reuse_log_msg= false;
                  boost::shared_ptr<sql::ResultSet> dbc_resultset;
                  {
                    ScopeExitTrigger schedule_statement_fetch_timer_stop(boost::bind(&Timer::stop, &statement_fetch_timer));
                    statement_fetch_timer.run();
                    dbc_resultset.reset(dbc_statement->getResultSet());
                  }
                  if (dbc_resultset)
                  {
                    if (!data_storage)
                    {
                      data_storage= Recordset_cdbc_storage::create(_grtm);
                      data_storage->rdbms(rdbms());
                      data_storage->dbms_conn(_usr_dbc_conn);
                      if (table_name.empty())
                        data_storage->sql_query(statement);
                      data_storage->schema_name(schema_name);
                      data_storage->table_name(table_name);
                    }

                    data_storage->dbc_statement(dbc_statement);
                    data_storage->dbc_resultset(dbc_resultset);
                    data_storage->reloadable(!is_multiple_statement && (Sql_syntax_check::sql_select == statement_type));

                    Recordset::Ref rs= Recordset::create(exec_sql_task);
                    rs->is_field_value_truncation_enabled(true);
                    rs->apply_changes= boost::bind(&SqlEditorForm::apply_changes_to_recordset, this, Recordset::Ptr(rs));
                    rs->on_close.connect(boost::bind(&SqlEditorForm::on_close_recordset, this, _1));
                    rs->caption(strfmt("%s %i",
                                       (table_name.empty() ? _("Result") : table_name.c_str()),
                                       ++*rs_sequence));
                    rs->generator_query(statement);
                    rs->action_list().register_action("recall_query",
                      boost::bind(&SqlEditorForm::recall_recordset_query, this, Recordset::Ptr(rs)));

                    // XXX: refresh recordset title on status bar change? Huh?
                    scoped_connect(&rs->refresh_ui_status_bar_signal,
                      boost::bind(&bec::RefreshUI::do_partial_ui_refresh, this, (int)RefreshRecordsetTitle));

                    rs->data_storage(data_storage);
                    rs->export_wizard= boost::bind(&SqlEditorForm::show_export_recordset, this, sql_editor_index(editor), Recordset::Ptr(rs));
                    rs->popup_menu_items_slot= boost::bind(&SqlEditorForm::recordset_popup_menu_items, this, _1, _2, Recordset::Ptr(rs));
                    rs->call_popup_menu_item_slot= boost::bind(&SqlEditorForm::call_recordset_popup_menu_item, this, _1, _2, _3, Recordset::Ptr(rs));
                    rs->reset(true);
                    if (data_storage->valid()) // query statement
                    {
                      {
                        GMutexLock recordsets_mutex(result_list_mutex);
                        result_list->push_back(rs);
                      }
                      if (editor_index >= 0)
                        recordset_list_changed(editor_index, rs, true);
                      std::string statement_res_msg= strfmt(_("%i row(s) returned"), rs->row_count());
                      if (!last_statement_info->empty())
                        statement_res_msg.append("\n").append(last_statement_info);
                      std::string exec_and_fetch_durations=
                        (((updated_rows_count >= 0) || (resultset_count)) ? std::string("-") : statement_exec_timer.duration_formatted()) + " / " +
                        statement_fetch_timer.duration_formatted();
                      set_log_message(log_message_index, DbSqlEditorLog::OKMsg, statement_res_msg, statement, exec_and_fetch_durations);
                    }
                    //! else failed to fetch data
                    //added_recordsets.push_back(rs);
                    ++resultset_count;
                  }
                  else
                  {
                    reuse_log_msg= true;
                  }
                  data_storage.reset();
                }
              }
              while ((more_results= dbc_statement->getMoreResults()));
            }
          }
          
          if ((updated_rows_count < 0) && !(resultset_count))
          {
            set_log_message(log_message_index, DbSqlEditorLog::OKMsg, _("OK"), statement, statement_exec_timer.duration_formatted());
          }
        }
      }
    } // BOOST_FOREACH (statement, statements)

    _grtm->replace_status_text(_("Query Completed"));

stop_processing_sql_script:
    // try to minimize the times this is called, since this will change the state of the connection
    // after a user query is ran (eg, it will reset all warnings)
    if (ran_set_sql_mode)
      cache_sql_mode();
  }
  CATCH_ANY_EXCEPTION_AND_DISPATCH(statement)

  if (dbc_driver)
    dbc_driver->threadEnd();

  update_menu_and_toolbar();
  
  _usr_dbc_conn->is_stop_query_requested = false;

  return grt::StringRef("");
}


bool SqlEditorForm::is_running_query()
{
  return _is_running_query;
}


void SqlEditorForm::continue_on_error(bool val)
{
  if (_continue_on_error == val)
    return;

  _continue_on_error= val;
  _options.set("DbSqlEditor:ContinueOnError", grt::IntegerRef((int)_continue_on_error));
  
  if (_menu)
    _menu->set_item_checked("query.stopOnError", !continue_on_error());
  set_editor_tool_items_checked("query.stopOnError", !continue_on_error());
}


void SqlEditorForm::send_message_keep_alive()
{
  try
  {
    // ping server and reset connection timeout counter
    // this also checks the connection state and restores it if possible
    ensure_valid_aux_connection();
    ensure_valid_usr_connection();
  }
  catch (const std::exception &)
  {
  }
}


Recordset::Ref SqlEditorForm::active_recordset(int editor)
{
  GMutexLock lock(_sql_editors_mutex);
  if (editor >= 0 && editor < (int)_sql_editors.size())
    return _sql_editors[editor]->active_recordset;
  return Recordset::Ref();
}

void SqlEditorForm::active_recordset(int editor, Recordset::Ref rset)
{
  GMutexLock lock(_sql_editors_mutex);
  if (editor >= 0 && editor < (int)_sql_editors.size())
    _sql_editors[editor]->active_recordset = rset;

  if (_menu)
  {
    _menu->set_item_enabled("query.save_edits", rset && rset->has_pending_changes());
    _menu->set_item_enabled("query.discard_edits", rset && rset->has_pending_changes());
    _menu->set_item_enabled("query.export", (bool)rset);
  }
}

bool SqlEditorForm::recordset_reorder(int editor, Recordset::Ref value, int new_index)
{
  GMutexTryLock lock(_sql_editors_mutex);
  if (lock.locked())
  {
    if (editor >= 0 && editor < (int)_sql_editors.size())
    {
      GMutexTryLock rlock(_sql_editors[editor]->recordset_mutex);
      if (rlock.locked())
      {
        RecordsetsRef recordsets(sql_editor_recordsets(editor));

        Recordsets::iterator iter = std::find(recordsets->begin(), recordsets->end(), value);
        if (iter != recordsets->end())
        {
          if (new_index >= (int)recordsets->size()-1)
            new_index= -1;
          recordsets->erase(iter);
          if (new_index < 0)
            recordsets->push_back(value);
          else
            recordsets->insert(recordsets->begin() + new_index, value);
          return true;
        }
      }
    }
  }
  return false;
}


void SqlEditorForm::on_close_recordset(Recordset::Ptr rs_ptr)
{
  int editor = 0;
  RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, rs_ptr, rs)
  {
    {
      GMutexLock ed_lock(_sql_editors_mutex);
      editor = sql_editor_index_for_recordset(rs_ref->key());
      if (editor >= 0)
      {
        GMutexLock recordsets_mutex(_sql_editors[editor]->recordset_mutex);
        RecordsetsRef rsets(sql_editor_recordsets(editor));
        rsets->erase(std::find(rsets->begin(), rsets->end(), rs_ref));
        if (_sql_editors[editor]->active_recordset == rs_ref)
          _sql_editors[editor]->active_recordset.reset();
      }
    }
    recordset_list_changed(editor, rs_ref, false);
    //delme close_recordset_ui.emit(rs->key());    
  }
}

void SqlEditorForm::recall_recordset_query(Recordset::Ptr rs_ptr)
{
  RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, rs_ptr, rs)
  {
    std::string query = rs->generator_query();
    
    new_sql_scratch_area();
    set_sql_editor_text(query);
  }
}

void SqlEditorForm::apply_changes_to_recordset(Recordset::Ptr rs_ptr)
{
  RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, rs_ptr, rs)

  try
  {
    GMutexLock usr_dbc_conn_mutex= ensure_valid_usr_connection();

    // we need transaction to enforce atomicity of change set
    // so if autocommit is currently enabled disable it temporarily
    bool auto_commit= _usr_dbc_conn->ref->getAutoCommit();
    ScopeExitTrigger autocommit_mode_keeper;
    int res= -2;
    if (!auto_commit)
    {
      int res= mforms::Utilities::show_warning(
        _("Apply Changes to Recordset"),
        _("Autocommit is currently disabled. Do you want to perform a COMMIT before applying the changes?\n"
          "If you do not commit, a failure during the recordset update will result in a rollback of the active transaction, if you have one."),
        _("Commit and Apply"),
        _("Cancel"),
        _("Apply"));

      if (res == mforms::ResultOk)
      {
        _usr_dbc_conn->ref->commit();
      }
    }
    else
    {
      autocommit_mode_keeper.slot= boost::bind(
        &sql::Connection::setAutoCommit, _usr_dbc_conn->ref.get(), 
        auto_commit);
      _usr_dbc_conn->ref->setAutoCommit(false);
    }

    if (res != mforms::ResultCancel) // only if not canceled
    {
      bool is_data_changes_commit_wizard_enabled= (0 != _options.get_int("DbSqlEditor:IsDataChangesCommitWizardEnabled", 1));
      if (is_data_changes_commit_wizard_enabled)
      {
        /*bool are_changes_committed= */run_data_changes_commit_wizard(rs_ptr);
      }
      else
      {
        rs->apply_changes_();
      }
    }
  }
  CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Apply changes to recordset"))
}


bool SqlEditorForm::run_data_changes_commit_wizard(Recordset::Ptr rs_ptr)
{
  RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, rs_ptr, rs, false)

  on_sql_script_run_error.disconnect_all_slots();
  on_sql_script_run_progress.disconnect_all_slots();
  on_sql_script_run_statistics.disconnect_all_slots();

  // set underlying recordset data storage to use sql substitute (potentially modified by user)
  // instead of generating sql based on swap db contents
  Recordset_data_storage::Ref data_storage_ref= rs->data_storage();
  Recordset_sql_storage *sql_storage= dynamic_cast<Recordset_sql_storage *>(data_storage_ref.get());
  if (!sql_storage)
    return false;
  sql_storage->init_sql_script_substitute(rs_ptr, true);
  sql_storage->is_sql_script_substitute_enabled(true);
  const Sql_script &sql_script= sql_storage->sql_script_substitute();;
  std::string sql_script_text= Recordset_sql_storage::statements_as_sql_script(sql_script.statements);

  SqlScriptRunWizard wizard(_grtm);
  scoped_connection c1(on_sql_script_run_error.connect(boost::bind(&SqlScriptApplyPage::on_error, wizard.apply_page, _1, _2, _3)));
  scoped_connection c2(on_sql_script_run_progress.connect(boost::bind(&SqlScriptApplyPage::on_exec_progress, wizard.apply_page, _1)));
  scoped_connection c3(on_sql_script_run_statistics.connect(boost::bind(&SqlScriptApplyPage::on_exec_stat, wizard.apply_page, _1, _2)));
  wizard.values().gset("sql_script", sql_script_text);
  wizard.apply_page->apply_sql_script= boost::bind(&SqlEditorForm::apply_data_changes_commit, this, _1, rs_ptr);
  wizard.run_modal();

  return !wizard.has_errors();
}


void SqlEditorForm::apply_data_changes_commit(std::string &sql_script_text, Recordset::Ptr rs_ptr)
{
  RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, rs_ptr, rs);

  // this lock is supposed to be acquired lower in call-stack by SqlEditorForm::apply_changes_to_recordset
  //GMutexLock usr_conn_mutex= ensure_valid_usr_connection();

  Recordset_data_storage::Ref data_storage_ref= rs->data_storage();
  Recordset_sql_storage *sql_storage= dynamic_cast<Recordset_sql_storage *>(data_storage_ref.get());
  if (!sql_storage)
    return;

  int max_query_size_to_log = _options.get_int("DbSqlEditor:MaxQuerySizeToHistory", 0);

  Sql_script sql_script= sql_storage->sql_script_substitute();
  sql_script.statements.clear();
  SqlFacade::Ref sql_splitter= SqlFacade::instance_for_rdbms(rdbms());
  sql_splitter->splitSqlScript(sql_script_text, sql_script.statements);

  scoped_connection on_sql_script_run_error_conn(sql_storage->on_sql_script_run_error.connect(on_sql_script_run_error));
  scoped_connection on_sql_script_run_progress_conn(sql_storage->on_sql_script_run_progress.connect(on_sql_script_run_progress));
  scoped_connection on_sql_script_run_statistics_conn(sql_storage->on_sql_script_run_statistics.connect(on_sql_script_run_statistics));

  sql_storage->sql_script_substitute(sql_script);
  rs->do_apply_changes(_grtm->get_grt(), rs_ptr, Recordset_data_storage::Ptr(data_storage_ref));

  if (!max_query_size_to_log || max_query_size_to_log >= (int)sql_script_text.size() )
    _history->add_entry(sql_script.statements);
}


std::string SqlEditorForm::active_schema() const
{
  return (_usr_dbc_conn) ? _usr_dbc_conn->active_schema : std::string();
}


int SqlEditorForm::active_schema_index() const
{
  return _schema_tree->get_index_of_schema(active_schema());
}


void SqlEditorForm::cache_active_schema_name()
{
  std::string schema=_usr_dbc_conn->ref->getSchema();
  _usr_dbc_conn->active_schema= schema;
  _aux_dbc_conn->active_schema= schema;

  _schema_tree->set_active_schema(schema);

  exec_sql_task->execute_in_main_thread(
      boost::bind(&SqlEditorForm::update_editor_title_schema, this, schema),
      false,
      true);

  if (_side_bar)
  {
    //_grtm->run_once_when_idle(boost::bind(&mforms::GRTTreeView::refresh, _side_bar->get_schema_tree(), bec::NodeId()));
    _grtm->run_once_when_idle(boost::bind(&mforms::View::set_needs_repaint, _side_bar->get_schema_tree()));
  }
}


void SqlEditorForm::active_schema(const std::string &value)
{
  try
  {
    if (value.empty())
      return;

    std::string schema= active_schema();
    if (value == schema)
      return;

    {
      GMutexLock aux_dbc_conn_mutex= ensure_valid_aux_connection();
      _aux_dbc_conn->ref->setSchema(value);
      _aux_dbc_conn->active_schema= value;
    }

    {
      GMutexLock usr_dbc_conn_mutex= ensure_valid_usr_connection();
      _usr_dbc_conn->ref->setSchema(value);
      _usr_dbc_conn->active_schema= value;
    }
    
    _schema_tree->set_active_schema(value);
    if (_side_bar)
    {
      _grtm->run_once_when_idle(boost::bind(&mforms::View::set_needs_repaint, _side_bar->get_schema_tree()));
      //_side_bar->get_schema_tree()->refresh(bec::NodeId());
    }
    // remember active schema
    _connection->parameterValues().gset("DbSqlEditor:LastDefaultSchema", value);

    update_editor_title_schema(value);
    
    grt_manager()->replace_status_text(strfmt(_("Active schema changed to %s"), value.c_str()));
  }
  CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Set active schema"))
}


wb::LiveSchemaTree *SqlEditorForm::get_base_schema_tree()
{
  return &_base_schema_tree;
}

wb::LiveSchemaTree *SqlEditorForm::get_schema_tree()
{
  return _schema_tree;
}

wb::LiveSchemaTree *SqlEditorForm::get_filtered_schema_tree()
{
  return &_filtered_schema_tree;
}

db_mgmt_RdbmsRef SqlEditorForm::rdbms()
{
  if (_connection.is_valid())
  {
    if (!_connection->driver().is_valid())
      throw std::runtime_error("Connection has invalid driver, check connection parameters.");
    return db_mgmt_RdbmsRef::cast_from(_connection->driver()->owner());
  }
  else
    return db_mgmt_RdbmsRef::cast_from(_grtm->get_grt()->get("/wb/doc/physicalModels/0/rdbms"));
}


int SqlEditorForm::count_connection_editors(std::string conn_name)
{
  int count = 0;
  boost::weak_ptr<SqlEditorForm> editor;
  
  std::list<boost::weak_ptr<SqlEditorForm> >::iterator index, end;
  
  end = _wbsql->get_open_editors()->end();
  for(index = _wbsql->get_open_editors()->begin(); index != end; index++)
  {
    SqlEditorForm::Ref editor((*index).lock());
    std::string editor_connection = editor->_connection->name();
    if (editor_connection == conn_name)
      count++;    
  }
  
  return count;  
}

std::string SqlEditorForm::caption() const
{
  return _title;
}
 
std::string SqlEditorForm::get_title(bool new_editor)
{
  int count_limit = new_editor ? 0 : 1;
  std::string caption= _("SQL Editor");
  std::string editor_connection = _connection->name();
  
  if (*_connection->name().c_str())
    caption+= strfmt(" (%s", _connection->name().c_str());
  else
  {
    if (_connection->driver()->name() == "MysqlNativeSocket")
      caption+= " (localhost";
    else
      caption+= strfmt(" (%s", truncate_text(editor_connection,21).c_str());
  }

  // This function is called in three different scenarios:
  // New Editor: The title should include the active schema if there's a previously
  //             registered open editor to the same host (because the new editor is
  //             not registered yet)
  // Activating a new schema : in which case the default schema should be included
  //             if there are at least 2 registered open editors
  // Other editor has been opened to the same host : in which case the active schema
  //             should be included
  if(count_connection_editors(editor_connection) > count_limit && !_usr_dbc_conn->active_schema.empty())
    caption+= strfmt(" - %s", truncate_text(_usr_dbc_conn->active_schema, 20).c_str());

  caption+= ")";

  return caption;
}


bool SqlEditorForm::save_snippet(const std::string &text)
{
  if (text.empty()) return false;
  
  std::string name;
  if (!wbsql()->get_wbui()->get_wb()->request_input(_("Enter a Name for the Snippet (optional):"), 0, name))
    return false;

  DbSqlEditorSnippets::get_instance()->add_snippet(name, text);
  
  return true;
}

//--------------------------------------------------------------------------------------------------

bool SqlEditorForm::can_close()
{  
  return can_close_(true);
}


bool SqlEditorForm::can_close_(bool interactive)
{
  bool check_editors = true;
  
  if (!bec::UIForm::can_close())
    return false;

  if (_side_bar)
    _options.gset("DbSqlEditor:SidebarCollapseState", _side_bar->get_collapse_states());
  
  {
    // if Save of workspace on close is enabled, we don't need to check whether there are unsaved SQL editors
    // but other stuff should be checked
    grt::ValueRef option(_grtm->get_app_option("workbench:SaveSQLWorkspaceOnClose"));
    if (option.is_valid() && *grt::IntegerRef::cast_from(option))
      check_editors = false;
  }

  if (interactive)
  {
    ConfirmSaveDialog dialog(0, "Close SQL Editor", "The following files/resultsets have unsaved changes.\nDo you want to review these changes before closing?");
    for (int i = 0; i < sql_editor_count(); i++)
    {
      if (sql_editor_dirty(i) && check_editors)
      {
        std::string n= sql_editor_path(i);
        if (!n.empty())
          n= std::string(g_basename(n.c_str())).append(" - ").append(n);
        else
          n= "Unsaved SQL Query";
        dialog.add_item("Script Buffers", n);
      }

      RecordsetsRef rsets = sql_editor_recordsets(i);
      BOOST_FOREACH (Recordset::Ref rs, *(rsets.get()))
      {
        if (!rs->can_close(false))
          dialog.add_item("Resultset", rs->caption());
      }
    }
    
    bool review= false;
    if (dialog.change_count() > 1)
    {
      switch (dialog.run()) 
      {
        case ConfirmSaveDialog::ReviewChanges:
          review= true;
          break;

        case ConfirmSaveDialog::DiscardChanges:
          review= false;
          break;

        case ConfirmSaveDialog::Cancel:
          return false;
      }
    }
    else if (dialog.change_count() == 1)
      review= true;
     
    // review changes 1 by 1
    if (review && check_editors)
    {
      for (int i = 0; i < sql_editor_count(); i++)
      {
        // sql_editor_wilL_close() checks for unsaved recordsets
        if (!sql_editor_will_close(i))
          return false;
      }
    }
  }
  else // !interactive
  {
    for (int i = 0; i < sql_editor_count(); i++)
    {
      if (check_editors && sql_editor_dirty(i))
        return false;

      {
        GMutexTryLock lock(_sql_editors[i]->recordset_mutex);
        if (!lock.locked())
          return false;

        RecordsetsRef rsets = sql_editor_recordsets(i);
        BOOST_FOREACH (Recordset::Ref rs, *(rsets.get()))
        {
          if (!rs->can_close(false))
            return false;
        }
      }
    }
  }

  return true;
}



bec::MenuItemList SqlEditorForm::recordset_popup_menu_items(const std::vector<int> &rows, int column, Recordset::Ptr rs_ptr)
{
  RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, rs_ptr, rs, bec::MenuItemList())
  if (rs)
  {
    bec::ArgumentPool argpool;
    
    if (!rows.empty())
    {
      grt::IntegerListRef row_list(grt_manager()->get_grt());
      for (std::vector<int>::const_iterator iter= rows.begin(); iter != rows.end(); ++iter)
        row_list.insert(grt::IntegerRef(*iter));
      argpool.add_simple_value("selectedRows", row_list);
      argpool.add_simple_value("clickedRow", grt::IntegerRef(rows[0]));
    }
    argpool.add_simple_value("clickedColumn", grt::IntegerRef(column));

    _context_ui->get_wb()->update_plugin_arguments_pool(argpool);

    db_query_EditorRef editor(_wbsql->get_grt_editor_object(this));
    
    for (size_t c= editor->resultsets().count(), i= 0; i < c; i++)
    {
      db_query_ResultsetRef rset(editor->resultsets()[i]);
      if (rset->get_data()->recordset.get() == rs)
      {
        // add rset to the pool, overriding the one added by update_args
        argpool.add_entries_for_object("", rset, "db.query.Resultset");
        break;
      }
    }

    std::list<std::string> groups;
    groups.push_back("Menu/SQL/Resultset");

    return grt_manager()->get_plugin_context_menu_items(groups, argpool);    
  }
  
  return bec::MenuItemList();
}


bool SqlEditorForm::call_recordset_popup_menu_item(const std::string &item, const std::vector<int> &rows,
                                                   int column, Recordset::Ptr rs_ptr)
{
  RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, rs_ptr, rs, false)
  
  if (rs)
  {
    bec::ArgumentPool argpool;
    
    if (!rows.empty())
    {
      grt::IntegerListRef row_list(grt_manager()->get_grt());
      for (std::vector<int>::const_iterator iter= rows.begin(); iter != rows.end(); ++iter)
        row_list.insert(grt::IntegerRef(*iter));
      argpool["app.PluginInputDefinition:selectedRows"]= row_list;
      argpool["app.PluginInputDefinition:clickedRow"]= grt::IntegerRef(rows[0]);
    }
    argpool["app.PluginInputDefinition:clickedColumn"]= grt::IntegerRef(column);
    
    _context_ui->get_wb()->update_plugin_arguments_pool(argpool);
    
    db_query_EditorRef editor(_wbsql->get_grt_editor_object(this));
    
    for (size_t c= editor->resultsets().count(), i= 0; i < c; i++)
    {
      db_query_ResultsetRef rset(editor->resultsets()[i]);
      if (rset->get_data()->recordset.get() == rs)
      {
        // add rset to the pool, overriding the one added by update_args
        argpool.add_entries_for_object("", rset, "db.query.Resultset");
        break;
      }
    }
    
    return _context_ui->get_command_ui()->activate_command(item, argpool);
  }
  
  return false;
}


//--------------------------------------------------------------------------------------------------

bool SqlEditorForm::is_editable_select(const std::string& statement, std::string &schema_name, std::string& table_name, std::string& error)
{
  SqlFacade::Ref sql_facade= SqlFacade::instance_for_rdbms(rdbms());
  SqlFacade::Column_name_list column_names;
  schema_name = "";
  table_name = "";
  int pk_count = 0;
  bool missing_pk = false;
  bool found_id = false;

  error = "";

  // Inserts the key columns if not already present included
  if (sql_facade->parseSelectStatementForEdit(statement, schema_name, table_name, column_names))
  {
    bool fetch_table_data = true;

    if ( schema_name.length() == 0 )
      schema_name = _usr_dbc_conn->active_schema;

    LiveSchemaTree::TableNode table;

    bec::NodeId table_node = _schema_tree->get_node_for_object(schema_name, LiveSchemaTree::Table, table_name);

    if (table_node.is_valid())
    {
      table = *dynamic_cast<LiveSchemaTree::TableNode *>(_schema_tree->get_object_node_by_index(table_node));
      
      // If the column info is not available it must be fetched
      fetch_table_data = !table.columns_loaded;
    }

    // If the table information has not been loaded
    if (fetch_table_data)
    {
      // Loads the table information
      fetch_object_details(LiveSchemaTree::Table, schema_name, table_name, LiveSchemaTree::COLUMN_DATA,
                                       boost::bind(&SqlEditorForm::table_details_arrived, this, _1, _2, _3,  &table));
    }

    // Checks the id columns (if exist) until one is found on the select statement
    for(size_t index = 0; !found_id && index < table.columns.size(); index++)
    {
      if (table.columns[index].is_id)
      {
        // It could be ID and not PK
        bool is_pk = table.columns[index].is_pk;

        std::string column_name = table.columns[index].name;
        
        // Counts the primary key columns
        if (is_pk)
          pk_count++;

        // If the query is not retrieving all the columns, reviews if the primary key column is being retrieved
        if (column_names.front().first != "*")
        {
          bool found = false;
          SqlFacade::Column_name_list::iterator index, end = column_names.end();
          for(index = column_names.begin(); !found && index != end; index++)
            found = ((*index).first == column_name);

          if (!found && is_pk)
            missing_pk = true;

          if (found && !is_pk)
            found_id = true;
        }
      }
    }

    if (!found_id)
    {
      if(pk_count == 0)
        error = base::strfmt("Table %s has no primary keys or unique non nullable columns defined. Only tables with primary keys or unique non nullable columns can be edited.", table_name.c_str());
      else if(missing_pk)
        error = base::strfmt("The select statement is missing primary key or unique non nullable columns on table %s.", table_name.c_str());
    }
  }
  else
    error = "The statement is not editable.";

  // The return value is true if:
  //     All the columns included on the PK were found on the select statement (in case of a compound PK)
  //     A unique non nullable column was found on the select statement
  return ((pk_count > 0) && !missing_pk) || found_id;
}

void SqlEditorForm::table_details_arrived(const wb::LiveSchemaTree::SchemaNode &schema_node, const wb::LiveSchemaTree::ObjectNode *obj_node, wb::LiveSchemaTree::ObjectType obj_type, wb::LiveSchemaTree::TableNode *table_node)
{
  *table_node = *obj_node;
}

void SqlEditorForm::update_editor_title_schema(const std::string& schema)
{
    // Gets the editor label including the schema name only if
    // the number of opened editors to the same host is > 1
    std::string temp_title = get_title(false);

    if (temp_title != _title)      
    {
      _title = temp_title;
      title_changed();
    }
}