/*
 * Copyright (c) 2007, 2023, 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, version 2.0,
 * as published by the Free Software Foundation.
 *
 * This program is designed to work with certain software (including
 * but not limited to OpenSSL) that is licensed under separate terms, as
 * designated in a particular file or component or in included license
 * documentation.  The authors of MySQL hereby grant you an additional
 * permission to link the program and your derivative works with the
 * separately licensed software that they have either included with
 * the program or referenced in the documentation.
 * 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, version 2.0, 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 <regex>

#include "grtpp_util.h"
#include "wb_sql_editor_tree_controller.h"
#include "wb_sql_editor_form.h"
#include "execute_routine_wizard.h"
#include "wb_sql_editor_panel.h"

#include "base/sqlstring.h"
#include "base/util_functions.h"
#include "base/string_utilities.h"
#include "base/boost_smart_ptr_helpers.h"
#include "base/log.h"
#include "base/notifications.h"

#include "workbench/wb_command_ui.h"
#include "workbench/wb_context_ui.h"

#include <boost/signals2/connection.hpp>

#include "diff/diffchange.h"
#include "grtdb/diff_dbobjectmatch.h"
#include "db.mysql/src/module_db_mysql.h"

#include "grtdb/editor_table.h"
#include "grtdb/db_helpers.h"
#include "grtsqlparser/sql_facade.h"

#include "workbench/wb_db_schema.h"

#include "objimpl/wrapper/mforms_ObjectReference_impl.h"

#include "advanced_sidebar.h"

#include "mforms/textentry.h"
#include "mforms/hypertext.h"
#include "mforms/splitter.h"
#include "mforms/tabview.h"
#include "mforms/panel.h"
#include "mforms/menubar.h"
#include "mforms/utilities.h"

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

using boost::signals2::scoped_connection;

DEFAULT_LOG_DOMAIN("SqlEditorSchemaTree");

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

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

#define CATCH_ANY_EXCEPTION_AND_DISPATCH(statement)                                                                 \
  catch (sql::SQLException & e) {                                                                                   \
    _owner->add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), \
                            statement, "");                                                                         \
    logError("SQLException executing %s: %s\n", std::string(statement).c_str(),                                     \
             strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()).c_str());                                 \
  }                                                                                                                 \
  CATCH_EXCEPTION_AND_DISPATCH(statement)

#define CATCH_EXCEPTION_AND_DISPATCH(statement)                                                               \
  catch (std::exception & e) {                                                                                \
    _owner->add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(EXCEPTION_MSG_FORMAT, e.what()), statement, ""); \
    logError("Exception executing %s: %s\n", std::string(statement).c_str(),                                  \
             strfmt(EXCEPTION_MSG_FORMAT, e.what()).c_str());                                                 \
  }

#define CATCH_ANY_EXCEPTION_AND_DISPATCH(statement)                                                                 \
  catch (sql::SQLException & e) {                                                                                   \
    _owner->add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), \
                            statement, "");                                                                         \
    logError("SQLException executing %s: %s\n", std::string(statement).c_str(),                                     \
             strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()).c_str());                                 \
  }                                                                                                                 \
  CATCH_EXCEPTION_AND_DISPATCH(statement)

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

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

std::shared_ptr<SqlEditorTreeController> SqlEditorTreeController::create(SqlEditorForm *owner) {
  std::shared_ptr<SqlEditorTreeController> instance(new SqlEditorTreeController(owner));

  instance->_base_schema_tree.set_delegate(instance);
  instance->_base_schema_tree.set_fetch_delegate(instance);

  instance->_filtered_schema_tree.set_delegate(instance);
  instance->_filtered_schema_tree.set_fetch_delegate(instance);
  instance->_filtered_schema_tree.set_base(&instance->_base_schema_tree);

  return instance;
}

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

SqlEditorTreeController::SqlEditorTreeController(SqlEditorForm *owner)
  : _owner(owner),
    _schema_side_bar(nullptr),
    _admin_side_bar(nullptr),
    _task_tabview(nullptr),
    _taskbar_box(nullptr),
    _schema_tree(&_base_schema_tree),
    _base_schema_tree(bec::versionToEnum(owner->rdbms_version())),
    _filtered_schema_tree(bec::versionToEnum(owner->rdbms_version())),
    live_schema_fetch_task(GrtThreadedTask::create()),
    live_schemata_refresh_task(GrtThreadedTask::create()),
    _is_refreshing_schema_tree(false),
    _use_show_procedure(false),
    _side_splitter(nullptr),
    _info_tabview(nullptr),
    _object_info(nullptr),
    _session_info(nullptr) {

  grt::GRTNotificationCenter::get()->add_grt_observer(this, "GRNDBObjectEditorCreated");
  grt::GRTNotificationCenter::get()->add_grt_observer(this, "GRNPreferencesDidClose");
  grt::GRTNotificationCenter::get()->add_grt_observer(this, "GRNSQLEditorReconnected");
  base::NotificationCenter::get()->add_observer(this, "GNColorsChanged");

  _base_schema_tree.is_schema_contents_enabled(
    bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ShowSchemaTreeSchemaContents", 1) != 0);
  _filtered_schema_tree.is_schema_contents_enabled(
    bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ShowSchemaTreeSchemaContents", 1) != 0);

  _base_schema_tree.sql_editor_text_insert_signal.connect(
    std::bind(&SqlEditorTreeController::insert_text_to_active_editor, this, std::placeholders::_1));
  _filtered_schema_tree.sql_editor_text_insert_signal.connect(
    std::bind(&SqlEditorTreeController::insert_text_to_active_editor, this, std::placeholders::_1));

  live_schemata_refresh_task->desc("Live Schema Refresh Task");
  live_schemata_refresh_task->send_task_res_msg(false);
  live_schemata_refresh_task->msg_cb(std::bind(&SqlEditorForm::add_log_message, _owner, std::placeholders::_1,
                                               std::placeholders::_2, std::placeholders::_3, ""));

  live_schema_fetch_task->desc("Live Schema Fetch Task");
  live_schema_fetch_task->send_task_res_msg(false);
  live_schema_fetch_task->msg_cb(std::bind(&SqlEditorForm::add_log_message, _owner, std::placeholders::_1,
                                           std::placeholders::_2, std::placeholders::_3, ""));
}

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

SqlEditorTreeController::~SqlEditorTreeController() {
  grt::GRTNotificationCenter::get()->remove_grt_observer(this);

  delete _taskbar_box;
  delete _info_tabview;
  delete _schema_side_bar;
  delete _admin_side_bar;
  delete _task_tabview;

  if (_side_splitter != NULL)
    _side_splitter->release();

  delete _session_info;
  delete _object_info;
}

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

void SqlEditorTreeController::finish_init() {
  // Box to host the management and SQL IDE task bars.
  _taskbar_box = new mforms::Box(false);

  // Left hand sidebar tabview with admin and schema tree pages.
  _task_tabview = new mforms::TabView(mforms::TabViewSelectorSecondary);
  _task_tabview->set_name("SQL IDE Sidebar Tabview");
  _schema_side_bar = dynamic_cast<wb::AdvancedSidebar *>(mforms::TaskSidebar::create("SchemaTree"));
  scoped_connect(_schema_side_bar->on_section_command(),
                 std::bind(&SqlEditorTreeController::sidebar_action, this, std::placeholders::_1));
  _admin_side_bar = dynamic_cast<wb::SimpleSidebar *>(mforms::TaskSidebar::create("Simple"));
  scoped_connect(_admin_side_bar->on_section_command(),
                 std::bind(&SqlEditorTreeController::sidebar_action, this, std::placeholders::_1));
  
  _admin_side_bar->set_name("Administration Page");
  _schema_side_bar->set_name("Schema Page");

  mforms::TaskSectionFlags flags = mforms::TaskSectionRefreshable;
  _schema_side_bar->add_section("SchemaTree", "Schema Tree", _("SCHEMAS"), flags);

  _task_tabview->add_page(_admin_side_bar, _("Administration"));
  _task_tabview->add_page(_schema_side_bar, _("Schemas"));

  int i = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ActiveTaskTab", 0);
  if (i < 0)
    i = 0;
  else if (i >= 2)
    i = 1;
  _task_tabview->set_active_tab(i);

  _schema_side_bar->get_context_menu()->signal_will_show()->connect(
    std::bind(&SqlEditorTreeController::context_menu_will_show, this, std::placeholders::_1));
  _schema_side_bar->set_schema_model(&_base_schema_tree);
  _schema_side_bar->set_filtered_schema_model(&_filtered_schema_tree);
  _schema_side_bar->set_selection_color(base::HighlightColor);

  int initial_splitter_pos =
    (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:SidebarInitialSplitterPos", 500);
  _side_splitter = mforms::manage(new mforms::Splitter(false, false));
  _side_splitter->set_name("SQL IDE Sidebar Splitter");

#ifdef _MSC_VER
  mforms::Panel *panel;
  _side_splitter->set_back_color(base::Color::getApplicationColorAsString(AppColorMainBackground, false));
  panel = mforms::manage(new mforms::Panel(mforms::StyledHeaderPanel));
  panel->set_title("Navigator");
  panel->add(_taskbar_box);
  _taskbar_box->add(_task_tabview, true, true);
  _side_splitter->add(panel, 200);
#else
  _taskbar_box->add(_task_tabview, true, true);
  _side_splitter->add(_taskbar_box, 200);
#endif

  _info_tabview = new mforms::TabView(mforms::TabViewSelectorSecondary);
  _info_tabview->set_name("Info View");

#ifdef _MSC_VER
  panel = mforms::manage(new mforms::Panel(mforms::StyledHeaderPanel));
  panel->set_title(_("Information"));
  panel->add(_info_tabview);
  _side_splitter->add(panel, 200);
#else
  _side_splitter->add(_info_tabview, 200);
#endif

  _object_info = new mforms::HyperText();
  _session_info = new mforms::HyperText();
#ifdef _MSC_VER
  _object_info->set_padding(3, 3, 3, 3);
  _session_info->set_padding(3, 3, 3, 3);
#endif

  _info_tabview->add_page(_object_info, _("Object Info"));
  _info_tabview->add_page(_session_info, _("Session"));

  updateColors();

  scoped_connect(_schema_side_bar->signal_filter_changed(),
                 std::bind(&SqlEditorTreeController::side_bar_filter_changed, this, std::placeholders::_1));
  scoped_connect(_schema_side_bar->tree_node_selected(),
                 std::bind(&SqlEditorTreeController::schema_row_selected, this));

  // update the info box
  schema_row_selected();

  tree_refresh();

  // make sure to restore the splitter pos after layout is ready
  bec::GRTManager::get()->run_once_when_idle(
    this, std::bind(&mforms::Splitter::set_divider_position, _side_splitter, initial_splitter_pos));

  // Connect the splitter change event after the setup is done to avoid wrong triggering.
  _splitter_connection = _side_splitter->signal_position_changed()->connect(
    std::bind(&SqlEditorTreeController::sidebar_splitter_changed, this));

  // Setup grt access to sidebar.
  db_query_EditorRef editor(_owner->wbsql()->get_grt_editor_object(_owner));
  if (editor.is_valid())
    editor->sidebar(mforms_to_grt(_admin_side_bar, "TaskSidebar"));

  if (!_owner->connected())
    _info_tabview->set_active_tab(1);
}

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

void SqlEditorTreeController::prepare_close() {
  // Explicitly disconnect from the splitter change event as it sends unwanted change notifications
  // when controls are freed on shutdown.
  _splitter_connection.disconnect();

  if (_schema_side_bar)
    bec::GRTManager::get()->set_app_option("DbSqlEditor:SidebarCollapseState",
                                           grt::StringRef(_schema_side_bar->get_collapse_states()));

  int tab = _task_tabview->get_active_tab();
  bec::GRTManager::get()->set_app_option("DbSqlEditor:ActiveTaskTab", grt::IntegerRef(tab));
  tab = _info_tabview->get_active_tab();
  bec::GRTManager::get()->set_app_option("DbSqlEditor:ActiveInfoTab", grt::IntegerRef(tab));
}

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

void SqlEditorTreeController::schema_row_selected() {
  std::list<mforms::TreeNodeRef> nodes;
  std::string details;

  if (_schema_side_bar) {
    Color textColor = Color::getSystemColor(base::SystemColor::LabelColor);

    nodes = _schema_side_bar->get_schema_tree()->get_selection();
    if (nodes.empty() || nodes.size() > 1)
      textColor.alpha = 0.75;

    details = "<html><body style='padding:15px; font-family:";
    details += DEFAULT_FONT_FAMILY;
    details += "; font-size: 9pt; color: " + textColor.to_html() + ";'>";
    if (nodes.empty()) {
      details += "<b>No object selected</b>";
    } else if (nodes.size() > 1) {
      details += "<b>Multiple selected objects</b>";
    } else {
      // When there's a single node selected, gets the details and tells it to notify if changes in it occur.
      details += _schema_tree->get_field_description(nodes.front());
      _schema_tree->set_notify_on_reload(nodes.front());
    }
    details += "</body></html>";

    _object_info->set_markup_text(details);

    // send out notification about selection change
    grt::DictRef info(true);
    info.gset("selection-size", (int)nodes.size());
    grt::GRTNotificationCenter::get()->send_grt("GRNLiveDBObjectSelectionDidChange",
                                                _owner->wbsql()->get_grt_editor_object(_owner), info);
  }
}

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

void SqlEditorTreeController::side_bar_filter_changed(const std::string &filter) {
  if (filter.length() > 0)
    _schema_tree = &_filtered_schema_tree;
  else
    _schema_tree = &_base_schema_tree;
}

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

void SqlEditorTreeController::sidebar_splitter_changed() {
  int pos = _side_splitter->get_divider_position();
  if (pos > 0)
    bec::GRTManager::get()->set_app_option("DbSqlEditor:SidebarInitialSplitterPos", grt::IntegerRef(pos));
}

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

bool SqlEditorTreeController::fetch_data_for_filter(
  const std::string &schema_filter, const std::string &object_filter,
  const wb::LiveSchemaTree::NewSchemaContentArrivedSlot &arrived_slot) {
  std::string wb_internal_schema = bec::GRTManager::get()->get_app_option_string("workbench:InternalSchema");

  sql::Dbc_connection_handler::Ref conn;

  RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

  InternalSchema internal_schema(wb_internal_schema, conn);

  // Validates the remote search is available
  bool remote_search_enabled = internal_schema.is_remote_search_deployed();

  if (!remote_search_enabled) {
    if (mforms::Utilities::show_message(
          _("Search Objects in Server"),
          base::strfmt(_("To enable searching objects in the remote server, MySQL Workbench needs to create a stored "
                         "procedure in a custom schema (%s)."),
                       wb_internal_schema.c_str()),
          _("Create"), _("Cancel")) == 1) {
      // Performs the deployment, any error will be returned
      std::string error = internal_schema.deploy_remote_search();

      if (!error.length())
        remote_search_enabled = true;
      else {
        std::string userName = _owner->connection_descriptor()->parameterValues().get_string("userName");
        std::string msgFmt =
          _("The user %s has no privileges to create the required schema and stored procedures "
            "to enable remote search in this server. \n"
            "Ensure your database administrator creates a schema for internal use of MySQL Workbench"
            " with full privileges for the user %s, once created configure it in "
            "Preferences->General->Internal Workbench Schema and retry.\n\n%s.");

        std::string message = base::strfmt(msgFmt.c_str(), userName.c_str(), userName.c_str(), error.c_str());

        mforms::Utilities::show_error("Search Objects in Server", message, "Ok");
      }
    }
  }

  // If the remote search is available performs the search
  if (remote_search_enabled) {
    bool sync = !bec::GRTManager::get()->in_main_thread();
    logDebug3("Fetch data for filter %s.%s\n", schema_filter.c_str(), object_filter.c_str());

    live_schema_fetch_task->exec(sync, std::bind(&SqlEditorTreeController::do_fetch_data_for_filter, this,
                                                 weak_ptr_from(this), schema_filter, object_filter, arrived_slot));
  }

  return true;
}

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

std::vector<std::string> SqlEditorTreeController::fetch_schema_list() {
  static std::set<std::string> systemSchemaNames{"information_schema", "performance_schema", "mysql"};

  std::vector<std::string> schemata_names;
  try {
    sql::Dbc_connection_handler::Ref conn;

    RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

    bool showSystemSchemas = bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ShowMetadataSchemata", 0) != 0;

    std::unique_ptr<sql::ResultSet> rs(conn->ref->getMetaData()->getSchemata());
    while (rs->next()) {
      std::string name = rs->getString(1);
      if (name[0] == '.')
        continue;
      if (showSystemSchemas || systemSchemaNames.count(name) == 0)
        schemata_names.push_back(name);
    }
  }
  CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Get schemata"))
  return schemata_names;
}

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

bool SqlEditorTreeController::fetch_schema_contents(
  const std::string &schema_name, const wb::LiveSchemaTree::NewSchemaContentArrivedSlot &arrived_slot) {
  // in windows we use TreeViewAdv feature to expand nodes asynchronously
  // that is this function is already called from a separate thread
  // and it must have items loaded when it returns.
  bool sync = !bec::GRTManager::get()->in_main_thread();
  logDebug3("Fetch schema contents for %s\n", schema_name.c_str());

  live_schema_fetch_task->exec(sync, std::bind(&SqlEditorTreeController::do_fetch_live_schema_contents, this,
                                               weak_ptr_from(this), schema_name, arrived_slot));

  return true;
}

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

void SqlEditorTreeController::refresh_live_object_in_overview(wb::LiveSchemaTree::ObjectType type,
                                                              const std::string schema_name,
                                                              const std::string old_obj_name,
                                                              const std::string new_obj_name) {
  try {
    // update schema tree even if no object was added/dropped, to clear details attribute which contents might to be
    // changed
    _schema_tree->update_live_object_state(type, schema_name, old_obj_name, new_obj_name);
  }
  CATCH_ANY_EXCEPTION_AND_DISPATCH_TO_DEFAULT_LOG(_("Refresh live schema object"))
}

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

mforms::View *SqlEditorTreeController::get_sidebar() {
  return _side_splitter;
}

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

grt::StringRef SqlEditorTreeController::do_fetch_live_schema_contents(
  std::weak_ptr<SqlEditorTreeController> self_ptr, const std::string &schema_name,
  wb::LiveSchemaTree::NewSchemaContentArrivedSlot arrived_slot) {
  RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR(SqlEditorTreeController, self_ptr, self, grt::StringRef(""))
  try {
    StringListPtr tables(new std::list<std::string>());
    StringListPtr views(new std::list<std::string>());
    StringListPtr procedures(new std::list<std::string>());
    StringListPtr functions(new std::list<std::string>());

    MutexLock schema_contents_mutex(_schema_contents_mutex);
    if (!arrived_slot)
      return grt::StringRef("");

    {
      sql::Dbc_connection_handler::Ref conn;
      RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));
      std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());

      {
        std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
        std::unique_ptr<sql::ResultSet> rs(
          stmt->executeQuery(std::string(sqlstring("SHOW FULL TABLES FROM !", 0) << schema_name)));
        while (rs->next()) {
          std::string name = rs->getString(1);
          std::string type = rs->getString(2);

          if (type == "VIEW")
            views->push_back(name);
          else
            tables->push_back(name);
        }
      }
        {
          std::unique_ptr<sql::ResultSet> rs(
            stmt->executeQuery(std::string(sqlstring("SHOW PROCEDURE STATUS WHERE Db=?", 0) << schema_name)));

          while (rs->next()) {
            std::string name = rs->getString(2);
            procedures->push_back(name);
          }
        }
        {
          std::unique_ptr<sql::ResultSet> rs(
            stmt->executeQuery(std::string(sqlstring("SHOW FUNCTION STATUS WHERE Db=?", 0) << schema_name)));
          while (rs->next()) {
            std::string name = rs->getString(2);
            functions->push_back(name);
          }
        }
    }

    if (arrived_slot) {
      std::function<void()> schema_contents_arrived =
        std::bind(arrived_slot, schema_name, tables, views, procedures, functions, false);
      bec::GRTManager::get()->run_once_when_idle(this, schema_contents_arrived);
    }

    // Let the owner form know we got fresh schema meta data. Can be used to update caches.
    _owner->schema_meta_data_refreshed(schema_name, tables, views, procedures, functions);
  } catch (const sql::SQLException &e) {
    _owner->add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()),
                            "Error loading schema content", "");
    logError("SQLException executing %s: %s\n", std::string("Error loading schema content").c_str(),
             strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()).c_str());

    if (arrived_slot) {
      StringListPtr empty_list;
      std::function<void()> schema_contents_arrived =
        std::bind(arrived_slot, schema_name, empty_list, empty_list, empty_list, empty_list, false);
      bec::GRTManager::get()->run_once_when_idle(this, schema_contents_arrived);
    }
  }

  return grt::StringRef("");
}

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

grt::StringRef SqlEditorTreeController::do_fetch_data_for_filter(
  std::weak_ptr<SqlEditorTreeController> self_ptr, const std::string &schema_filter, const std::string &object_filter,
  wb::LiveSchemaTree::NewSchemaContentArrivedSlot arrived_slot) {
  RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR(SqlEditorTreeController, self_ptr, self, grt::StringRef(""))

  logDebug3("Searching data for %s.%s\n", schema_filter.c_str(), object_filter.c_str());

  std::shared_ptr<sql::ResultSet> dbc_resultset;
  std::map<std::string, int> schema_directory;
  std::string last_schema;

  std::string wb_internal_schema = bec::GRTManager::get()->get_app_option_string("workbench:InternalSchema");

  try {
    // Creates the template for the sqlstring
    std::string procedure(base::sqlstring("CALL !.SEARCH_OBJECTS(?,?,0)", 0) << wb_internal_schema << schema_filter
                                                                             << object_filter);

    // Gets the data
    std::string error = _owner->fetch_data_from_stored_procedure(procedure, dbc_resultset);

    if (dbc_resultset && !error.length()) {
      StringListPtr tables(new std::list<std::string>());
      StringListPtr views(new std::list<std::string>());
      StringListPtr procedures(new std::list<std::string>());
      StringListPtr functions(new std::list<std::string>());

      // Creates the needed schema/objects
      while (dbc_resultset->next()) {
        std::string schema = dbc_resultset->getString(1);
        std::string object = dbc_resultset->getString(2);
        std::string type = dbc_resultset->getString(3);

        // A schema change occurred, need to create the structure for the data loaded so far
        if (schema != last_schema && last_schema != "") {
          if (arrived_slot)
            bec::GRTManager::get()->run_once_when_idle(
              this, std::bind(arrived_slot, last_schema, tables, views, procedures, functions, true));

          tables->clear();
          views->clear();
          procedures->clear();
          functions->clear();
        }

        last_schema = schema;

        if (type == "T")
          tables->push_back(object);
        else if (type == "V")
          views->push_back(object);
        else if (type == "P")
          procedures->push_back(object);
        else
          functions->push_back(object);
      }

      if (last_schema != "" && arrived_slot)
        bec::GRTManager::get()->run_once_when_idle(
          this, std::bind(arrived_slot, last_schema, tables, views, procedures, functions, true));
    } else {
      std::string userName = _owner->connection_descriptor()->parameterValues().get_string("userName");

      std::string msgFmt =
        _("The user %s has no privileges on %s to create temporal tables or execute required stored procedures "
          "used in remote search in this server.\n"
          "Ensure your database administrator grants you full access to the schema %s and retry.\n\n%s.");

      std::string message = base::strfmt(msgFmt.c_str(), userName.c_str(), wb_internal_schema.c_str(),
                                         wb_internal_schema.c_str(), error.c_str());

      mforms::Utilities::show_error(_("Search Objects in Server"), message, _("Ok"));
    }
  }

  CATCH_ANY_EXCEPTION_AND_DISPATCH_TO_DEFAULT_LOG(_("Get data for filter"))

  return grt::StringRef("");
}

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

void SqlEditorTreeController::fetch_column_data(const std::string &schema_name, const std::string &obj_name,
                                                wb::LiveSchemaTree::ObjectType type,
                                                const wb::LiveSchemaTree::NodeChildrenUpdaterSlot &updater_slot) {
  // Searches for the target node...
  mforms::TreeNodeRef node = _schema_tree->get_node_for_object(schema_name, type, obj_name);
  LiveSchemaTree::ViewData *pdata = NULL;

  if (node)
    pdata = dynamic_cast<LiveSchemaTree::ViewData *>(node->get_data());

  // Loads the information...
  StringListPtr columns(new std::list<std::string>);
  std::map<std::string, LiveSchemaTree::ColumnData> column_data;

  logDebug3("Fetching column data for %s.%s\n", schema_name.c_str(), obj_name.c_str());

  try {
    sql::Dbc_connection_handler::Ref conn;

    RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

    std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::unique_ptr<sql::ResultSet> rs(
      stmt->executeQuery(std::string(base::sqlstring("SHOW FULL COLUMNS FROM !.!", 0) << schema_name << obj_name)));

    while (rs->next()) {
      LiveSchemaTree::ColumnData col_node(type);
      std::string column_name = rs->getString(1);

      columns->push_back(column_name);

      std::string type = rs->getString(2);
      std::string collation = rs->isNull(3) ? "" : rs->getString(3);
      std::string nullable = rs->getString(4);
      std::string key = rs->getString(5);
      std::string default_value = rs->getString(6);
      std::string extra = rs->getString(7);

      base::replaceStringInplace(type, "unsigned", "UN");

      if (extra == "auto_increment")
        type += " AI";

      col_node.name = column_name;
      col_node.type = type;
      col_node.charset_collation = collation;
      col_node.is_pk = key == "PRI";
      col_node.is_id = (col_node.is_pk || (nullable == "NO" && key == "UNI"));
      col_node.is_idx = key != "";
      col_node.default_value = default_value;

      column_data[column_name] = col_node;
    }

    // If information was found, creates the TreeNode structure for it
    if (columns->size()) {
      // Creates the node if it didn't exist...
      if (!node) {
        node = _schema_tree->create_node_for_object(schema_name, type, obj_name);

        if (node)
          pdata = dynamic_cast<LiveSchemaTree::ViewData *>(node->get_data());
        else
          logWarning("Error fetching column information for '%s'.'%s'\n", schema_name.c_str(), obj_name.c_str());
      }

      if (pdata) {
        // Identifies the node that will be the parent for the loaded columns...
        mforms::TreeNodeRef target_parent;
        if (pdata->get_type() == LiveSchemaTree::Table) {
          target_parent = node->get_child(wb::LiveSchemaTree::TABLE_COLUMNS_NODE_INDEX);
          type = LiveSchemaTree::TableColumn;
        } else if (pdata->get_type() == LiveSchemaTree::View) {
          target_parent = node;
          type = LiveSchemaTree::ViewColumn;
        }

        if (target_parent) {
          updater_slot(target_parent, columns, type, false, false);

          for (int index = 0; index < target_parent->count(); index++) {
            mforms::TreeNodeRef child = target_parent->get_child(index);
            LiveSchemaTree::LSTData *pchilddata = dynamic_cast<LiveSchemaTree::LSTData *>(child->get_data());
            LiveSchemaTree::LSTData *psource = &column_data[child->get_string(0)];
            pchilddata->copy(psource);
          }

          pdata->columns_load_error = false;
          pdata->set_loaded_data(LiveSchemaTree::COLUMN_DATA);
          _schema_tree->notify_on_reload(target_parent);
        }
      }
    }
  } catch (const sql::SQLException &exc) {
    logWarning("Error fetching column information for '%s'.'%s': %s\n", schema_name.c_str(), obj_name.c_str(),
               exc.what());

    // Sets flag indicating error loading columns ( Used for broken views )
    if (pdata) {
      if (exc.getErrorCode() == 1356)
        pdata->columns_load_error = true;

      pdata->details = exc.what();

      _schema_tree->update_node_icon(node);
    }
  }
}

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

void SqlEditorTreeController::fetch_trigger_data(const std::string &schema_name, const std::string &obj_name,
                                                 wb::LiveSchemaTree::ObjectType type,
                                                 const wb::LiveSchemaTree::NodeChildrenUpdaterSlot &updater_slot) {
  // Loads the information...
  StringListPtr triggers(new std::list<std::string>);
  //  std::list<std::string> triggers;
  std::map<std::string, LiveSchemaTree::TriggerData> trigger_data_dict;

  try {
    sql::Dbc_connection_handler::Ref conn;

    RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

    std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::unique_ptr<sql::ResultSet> rs(
      stmt->executeQuery(std::string(base::sqlstring("SHOW TRIGGERS FROM ! LIKE ?", 0) << schema_name << obj_name)));

    while (rs->next()) {
      wb::LiveSchemaTree::TriggerData trigger_node;

      std::string name = rs->getString(1);
      trigger_node.event_manipulation = wb::LiveSchemaTree::internalize_token(rs->getString(2));
      trigger_node.timing = wb::LiveSchemaTree::internalize_token(rs->getString(5));

      triggers->push_back(name);
      trigger_data_dict[name] = trigger_node;
    }

    // If information was found, creates the TreeNode structure for it
    // Searches for the target node...
    mforms::TreeNodeRef node = _schema_tree->get_node_for_object(schema_name, type, obj_name);

    // Creates the node if it didn't exist...
    if (!node)
      node = _schema_tree->create_node_for_object(schema_name, type, obj_name);

    // Identifies the node that will be the parent for the loaded columns...
    mforms::TreeNodeRef target_parent = node->get_child(wb::LiveSchemaTree::TABLE_TRIGGERS_NODE_INDEX);
    updater_slot(target_parent, triggers, LiveSchemaTree::Trigger, false, false);

    for (int index = 0; index < target_parent->count(); index++) {
      mforms::TreeNodeRef child = target_parent->get_child(index);
      LiveSchemaTree::LSTData *pchilddata = dynamic_cast<LiveSchemaTree::LSTData *>(child->get_data());
      LiveSchemaTree::LSTData *psource = &trigger_data_dict[child->get_string(0)];
      pchilddata->copy(psource);
    }

    // Where there data or not the triggers were loaded
    LiveSchemaTree::ViewData *pdata = dynamic_cast<LiveSchemaTree::ViewData *>(node->get_data());
    pdata->set_loaded_data(LiveSchemaTree::TRIGGER_DATA);
    _schema_tree->notify_on_reload(target_parent);
  } catch (const sql::SQLException &exc) {
    logWarning("Error fetching trigger information for '%s'.'%s': %s\n", schema_name.c_str(), obj_name.c_str(),
              exc.what());
  }
}

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

void SqlEditorTreeController::fetch_index_data(const std::string &schema_name, const std::string &obj_name,
                                               wb::LiveSchemaTree::ObjectType type,
                                               const wb::LiveSchemaTree::NodeChildrenUpdaterSlot &updater_slot) {
  // Loads the information...
  StringListPtr indexes(new std::list<std::string>());
  std::map<std::string, LiveSchemaTree::IndexData> index_data_dict;

  try {
    sql::Dbc_connection_handler::Ref conn;

    RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

    std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::unique_ptr<sql::ResultSet> rs(
      stmt->executeQuery(std::string(base::sqlstring("SHOW INDEXES FROM !.!", 0) << schema_name << obj_name)));

    bool supportVisibility = _owner->rdbms_version().is_valid() && is_supported_mysql_version_at_least(_owner->rdbms_version(), 8, 0, 0);

    while (rs->next()) {
      LiveSchemaTree::IndexData index_data;

      std::string name = rs->getString(3);

      // Inserts the index to the list
      if (!index_data_dict.count(name)) {
        indexes->push_back(name);

        index_data.type = wb::LiveSchemaTree::internalize_token(rs->getString(11));
        index_data.unique = (rs->getInt(2) == 0);
        if (supportVisibility) {
          index_data.visible = rs->getString(14) == "YES";
        }

        index_data_dict[name] = index_data;
      }

      // Adds the column
      index_data_dict[name].columns.push_back(rs->getString(5));
    }

    // Searches for the target node...
    mforms::TreeNodeRef node = _schema_tree->get_node_for_object(schema_name, type, obj_name);

    // Creates the node if it didn't exist...
    if (!node)
      node = _schema_tree->create_node_for_object(schema_name, type, obj_name);

    // Identifies the node that will be the parent for the loaded indexes...
    mforms::TreeNodeRef target_parent = node->get_child(wb::LiveSchemaTree::TABLE_INDEXES_NODE_INDEX);
    updater_slot(target_parent, indexes, LiveSchemaTree::Index, false, false);

    for (int index = 0; index < target_parent->count(); index++) {
      mforms::TreeNodeRef child = target_parent->get_child(index);
      LiveSchemaTree::LSTData *pchilddata = dynamic_cast<LiveSchemaTree::LSTData *>(child->get_data());
      LiveSchemaTree::LSTData *psource = &index_data_dict[child->get_string(0)];
      pchilddata->copy(psource);
    }

    LiveSchemaTree::ViewData *pdata = dynamic_cast<LiveSchemaTree::ViewData *>(node->get_data());
    pdata->set_loaded_data(LiveSchemaTree::INDEX_DATA);
    _schema_tree->notify_on_reload(target_parent);
  } catch (const sql::SQLException &exc) {
    logWarning("Error fetching index information for '%s'.'%s': %s\n", schema_name.c_str(), obj_name.c_str(), exc.what());
  }
}

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

void SqlEditorTreeController::fetch_foreign_key_data(const std::string &schema_name, const std::string &obj_name,
                                                     wb::LiveSchemaTree::ObjectType type,
                                                     const wb::LiveSchemaTree::NodeChildrenUpdaterSlot &updater_slot) {
  StringListPtr foreign_keys(new std::list<std::string>());
  std::map<std::string, LiveSchemaTree::FKData> fk_data_dict;

  sql::Dbc_connection_handler::Ref conn;

  RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

  try {
    std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::unique_ptr<sql::ResultSet> rs(
      stmt->executeQuery(std::string(base::sqlstring("SHOW CREATE TABLE !.!", 0) << schema_name << obj_name)));

    while (rs->next()) {
      std::string statement = rs->getString(2);

      size_t def_start = statement.find("(");
      size_t def_end = statement.rfind(")");

      std::vector<std::string> def_lines = base::split(statement.substr(def_start, def_end - def_start), "\n");

      const std::string pattern =
        "CONSTRAINT\\s*(\\S*)\\s*FOREIGN KEY\\s*\\((\\S*)\\)\\s*REFERENCES\\s*(\\S*)\\s*\\((\\S*)\\)\\s*((\\w*\\s*)*),?$";

      std::regex selfRegex(pattern, std::regex_constants::icase);

      std::string fk_name;
      std::string fk_columns;
      std::string fk_ref_table;
      std::string fk_ref_columns;
      std::string fk_rules;

      for (size_t index = 0; index < def_lines.size(); index++) {
        auto resultBegin = std::sregex_iterator(def_lines[index].begin(), def_lines[index].end(), selfRegex);
        auto resultEnd = std::sregex_iterator();
        auto count = std::distance(resultBegin, resultEnd);
        if (count > 0) {
          
          for (auto iter = resultBegin; iter != resultEnd; ++iter) {
            std::smatch match = *iter;
            for (int i = 0; i < match.length(); ++i) {
              switch (i) {
                case 1:
                  fk_name = match[i].str();
                  fk_name = base::unquote_identifier(fk_name);
                  break;
                case 2:
                  fk_columns = match[i].str();
                  break;
                case 3:
                  fk_ref_table = match[i].str();
                  fk_ref_table = base::unquote_identifier(fk_ref_table);
                  ;
                  break;
                case 4:
                  fk_ref_columns = match[i].str();
                  break;
                case 5:
                  fk_rules = match[i].str();
                  break;
                default:
                  break;
              }

            }
          }
          
          // Parses the list fields
          std::vector<std::string> fk_column_list = base::split(fk_columns, ",");
          std::vector<std::string> fk_ref_column_list = base::split(fk_ref_columns, ",");
          std::vector<std::string> fk_rule_tokens = base::split(fk_rules, " ");

          // Create the foreign key node
          wb::LiveSchemaTree::FKData new_fk;
          foreign_keys->push_back(fk_name);
          new_fk.referenced_table = (fk_ref_table);

          // Set the default update and delete rules
          new_fk.update_rule = new_fk.delete_rule = wb::LiveSchemaTree::internalize_token("RESTRICT");

          // A rule has at least 3 tokens so the number of tokens could be
          // 3 or 4 for 1 rule and 6,7,8 for two rules, so we get the number with this
          size_t rule_count = fk_rule_tokens.size() / 3;

          int token_offset = 0;
          for (size_t index = 0; index < rule_count; index++) {
            // Skips the ON token
            token_offset++;

            // Gets the UPDATE/DELETE token
            std::string rule = fk_rule_tokens[token_offset++];

            // Gets the action
            std::string action = fk_rule_tokens[token_offset++];

            if (action == "SET" || action == "NO")
              action += " " + fk_rule_tokens[token_offset++];

            const unsigned char value = wb::LiveSchemaTree::internalize_token(action);

            if (rule == "UPDATE")
              new_fk.update_rule = value;
            else
              new_fk.delete_rule = value;
          }

          std::string from(""), to("");
          for (size_t column_index = 0; column_index < fk_column_list.size(); column_index++) {
            std::string from_col = base::unquote_identifier(fk_column_list[column_index]);
            std::string to_col = base::unquote_identifier(fk_ref_column_list[column_index]);

            if (from.empty())
              from = from_col;
            else
              from.append(", ").append(from_col);
            if (to.empty())
              to = to_col;
            else
              to.append(", ").append(to_col);
          }
          new_fk.from_cols = from;
          new_fk.to_cols = to;
          fk_data_dict[fk_name] = new_fk;
        }
      }
    }

    // Searches for the target node...
    mforms::TreeNodeRef node = _schema_tree->get_node_for_object(schema_name, type, obj_name);

    // Creates the node if it didn't exist...
    if (!node)
      node = _schema_tree->create_node_for_object(schema_name, type, obj_name);

    // Identifies the node that will be the parent for the loaded columns...
    mforms::TreeNodeRef target_parent = node->get_child(wb::LiveSchemaTree::TABLE_FOREIGN_KEYS_NODE_INDEX);
    updater_slot(target_parent, foreign_keys, LiveSchemaTree::ForeignKey, false, false);

    for (int index = 0; index < target_parent->count(); index++) {
      mforms::TreeNodeRef child = target_parent->get_child(index);
      LiveSchemaTree::LSTData *pchilddata = dynamic_cast<LiveSchemaTree::LSTData *>(child->get_data());
      LiveSchemaTree::LSTData *psource = &fk_data_dict[child->get_string(0)];
      pchilddata->copy(psource);
    }

    LiveSchemaTree::ViewData *pdata = dynamic_cast<LiveSchemaTree::ViewData *>(node->get_data());
    pdata->set_loaded_data(LiveSchemaTree::FK_DATA);
    _schema_tree->notify_on_reload(target_parent);
  } catch (const sql::SQLException &exc) {
    logWarning("Error fetching foreign key information for '%s'.'%s': %s\n", schema_name.c_str(), obj_name.c_str(),
              exc.what());
  }
}

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

bool SqlEditorTreeController::fetch_object_details(const std::string &schema_name, const std::string &object_name,
                                                   wb::LiveSchemaTree::ObjectType type, short flags,
                                                   const wb::LiveSchemaTree::NodeChildrenUpdaterSlot &updater_slot) {
  // If the type has not been specified, pulls it from the database
  // Most of the time the type will be specified as it can bee retrieved from
  // the LST, this is to handle the case when a query is executed using direct SQL
  // and the tree hasn't been populated with the target object
  if (type == wb::LiveSchemaTree::Any)
    type = fetch_object_type(schema_name, object_name);

  if (type != wb::LiveSchemaTree::Any) {
    if (flags & wb::LiveSchemaTree::COLUMN_DATA)
      fetch_column_data(schema_name, object_name, type, updater_slot);

    if (flags & wb::LiveSchemaTree::INDEX_DATA)
      fetch_index_data(schema_name, object_name, type, updater_slot);

    if (flags & wb::LiveSchemaTree::TRIGGER_DATA)
      fetch_trigger_data(schema_name, object_name, type, updater_slot);

    if (flags & wb::LiveSchemaTree::FK_DATA)
      fetch_foreign_key_data(schema_name, object_name, type, updater_slot);
  }

  return false;
}

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

bool SqlEditorTreeController::fetch_routine_details(const std::string &schema_name, const std::string &obj_name,
                                                    wb::LiveSchemaTree::ObjectType type) {
  bool ret_val = false;
  std::string object = type == LiveSchemaTree::Function ? "FUNCTION" : "PROCEDURE";
  std::string statement = "SHOW CREATE " + object + " !.!";
  // Loads the information...
  std::list<std::string> indexes;
  std::map<std::string, LiveSchemaTree::IndexData> index_data_dict;

  try {
    sql::Dbc_connection_handler::Ref conn;

    RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

    std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::unique_ptr<sql::ResultSet> rs(
      stmt->executeQuery(std::string(base::sqlstring(statement.c_str(), 0) << schema_name << obj_name)));

    if (rs->next()) {
      LiveSchemaTree::IndexData index_data;

      std::string ddl = rs->getString(3);

      SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms(_owner->rdbms());
      SqlFacade::String_tuple_list parameters;
      std::string ddl_type, ddl_name, ddl_ret, ddl_comments;
      ddl = "DELIMITER " + bec::GRTManager::get()->get_app_option_string("SqlDelimiter", "$$") + "\n" + ddl;
      sql_facade->parseRoutineDetails(ddl, ddl_type, ddl_name, parameters, ddl_ret, ddl_comments);

      std::string details = "";
      if (parameters.size()) {
        details = _("<b>Parameters:</b>");
        details += "<table border=0>";

        SqlFacade::String_tuple_list::iterator index, end = parameters.end();
        for (index = parameters.begin(); index != end; index++) {
          details += "<tr><td style=\"border:none; padding-left: 15px;\">" + index->first + ":</td>";
          details +=
            "<td style=\"border:none; padding-left: 15px;\"><font color='#717171'>" + index->second + "</td></tr>";
        }

        details += "</table>";
      }

      if (!ddl_ret.empty())
        details += "<br><br><b><font color='#000000'>Returns: </b><font color='#717171'>" + ddl_ret;

      if (!ddl_comments.empty())
        details += "<br><br><b><font color='#000000'>Comments: </b><font color='#717171'>" + ddl_comments;

      LiveSchemaTree::ObjectData new_data;
      new_data.details = details; // ddl.substr(start, end - start);
      new_data.fetched = true;

      // Searches for the target node...
      mforms::TreeNodeRef node = _schema_tree->get_node_for_object(schema_name, type, obj_name);

      // The node should exist as this method is only called for a selected node
      if (node) {
        LiveSchemaTree::ObjectData *ptargetdata = dynamic_cast<LiveSchemaTree::ObjectData *>(node->get_data());
        ptargetdata->copy(&new_data);
        ret_val = true;
      }
    }
  } catch (const sql::SQLException &exc) {
    logWarning("Error fetching routine information for '%s'.'%s': %s\n", schema_name.c_str(), obj_name.c_str(),
              exc.what());
  }

  return ret_val;
}

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

wb::LiveSchemaTree::ObjectType SqlEditorTreeController::fetch_object_type(const std::string &schema_name,
                                                                          const std::string &obj_name) {
  wb::LiveSchemaTree::ObjectType type = wb::LiveSchemaTree::Any;

  try {
    MutexLock schema_contents_mutex(_schema_contents_mutex);

    {
      sql::Dbc_connection_handler::Ref conn;
      RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

      {
        std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
        std::unique_ptr<sql::ResultSet> rs(
          stmt->executeQuery(std::string(sqlstring("SHOW FULL TABLES FROM ! LIKE ?", 0) << schema_name << obj_name)));
        while (rs->next()) {
          std::string str_type = rs->getString(2);

          if (str_type == "VIEW")
            type = wb::LiveSchemaTree::View;
          else
            type = wb::LiveSchemaTree::Table;
        }
      }
    }
  }
  CATCH_ANY_EXCEPTION_AND_DISPATCH_TO_DEFAULT_LOG(_("Get schema contents"));

  return type;
}

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

void SqlEditorTreeController::tree_refresh() {
  if (_owner->connected()) {
    live_schemata_refresh_task->exec(false, std::bind((grt::StringRef(SqlEditorTreeController::*)(SqlEditorForm::Ptr)) &
                                                        SqlEditorTreeController::do_refresh_schema_tree_safe,
                                                      this, weak_ptr_from(_owner)));
    _schema_tree->set_enabled(true);
  } else {
    _schema_tree->set_no_connection();
    _schema_tree->set_enabled(false);
  }
}

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

bool SqlEditorTreeController::sidebar_action(const std::string &name) {
  return false;
}

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

/**
 * Activate one or more objects. The term "activate" is a bit misleading as we do other operations too
 * (like clipboard handling).
 */
void SqlEditorTreeController::tree_activate_objects(const std::string &action,
                                                    const std::vector<wb::LiveSchemaTree::ChangeRecord> &changes) {
  // Most of the activations should lead to a single result (e.g. all clipboard ops go into one string).
  std::string action_modifier; // action can contain prefix denoting action modifier
  std::string real_action =
    action; // if action modifier is present real_action will store part after w/o modifier prefix

  if (real_action == "select_data_columns") {
    typedef std::string TableName;
    typedef std::string ColumnName;
    std::map<TableName, std::map<ColumnName, std::string> > table_column_types;
    std::string text;
    typedef std::map<TableName, std::string> TableStringMap;
    TableStringMap first_text;
    TableStringMap second_text;

    // cache default values for tables
    for (size_t i = 0; i < changes.size(); i++) {
      TableName full_table_name = sqlstring("!.!", 0) << changes[i].schema << changes[i].name;
      std::string column_type;

      if (!first_text[full_table_name].empty())
        first_text[full_table_name].append(", ");
      first_text[full_table_name].append(changes[i].detail);
    }

    for (const TableStringMap::value_type &table_columns : first_text)
      text += strfmt("SELECT %s\nFROM %s;\n", table_columns.second.c_str(), table_columns.first.c_str());

    _owner->run_sql_in_scratch_tab(text, false, true);
  }

  try {
    for (size_t i = 0; i < changes.size(); i++) {
      std::string sql;
      switch (changes[i].type) {
        case LiveSchemaTree::Schema:
          if (real_action == "filter") {
            _schema_side_bar->get_filter_entry()->set_value(changes[i].name);
            (*_schema_side_bar->get_filter_entry()->signal_changed())();
          } else if (real_action == "inspect")
            _owner->inspect_object(changes[i].name, "", "db.Schema");
          else if (real_action == "alter")
            do_alter_live_object(LiveSchemaTree::Schema, changes[i].name, changes[i].name);
          else
            _owner->active_schema(changes[i].name);
          break;

        case LiveSchemaTree::Table:
          if (real_action == "activate" || real_action == "edit_data" || real_action == "select_data")
            sql = sqlstring("SELECT * FROM !.!;", base::QuoteOnlyIfNeeded) << changes[i].schema << changes[i].name;
          else if (real_action == "inspect")
            _owner->inspect_object(changes[i].schema, changes[i].name, "db.Table");
          else if (real_action == "alter")
            do_alter_live_object(changes[i].type, changes[i].schema, changes[i].name);
          break;

        case LiveSchemaTree::View:
          if (real_action == "activate" || real_action == "edit_data" || real_action == "select_data")
            sql = sqlstring("SELECT * FROM !.!;", base::QuoteOnlyIfNeeded) << changes[i].schema << changes[i].name;
          else if (real_action == "inspect")
            _owner->inspect_object(changes[i].schema, changes[i].name, "db.View");
          else if (real_action == "alter")
            do_alter_live_object(changes[i].type, changes[i].schema, changes[i].name);
          break;

        case LiveSchemaTree::Procedure:
        case LiveSchemaTree::Function:
          if (real_action == "alter")
            do_alter_live_object(changes[i].type, changes[i].schema, changes[i].name);
          else if (real_action == "execute")
            sql = run_execute_routine_wizard(changes[i].type, changes[i].schema, changes[i].name);
          break;

        default:
          break;
      }

      if (!sql.empty()) {
        bool _autosave = _owner->get_autosave_disabled();
        _owner->set_autosave_disabled(true);
        SqlEditorPanel *ed = _owner->run_sql_in_scratch_tab(sql, false, true);
        if (ed)
          ed->set_title(changes[i].name);
        _owner->set_autosave_disabled(_autosave);
      }
    }
  } catch (std::exception &exc) {
    mforms::Utilities::show_error("Error", exc.what(), "OK");
  }
}

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

/**
 * Convenience API for the activation interface.
 */
void SqlEditorTreeController::schema_object_activated(const std::string &action, wb::LiveSchemaTree::ObjectType type,
                                                      const std::string &schema, const std::string &name) {
  std::vector<wb::LiveSchemaTree::ChangeRecord> changes;
  wb::LiveSchemaTree::ChangeRecord record = {type, schema, name, ""};
  changes.push_back(record);
  tree_activate_objects(action, changes);
}

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

void SqlEditorTreeController::do_alter_live_object(wb::LiveSchemaTree::ObjectType type, const std::string &schema_name,
                                                   const std::string &aobj_name) {
  std::string used_schema_name = schema_name;
  std::string obj_name = aobj_name;
  try {
    db_mgmt_RdbmsRef rdbms = _owner->rdbms();
    // std::string database_package= *rdbms->databaseObjectPackage();

    if (rdbms.is_valid()) {
      rdbms = grt::shallow_copy_object(rdbms);
      rdbms->version(grt::shallow_copy_object(_owner->rdbms_version()));
      rdbms->version()->owner(rdbms);
    }

    // reset_references on the catalog is called when we try to apply changes (generate the alter script).
    db_mysql_CatalogRef client_state_catalog = grt::GRT::get()->create_object<db_mysql_Catalog>("db.mysql.Catalog");
    client_state_catalog->name("default");
    client_state_catalog->oldName("default");
    client_state_catalog->version(rdbms->version());
    grt::replace_contents(client_state_catalog->simpleDatatypes(), rdbms->simpleDatatypes());
    grt::replace_contents(client_state_catalog->characterSets(), rdbms->characterSets());
    // XXX this should be changed when/if global userDatatypes are added
    // XXX    grt::replace_contents(client_state_catalog->userDatatypes(),
    // XXX workbench_physical_ModelRef::cast_from(_live_physical_overview->get_model())->catalog()->userDatatypes());

    db_mysql_SchemaRef schema;
    if (wb::LiveSchemaTree::Schema != type) {
      if (used_schema_name == "")
        used_schema_name = _owner->active_schema();

      if (used_schema_name == "") {
        mforms::Utilities::show_warning(
          strfmt(_("No Schema Selected")),
          _("A default schema must be set by double clicking its name in the SCHEMA list."), _("OK"));

        return;
      } else {
        schema = grt::GRT::get()->create_object<db_mysql_Schema>("db.mysql.Schema");
        schema->owner(client_state_catalog);

        schema->name(used_schema_name);
        schema->oldName(used_schema_name);
        client_state_catalog->schemata().insert(schema);
        client_state_catalog->defaultSchema(schema);
      }
    }

    bool is_object_new = obj_name.empty();

    std::string ddl_script;
    std::string sql_mode;
    if (!is_object_new) {
      // parse selected object DDL into auxiliary catalog
      ddl_script = get_object_ddl_script(type, used_schema_name, obj_name);
      if (ddl_script.empty()) {
        logWarning("Unable to get DDL for %s.%s\n", used_schema_name.c_str(), obj_name.c_str());
        return;
      }
      {
        sql::Dbc_connection_handler::Ref conn;
        RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));
        if (conn)
          _owner->get_session_variable(conn->ref.get(), "sql_mode", sql_mode);
      }

      // if this is a View, then auto-reformat it before sending it to parser/editor
      if (type == wb::LiveSchemaTree::View &&
          bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ReformatViewDDL", 0)) {
        try {
          grt::Module *module = grt::GRT::get()->get_module("SQLIDEUtils");
          grt::BaseListRef args(true);
          args.ginsert(grt::StringRef(ddl_script));
          ddl_script = grt::StringRef::cast_from(module->call_function("reformatSQLStatement", args));
        } catch (std::exception &exc) {
          logWarning("Error reformatting view code: %s\n", exc.what());
        }
      }

      if (!parse_ddl_into_catalog(client_state_catalog, strfmt("`%s`.`%s`", schema_name.c_str(), obj_name.c_str()),
                                  ddl_script, sql_mode, schema_name)) {
        logWarning("Error parsing DDL for %s.%s: %s\n", schema_name.c_str(), obj_name.c_str(), ddl_script.c_str());
        return;
      }
    }

    // Make a copy of the catalog before we modify it with new/edited objects,
    // so we can later do a diff between both to find changes.
    db_CatalogRef server_state_catalog = grt::copy_object(client_state_catalog);

    // "reset_references" for the created object is called when the base editor is destroyed.
    db_DatabaseObjectRef db_object;
    switch (type) {
      case wb::LiveSchemaTree::Schema:
        db_object = is_object_new
                      ? create_new_schema(client_state_catalog)
                      : db_SchemaRef::cast_from(find_named_object_in_list(client_state_catalog->schemata(), obj_name));
        break;
      case wb::LiveSchemaTree::Table:
        db_object = is_object_new ? create_new_table(schema)
                                  : db_TableRef::cast_from(find_named_object_in_list(schema->tables(), obj_name));
        break;
      case wb::LiveSchemaTree::View:
        db_object = is_object_new ? create_new_view(schema)
                                  : db_ViewRef::cast_from(find_named_object_in_list(schema->views(), obj_name));
        break;
      case wb::LiveSchemaTree::Procedure:
      case wb::LiveSchemaTree::Function:
        db_object = is_object_new ? create_new_routine(schema, type)
                                  : db_RoutineRef::cast_from(find_named_object_in_list(schema->routines(), obj_name));
        break;
      default:
        break;
    }

    if (db_object.is_valid()) {
      db_object->customData().set("sqlMode", grt::StringRef(sql_mode));
      db_object->customData().set("originalObjectDDL", grt::StringRef(ddl_script));
      open_alter_object_editor(db_object, server_state_catalog);
    } else
      logError("Failed to create/alter `%s`.`%s`", used_schema_name.c_str(), obj_name.c_str());
  } catch (const std::exception &e) {
    logError("Failed to create/alter `%s`.`%s`: %s", used_schema_name.c_str(), obj_name.c_str(), e.what());
    mforms::Utilities::show_error(
      strfmt(_("Failed to create/alter `%s`.`%s`"), used_schema_name.c_str(), obj_name.c_str()), e.what(), _("OK"));
  }
}

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

void SqlEditorTreeController::open_alter_object_editor(db_DatabaseObjectRef object,
                                                       db_CatalogRef server_state_catalog) {
  db_CatalogRef client_state_catalog;
  if (db_SchemaRef::can_wrap(object)) {
    if (!object->owner().is_valid())
      throw std::invalid_argument("schema object does not have owner set to expected value");
    client_state_catalog = db_CatalogRef::cast_from(object->owner());
  } else {
    if (!object->owner().is_valid())
      throw std::invalid_argument("object does not have owner set to expected schema value");
    if (!object->owner()->owner().is_valid())
      throw std::invalid_argument("object does not have owner set to expected schema value");
    client_state_catalog = db_CatalogRef::cast_from(object->owner()->owner());
  }

  sql::Dbc_connection_handler::Ref conn;
  grt::NormalizedComparer comparer;
  {
    RecMutexLock lock(_owner->ensure_valid_aux_connection(conn));
    // db_object->customData().set("CaseSensitive",grt::IntegerRef(conn->ref->getMetaData()->storesMixedCaseIdentifiers()));
    // TODO use DB_Plugin here somehow
    comparer.load_db_options(conn->ref->getMetaData());
  }

  db_mgmt_RdbmsRef rdbms = _owner->rdbms();
  // std::string database_package= *rdbms->databaseObjectPackage();

  if (rdbms.is_valid()) {
    rdbms = grt::shallow_copy_object(rdbms);
    rdbms->version(grt::shallow_copy_object(_owner->rdbms_version()));
    rdbms->version()->owner(rdbms);
  }

  if (!client_state_catalog->version().is_valid())
    client_state_catalog->version(rdbms->version());
  if (!server_state_catalog->version().is_valid())
    server_state_catalog->version(rdbms->version());

  object->customData().set("DBSettings", comparer.get_options_dict());
  object->customData().set("liveRdbms", _owner->rdbms());
  object->customData().set("ownerSqlEditor", _owner->wbsql()->get_grt_editor_object(_owner));

  object->customData().set("clientStateCatalog", client_state_catalog);
  object->customData().set("serverStateCatalog", server_state_catalog);

  // TODO: make docking/non-docking switchable via preferences.
  //_context_ui->get_wb()->open_object_editor(db_object, bec::StandaloneWindowFlag);
  bec::GRTManager::get()->open_object_editor(object, bec::ForceNewWindowFlag);
}

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

std::string SqlEditorTreeController::run_execute_routine_wizard(wb::LiveSchemaTree::ObjectType type,
                                                                const std::string &schema_name,
                                                                const std::string &obj_name) {
  std::pair<std::string, std::string> script = get_object_create_script(type, schema_name, obj_name);
  if (script.second.empty())
    return ""; // get_object_create_script() already showed an error.

  db_mysql_RoutineRef routine(grt::Initialized);
  parsers::MySQLParserServices::Ref services = parsers::MySQLParserServices::get();

  db_mysql_CatalogRef catalog(grt::Initialized);
  catalog->version(_owner->rdbms_version());
  grt::replace_contents(catalog->simpleDatatypes(), _owner->rdbms()->simpleDatatypes());

  db_mysql_SchemaRef schema(grt::Initialized);
  schema->owner(catalog);
  schema->name(schema_name);
  catalog->schemata().insert(schema);

  routine->owner(schema);
  schema->routines().insert(routine);

  std::string previous_sql_mode;
  std::string sql_mode = _owner->work_parser_context()->sqlMode();
  if (!script.first.empty()) {
    previous_sql_mode = sql_mode;
    sql_mode = script.first;
    _owner->work_parser_context()->updateSqlMode(script.first);
  }

  size_t error_count = services->parseRoutine(_owner->work_parser_context(), routine, script.second);

  if (!previous_sql_mode.empty())
    _owner->work_parser_context()->updateSqlMode(previous_sql_mode);

  if (error_count > 0) {
    logWarning("Error parsing SQL code for %s.%s:\n%s\n", schema_name.c_str(), obj_name.c_str(), script.second.c_str());

    std::vector<ParserErrorInfo> errors = _owner->work_parser_context()->errorsWithOffset(0);
    mforms::Utilities::show_error(_("Error parsing sql code for object"), errors[0].message, "OK");
    return "";
  }

  ExecuteRoutineWizard wizard(routine, sql_mode);
  wizard.center();
  return wizard.run();
}

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

db_SchemaRef SqlEditorTreeController::create_new_schema(db_CatalogRef owner) {
  db_SchemaRef object = grt::GRT::get()->create_object<db_Schema>(owner->schemata()->content_type_spec().object_class);
  object->owner(owner);
  object->name("new_schema");
  owner->schemata().insert(object);
  owner->defaultSchema(object);
  return object;
}

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

db_TableRef SqlEditorTreeController::create_new_table(db_SchemaRef owner) {
  db_TableRef object = grt::GRT::get()->create_object<db_Table>(owner->tables()->content_type_spec().object_class);
  object->owner(owner);
  object->name("new_table");
  owner->tables().insert(object);
  return object;
}

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

db_ViewRef SqlEditorTreeController::create_new_view(db_SchemaRef owner) {
  db_ViewRef object = grt::GRT::get()->create_object<db_View>(owner->views()->content_type_spec().object_class);
  object->owner(owner);
  object->name("new_view");
  owner->views().insert(object);
  return object;
}

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

db_RoutineRef SqlEditorTreeController::create_new_routine(db_SchemaRef owner, wb::LiveSchemaTree::ObjectType type) {
  db_RoutineRef object =
    grt::GRT::get()->create_object<db_Routine>(owner->routines()->content_type_spec().object_class);
  object->owner(owner);

  if (type == wb::LiveSchemaTree::Procedure) {
    object->name("new_procedure");
    object->routineType("procedure");
  } else if (type == wb::LiveSchemaTree::Function) {
    object->name("new_function");
    object->routineType("function");
  }

  owner->routines().insert(object);
  return object;
}

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

void SqlEditorTreeController::tree_create_object(wb::LiveSchemaTree::ObjectType type, const std::string &schema_name,
                                                 const std::string &obj_name) {
  do_alter_live_object(type, schema_name, obj_name);
}

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

/**
 * Generates an alter script for the given db object using the specified online DDL options.
 * These are however only applied if the server version is >= 5.6.
 */
std::string SqlEditorTreeController::generate_alter_script(const db_mgmt_RdbmsRef &rdbms,
                                                           db_DatabaseObjectRef db_object, std::string algorithm,
                                                           std::string lock) {
  DbMySQLImpl *diffsql_module = grt::GRT::get()->find_native_module<DbMySQLImpl>("DbMySQL");

  db_CatalogRef server_cat = db_CatalogRef::cast_from(db_object->customData().get("serverStateCatalog"));
  db_CatalogRef client_cat = db_CatalogRef::cast_from(db_object->customData().get("clientStateCatalog"));

  db_CatalogRef client_cat_copy = db_CatalogRef::cast_from(grt::copy_object(client_cat));
  db_CatalogRef server_cat_copy = db_CatalogRef::cast_from(grt::copy_object(server_cat));

  grt::DictRef diff_options(true);
  // diff_options.set("CaseSensitive",db_object->customData().get("CaseSensitive"));
  grt::DictRef db_settings = grt::DictRef::cast_from(db_object->customData().get("DBSettings"));
  if (_owner->rdbms_version().is_valid() && is_supported_mysql_version_at_least(_owner->rdbms_version(), 5, 6)) {
    db_settings.gset("AlterAlgorithm", algorithm != "DEFAULT" ? algorithm : "");
    db_settings.gset("AlterLock", lock != "DEFAULT" ? lock : "");
  }
  diff_options.set("DBSettings", db_settings);

  std::string alter_script =
    diffsql_module->makeAlterScriptForObject(server_cat_copy, client_cat_copy, db_object, diff_options);
  client_cat_copy->reset_references();
  server_cat_copy->reset_references();

  return alter_script;
}

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

std::string SqlEditorTreeController::get_object_ddl_script(wb::LiveSchemaTree::ObjectType type,
                                                           const std::string &schema_name,
                                                           const std::string &obj_name) {
  std::string delimiter =  bec::GRTManager::get()->get_app_option_string("SqlDelimiter", "$$");
  std::string ddl_script = "delimiter " + delimiter + "\n\n";

  // Triggers are fetched prior to table ddl, but should appear after table created.
  std::string additional_ddls;

  try {
    sql::Dbc_connection_handler::Ref conn;
    std::string query;

    RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

    // Can't use getSchemaObjects() because it silently ignores errors.
    switch (type) {
      case wb::LiveSchemaTree::Schema:
        query = base::sqlstring("SHOW CREATE SCHEMA !", 0) << obj_name;
        break;

      case wb::LiveSchemaTree::Table: {
        // triggers
        std::vector<std::string> triggers;
        {
          std::string trigger_query = base::sqlstring("SHOW TRIGGERS FROM ! WHERE ! = ?", 0) << schema_name << "Table"
                                                                                             << obj_name;
          std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
          std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(trigger_query));

          if (rs.get()) {
            while (rs->next())
              triggers.push_back(rs->getString(1));
          }
        }

        for (size_t index = 0; index < triggers.size(); index++) {
          std::string trigger_query = base::sqlstring("SHOW CREATE TRIGGER !.!", 0) << schema_name << triggers[index];
          std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
          std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(trigger_query));

          if (rs.get() && rs->next()) {
            std::string trigger_ddl = (rs->getString(3));
            additional_ddls += trigger_ddl;
            additional_ddls += delimiter + "\n\n";
          }
        }
      }

        query = base::sqlstring("SHOW CREATE TABLE !.!", 0) << schema_name << obj_name;
        break;

      case wb::LiveSchemaTree::View:
        query = base::sqlstring("SHOW CREATE VIEW !.!", 0) << schema_name << obj_name;
        break;

      case wb::LiveSchemaTree::Procedure:
        query = base::sqlstring("SHOW CREATE PROCEDURE !.!", 0) << schema_name << obj_name;
        break;

      case wb::LiveSchemaTree::Function:
        query = base::sqlstring("SHOW CREATE FUNCTION !.!", 0) << schema_name << obj_name;
        break;

      default:
        break;
    }

    std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(query));

    // Note: show create procedure includes the sql mode in the result before the actual DDL.
    if (rs.get() && rs->next()) {
      if (type == wb::LiveSchemaTree::Function || type == wb::LiveSchemaTree::Procedure)
        ddl_script += rs->getString(3) + delimiter + "\n\n";
      else
        ddl_script += rs->getString(2) + delimiter + "\n\n";
    }
    ddl_script += additional_ddls;
  } catch (const sql::SQLException &e) {
    // Error 1356 comes up when any of the referenced tables in a view are invalid (e.g. dropped)
    // or the definer/invoker has no rights to access it.
    // Solve this by using the I_S.
    if (type == wb::LiveSchemaTree::View && e.getErrorCode() == 1356) {
      sql::Dbc_connection_handler::Ref conn;
      std::string query, view;
      RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));
      query = base::sqlstring(
                "SELECT DEFINER, SECURITY_TYPE, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = ? "
                "AND TABLE_NAME = ?",
                0)
              << schema_name << obj_name;
      std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
      std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(query));

      if (rs.get() && rs->next()) {
        std::string view, definer;
        std::vector<std::string> definer_tokens = base::split(rs->getString(1), "@", 2);

        view = base::sqlstring("!.!", 0) << schema_name << obj_name;
        definer = base::sqlstring("!@!", 0) << definer_tokens[0] << definer_tokens[1];
        ddl_script += "CREATE ALGORITHM=UNDEFINED DEFINER=" + definer;
        ddl_script += " SQL SECURITY " + rs->getString(2);
        ddl_script += " VIEW " + view + " AS ";
        ddl_script += rs->getString(3) + delimiter + "\n\n";
      }
    } else {
      ddl_script.clear();
      std::string err = e.what();
      logError("Error getting SQL definition for %s.%s: %s\n", schema_name.c_str(), obj_name.c_str(), e.what());
      if (bec::GRTManager::get()->in_main_thread())
        mforms::Utilities::show_error("Error getting DDL for object", e.what(), "OK", "", "");
      else
        bec::GRTManager::get()->run_once_when_idle(
          std::bind(&mforms::Utilities::show_error, "Error getting DDL for object", err, "OK", "", ""));
    }
  }
  return ddl_script;
}

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

/**
 * Retrieves the original DDL text that was used to create the object.
 * Returns a tuple of <sql_mode, script>. The sql mode is what was used to create the object,
 * if it is a routine. Otherwise this value is empty.
 */
std::pair<std::string, std::string> SqlEditorTreeController::get_object_create_script(
  wb::LiveSchemaTree::ObjectType type, const std::string &schema_name, const std::string &obj_name) {
  std::pair<std::string, std::string> result;

  try {
    sql::Dbc_connection_handler::Ref conn;
    std::string query;

    RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

    // cant use getSchemaObjects() because it silently ignores errors
    switch (type) {
      case wb::LiveSchemaTree::Schema:
        query = base::sqlstring("SHOW CREATE SCHEMA !", 0) << obj_name;
        break;

      case wb::LiveSchemaTree::Table:
        query = base::sqlstring("SHOW CREATE TABLE !.!", 0) << schema_name << obj_name;
        break;

      case wb::LiveSchemaTree::View:
        query = base::sqlstring("SHOW CREATE VIEW !.!", 0) << schema_name << obj_name;
        break;

      case wb::LiveSchemaTree::Procedure:
        query = base::sqlstring("SHOW CREATE PROCEDURE !.!", 0) << schema_name << obj_name;
        break;

      case wb::LiveSchemaTree::Function:
        query = base::sqlstring("SHOW CREATE FUNCTION !.!", 0) << schema_name << obj_name;
        break;

      default:
        break;
    }

    std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(query));

    if (rs.get() && rs->next()) {
      if (type == wb::LiveSchemaTree::Function || type == wb::LiveSchemaTree::Procedure) {
        result.first = rs->getString(2);
        result.second = rs->getString(3);
      } else
        result.second = rs->getString(2);
    }
  } catch (const sql::SQLException &e) {
    if (type == wb::LiveSchemaTree::View && e.getErrorCode() == 1356) {
      // Error for not being allowed to run SHOW CREATE VIEW. Use I_S instead to get the code.
      sql::Dbc_connection_handler::Ref conn;
      std::string query, view;
      RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));
      query = base::sqlstring(
                "SELECT DEFINER, SECURITY_TYPE, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = ? "
                "AND TABLE_NAME = ?",
                0)
              << schema_name << obj_name;
      std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
      std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(query));

      if (rs.get() && rs->next()) {
        std::string view, definer;
        std::vector<std::string> definer_tokens = base::split(rs->getString(1), "@", 2);

        view = base::sqlstring("!.!", 0) << schema_name << obj_name;
        definer = base::sqlstring("!@!", 0) << definer_tokens[0] << definer_tokens[1];
        result.second = "CREATE ALGORITHM = UNDEFINED DEFINER = " + definer;
        result.second += " SQL SECURITY " + rs->getString(2);
        result.second += " VIEW " + view + " AS\n";
        result.second += rs->getString(3);
      }
    } else {
      logError("Error getting SQL definition for %s.%s: %s\n", schema_name.c_str(), obj_name.c_str(), e.what());
      mforms::Utilities::show_error("Error getting DDL for object", e.what(), "OK");
    }
  }

  return result;
}

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

/**
 *	Returns a list of trigger create scripts for the given table.
 */
std::vector<std::string> SqlEditorTreeController::get_trigger_sql_for_table(const std::string &schema_name,
                                                                            const std::string &table_name) {
  std::vector<std::string> result;

  try {
    sql::Dbc_connection_handler::Ref conn;
    RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

    std::vector<std::string> triggers;
    {
      std::string trigger_query = base::sqlstring("SHOW TRIGGERS FROM ! WHERE ! = ?", 0) << schema_name << "Table"
                                                                                         << table_name;
      std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
      std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(trigger_query));

      if (rs.get()) {
        while (rs->next())
          triggers.push_back(rs->getString(1));
      }
    }

    for (size_t index = 0; index < triggers.size(); index++) {
      std::string trigger_query = base::sqlstring("SHOW CREATE TRIGGER !.!", 0) << schema_name << triggers[index];
      std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
      std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(trigger_query));

      if (rs.get() && rs->next())
        result.push_back(rs->getString(3));
    }
  } catch (const sql::SQLException &e) {
    logError("Error getting SQL definition for %s.%s: %s\n", schema_name.c_str(), table_name.c_str(), e.what());
    mforms::Utilities::show_error("Error getting DDL for object", e.what(), "OK");
  }

  return result;
}

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

void SqlEditorTreeController::refresh_live_object_in_editor(bec::DBObjectEditorBE *obj_editor, bool using_old_name) {
  db_DatabaseObjectRef db_object = obj_editor->get_dbobject();

  db_mysql_CatalogRef client_state_catalog =
    db_mysql_CatalogRef::cast_from(db_object->customData().get("clientStateCatalog"));

  std::string obj_name = using_old_name ? db_object->oldName() : db_object->name();
  // don't refresh new objects that where not applied yet
  if (obj_name.empty())
    return;
  obj_editor->freeze_refresh_on_object_change();

  if (obj_name != *db_object->name())
    db_object->name(obj_name);
  obj_editor->thaw_refresh_on_object_change(true);

  std::string schema_name = db_SchemaRef::can_wrap(db_object) ? std::string() : *db_object->owner()->name();
  db_SchemaRef schema;
  if (!schema_name.empty())
    schema = db_SchemaRef::cast_from(db_object->owner());

  wb::LiveSchemaTree::ObjectType db_object_type = wb::LiveSchemaTree::Any;

  if (db_SchemaRef::can_wrap(db_object)) {
    db_object_type = wb::LiveSchemaTree::Schema;
  } else {
    if (db_TableRef::can_wrap(db_object)) {
      db_object_type = wb::LiveSchemaTree::Table;

      // reset selection of fkeys/indices to avoid warnings
      bec::TableEditorBE *table_editor = dynamic_cast<bec::TableEditorBE *>(obj_editor);
      table_editor->get_fks()->select_fk(NodeId());
      table_editor->get_indexes()->select_index(NodeId());
    } else if (db_ViewRef::can_wrap(db_object)) {
      db_object_type = wb::LiveSchemaTree::View;
    } else if (db_RoutineRef::can_wrap(db_object)) {
      db_RoutineRef db_routine = db_RoutineRef::cast_from(db_object);
      std::string obj_type = db_routine->routineType();

      if (obj_type == "function")
        db_object_type = wb::LiveSchemaTree::Function;
      else
        db_object_type = wb::LiveSchemaTree::Procedure;
    }
  }

  obj_editor->freeze_refresh_on_object_change();
  client_state_catalog->schemata().remove_all(); // Clean up. We only want the single object in it, which we edit.

  // reparse object's DDL
  std::string ddl_script;
  std::string sql_mode;
  {
    ddl_script = get_object_ddl_script(db_object_type, schema_name, obj_name);
    if (!ddl_script.empty()) {
      if (db_object_type == wb::LiveSchemaTree::View &&
          bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ReformatViewDDL", 0)) {
        try {
          grt::Module *module = grt::GRT::get()->get_module("SQLIDEUtils");
          grt::BaseListRef args(true);
          args.ginsert(grt::StringRef(ddl_script));
          ddl_script = grt::StringRef::cast_from(module->call_function("reformatSQLStatement", args));
        } catch (std::exception &exc) {
          logWarning("Error reformatting view code: %s\n", exc.what());
        }
      }

      try {
        sql::Dbc_connection_handler::Ref conn;
        RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));
        if (conn)
          _owner->get_session_variable(conn->ref.get(), "sql_mode", sql_mode);
      }
      CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Get 'sql_mode' session variable"));

      parse_ddl_into_catalog(client_state_catalog, strfmt("`%s`.`%s`", schema_name.c_str(), obj_name.c_str()),
                             ddl_script, sql_mode, schema_name);
    }
  }

  // Settings dict here only to copy it to the new db object.
  grt::DictRef dbSettings = grt::DictRef::cast_from(db_object->customData().get("DBSettings"));
  switch (db_object_type) {
    case wb::LiveSchemaTree::Table:
      if ((client_state_catalog->schemata()->count() > 0) &&
          (client_state_catalog->schemata()[0]->tables()->count() > 0))
        db_object = client_state_catalog->schemata()[0]->tables()[0];
      break;
    case wb::LiveSchemaTree::View:
      if ((client_state_catalog->schemata()->count() > 0) &&
          (client_state_catalog->schemata()[0]->views()->count() > 0))
        db_object = client_state_catalog->schemata()[0]->views()[0];
      break;
    case wb::LiveSchemaTree::Procedure:
    case wb::LiveSchemaTree::Function:
      if ((client_state_catalog->schemata()->count() > 0) &&
          (client_state_catalog->schemata()[0]->routines()->count() > 0))
        db_object = client_state_catalog->schemata()[0]->routines()[0];
      break;
    default: // wb::LiveSchemaTree::Schema, there are more cases, but we only use those listed here.
      if (client_state_catalog->schemata()->count() > 0)
        db_object = client_state_catalog->schemata()[0];
      break;
  }

  // The current name becomes now also the old name, so that we create alter scripts from the current name,
  // not the one from before this change.
  db_object->oldName(obj_name);

  {
    db_CatalogRef server_state_catalog(db_CatalogRef::cast_from(grt::copy_object(client_state_catalog)));
    db_object->customData().set("serverStateCatalog", server_state_catalog);
    db_object->customData().set("clientStateCatalog", client_state_catalog);
  }
  db_object->customData().set("originalObjectDDL", grt::StringRef(ddl_script));
  db_object->customData().set("sqlMode", grt::StringRef(sql_mode));

  db_object->customData().set("DBSettings", dbSettings);
  db_object->customData().set("liveRdbms", _owner->rdbms());
  db_object->customData().set("ownerSqlEditor", _owner->wbsql()->get_grt_editor_object(_owner));

  obj_editor->thaw_refresh_on_object_change();

  // Update the editor's grt object (the one it is managing), as this has now been
  // recreated. Will cause a UI refresh as well. If there was a parse error (which can only be
  // an internal error, since we start freshly) stay with the old object.
  obj_editor->set_object(db_object);

  // Enable refresh of sql editor contents.
  MySQLEditor::Ref active_sql_editor;
  if (obj_editor->has_editor())
    active_sql_editor = obj_editor->get_sql_editor();
  if (active_sql_editor)
    active_sql_editor->set_refresh_enabled(true);
}

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

bool SqlEditorTreeController::parse_ddl_into_catalog(db_mysql_CatalogRef catalog, const std::string &objectDescription,
                                                     const std::string &sql, std::string sqlMode,
                                                     const std::string &schema) {
  std::string currentSqlMode = _owner->work_parser_context()->sqlMode();

  grt::DictRef options(true);
  options.set("reuse_existing_objects", grt::IntegerRef(1));
  options.set("schema", grt::StringRef(schema));

  if (!sqlMode.empty())
    _owner->work_parser_context()->updateSqlMode(sqlMode);

  parsers::MySQLParserServices::Ref services = parsers::MySQLParserServices::get();
  size_t errorCount = services->parseSQLIntoCatalog(_owner->work_parser_context(), catalog, sql, options);

  bool haveErrors = false;

  if (options.has_key("sql_mode") && (errorCount > 0)) {
    if (sqlMode.find("ANSI_QUOTES") != std::string::npos)
      sqlMode = base::replaceString(sqlMode, "ANSI_QUOTES", "");
    else
      sqlMode += ", ANSI_QUOTES";
    _owner->work_parser_context()->updateSqlMode(sqlMode);

    errorCount = services->parseSQLIntoCatalog(_owner->work_parser_context(), catalog, sql, options);
    _owner->work_parser_context()->updateSqlMode(currentSqlMode);

    if (errorCount == 0) { // Error(s) solved by new sql mode -> inconsistency.
      if (mforms::Utilities::show_warning(
            strfmt(_("Error Parsing DDL for %s"), objectDescription.c_str()),
            _("The object's DDL retrieved from the server is inconsistent with respect to the SQL_MODE variable "
              "set for the connection. In particular the current state of the ANSI_QUOTES flag contradicts "
              "the value set when the object had been created. This may lead to errors when trying to "
              "apply changes. As a workaround you may want to temporarily change the SQL_MODE variable "
              "to its previous value.\nDo you want to view the DDL or cancel processing it?"),
            _("View DDL"), _("Cancel")) == mforms::ResultOk) {
        _owner->new_sql_scratch_area();
        insert_text_to_active_editor(sql);
      }
      return false;
    } else
      haveErrors = true;
  } else
    haveErrors = errorCount > 0;

  _owner->work_parser_context()->updateSqlMode(currentSqlMode);
  if (haveErrors) {
    if (mforms::Utilities::show_error(strfmt(_("Error Parsing DDL for %s"), objectDescription.c_str()),
                                      _("There was an error while parsing the DDL retrieved from the server.\n"
                                        "Do you want to view the DDL or cancel processing it?"),
                                      _("View DDL"), _("Cancel")) == mforms::ResultOk) {
      _owner->new_sql_scratch_area();
      insert_text_to_active_editor(sql);
    }
    return false;
  }

  return true;
}

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

bool SqlEditorTreeController::apply_changes_to_object(bec::DBObjectEditorBE *obj_editor, bool dry_run) {
  std::string log_descr;
  RowId log_id = -1;
  if (!dry_run) {
    log_descr = strfmt(_("Apply changes to %s"), obj_editor->get_name().c_str());
    log_id = _owner->add_log_message(DbSqlEditorLog::BusyMsg, "Applying object changes ...", log_descr, "");
  }
  try {
    if (!dry_run && obj_editor->has_editor() && obj_editor->get_sql_editor()->has_sql_errors()) {
      // We won't be able to apply with errors. Inform the user and return false.
      mforms::Utilities::show_error(_("Apply Changes to Object"),
                                    _("The object's DDL statement contains syntax errors.\n"
                                    "You cannot modify this object until you fix the errors."),
                                    _("OK"));
      return false;
    }

    db_DatabaseObjectRef db_object = obj_editor->get_dbobject();

    if (!dry_run) {
      ValueRef hasErrors = db_object->customData().get("triggerInvalid");
      if (hasErrors.is_valid() && (IntegerRef::cast_from(hasErrors) != 0)) {
        int res = mforms::Utilities::show_warning(_("Apply Changes to Object"),
                                                  _("The tables's trigger SQL code contains errors.\n"
                                                    "This will lead to invalid sql generated.\n"
                                                    "Are you sure you want to apply the DDL statement as is?"),
                                                  _("Yes"), _("No"));

        if (res != mforms::ResultOk) {
          _owner->set_log_message(log_id, DbSqlEditorLog::ErrorMsg, "Cancelled", log_descr, "");
          return false;
        }
      }

      // check for name conflicts
      // if the object is new or its name was changed
      std::string obj_name = db_object->name();
      std::string obj_old_name = db_object->oldName();
      if (_owner->lower_case_table_names() != 0) // if 1 or 2, treat everything as case insensitive
      {
        obj_name = tolower(obj_name);
        obj_old_name = tolower(obj_old_name);
        if (_owner->lower_case_table_names() == 1 && obj_name != *db_object->name() &&
            (db_TableRef::can_wrap(db_object) || db_ViewRef::can_wrap(db_object) ||
             db_SchemaRef::can_wrap(db_object))) // server will force everything to be lowercase
        {
          mforms::Utilities::show_message_and_remember(
            "Apply Changes to Object",
            base::strfmt("The server is configured with lower_case_table_names=1, which only allows lowercase "
                         "characters in schema and table names.\nThe object will be created as `%s`.",
                         obj_name.c_str()),
            "OK", "", "", "sqlide:lower_case_table_names", "");
          db_object->name(obj_name);
        }
      }
      // now here
      if (obj_name != obj_old_name) {
        std::list<std::string> obj_types;
        std::list<std::string> validation_queries;

        std::string schema_name = db_SchemaRef::can_wrap(db_object) ? std::string() : *db_object->owner()->name();

        {
          if (db_SchemaRef::can_wrap(db_object)) {
            obj_types.push_back("schema");
            validation_queries.push_back(sqlstring("SHOW DATABASES LIKE ?", 0) << db_object->name());
          } else if (db_TableRef::can_wrap(db_object) || db_ViewRef::can_wrap(db_object)) {
            obj_types.push_back("table");
            obj_types.push_back("view");
            std::string tables_format = base::strfmt(
              "SHOW FULL TABLES FROM ! WHERE `Tables_in_%s` = ? AND Table_type != 'VIEW'", schema_name.c_str());
            std::string views_format = base::strfmt(
              "SHOW FULL TABLES FROM ! WHERE `Tables_in_%s` = ? AND Table_type = 'VIEW'", schema_name.c_str());

            validation_queries.push_back(sqlstring(tables_format.c_str(), 0) << schema_name << db_object->name());
            validation_queries.push_back(sqlstring(views_format.c_str(), 0) << schema_name << db_object->name());
          } else if (db_RoutineRef::can_wrap(db_object)) {
            db_RoutineRef db_routine = db_RoutineRef::cast_from(db_object);

            std::string type = db_routine->routineType();
            if (type == "function")
              validation_queries.push_back(sqlstring("SHOW FUNCTION STATUS WHERE Db=? AND NAME = ?", 0)
                                           << schema_name << db_routine->name());
            else
              validation_queries.push_back(sqlstring("SHOW PROCEDURE STATUS WHERE Db=? AND NAME = ?", 0)
                                           << schema_name << db_routine->name());

            obj_types.push_back(type);
          }
        }

        sql::Dbc_connection_handler::Ref conn;

        RecMutexLock lock(_owner->ensure_valid_aux_connection(conn));

        for (const std::string &obj_type : obj_types) {
          std::string query = validation_queries.front();
          validation_queries.pop_front();

          std::unique_ptr<sql::ResultSet> rs(conn->ref->createStatement()->executeQuery(query));
          if (rs->next()) {
            mforms::Utilities::show_error(_("Apply Changes to Object"),
                                          strfmt(_("Selected name conflicts with existing %s `%s`."), obj_type.c_str(),
                                                 (*db_object->name()).c_str()),
                                          _("OK"));
            _owner->set_log_message(log_id, DbSqlEditorLog::ErrorMsg,
                                    strfmt(_("Selected name conflicts with existing %s `%s`."), obj_type.c_str(),
                                           (*db_object->name()).c_str()),
                                    log_descr, "");
            return false;
          }
        }
      }
    }

    // Generate the initial version of the alter script. This might be altered in the wizard
    // depending on the online DDL options.
    std::string algorithm;
    wb::WBContextUI::get()->get_wb_options_value("", "DbSqlEditor:OnlineDDLAlgorithm", algorithm);
    std::string lock;
    wb::WBContextUI::get()->get_wb_options_value("", "DbSqlEditor:OnlineDDLLock", lock);
    std::string alter_script = generate_alter_script(_owner->rdbms(), db_object, algorithm, lock);

    // The alter_script may contain a dummy USE statement.
    if (alter_script.empty() ||
        (alter_script.find("CREATE") == std::string::npos && alter_script.find("ALTER") == std::string::npos &&
         alter_script.find("DROP") == std::string::npos)) {
      if (!dry_run && !_owner->on_sql_script_run_error.empty())
        _owner->on_sql_script_run_error(log_id, _("No changes to object were detected."), "");
      if (!dry_run) {
        _owner->set_log_message(log_id, DbSqlEditorLog::NoteMsg, _("No changes detected"), log_descr, "");

        // Because of message throttling the previous log message doesn't cause a refresh of the UI
        // (it comes quicker than the throttling timeout). So we explicitly do a refresh.
        _owner->refresh_log_messages(true);
      }
      return false; // no changes detected
    }

    if (dry_run)
      return true; // some changes were detected

    return _owner->run_live_object_alteration_wizard(alter_script, obj_editor, log_id, log_descr);
  } catch (const std::exception &e) {
    if (!_owner->on_sql_script_run_error.empty())
      _owner->on_sql_script_run_error(log_id, e.what(), "");
    _owner->set_log_message(log_id, DbSqlEditorLog::ErrorMsg, e.what(), log_descr, "");
    logError("Exception applying changes to live object: %s\n", e.what());
  }
  return true; // some changes were detected and applied
}

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

void SqlEditorTreeController::create_live_table_stubs(bec::DBObjectEditorBE *table_editor) {
  db_DatabaseObjectRef db_object = table_editor->get_dbobject();
  if (db_object->customData().has_key("isLiveTableListLoaded"))
    return;

  try {
    sql::Dbc_connection_handler::Ref conn;

    RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));

    db_CatalogRef catalog = table_editor->get_catalog();
    grt::ListRef<db_Schema> schemata = catalog->schemata();
    db_SchemaRef schema;
    grt::ListRef<db_Table> tables;
    db_TableRef table;

    std::string database_package = *_owner->rdbms()->databaseObjectPackage();
    std::string schema_typename = database_package + ".Schema";
    std::string table_typename = database_package + ".Table";

    std::string prev_schema_name;

    std::shared_ptr<sql::ResultSet> rs;
    {
      std::string schema_name = db_SchemaRef::cast_from(db_object->owner())->name();
      std::list<sql::SQLString> table_types;
      table_types.push_back("TABLE");
      rs.reset(conn->ref->getMetaData()->getTables("", schema_name, "%", table_types));
    }
    while (rs->next()) {
      std::string schema_name = rs->getString(2);
      std::string table_name = rs->getString(3);
      if (prev_schema_name != schema_name) {
        schema = find_named_object_in_list(schemata, schema_name);
        if (!schema.is_valid()) {
          schema = grt::GRT::get()->create_object<db_Schema>(schema_typename);
          schema->owner(catalog);
          schema->name(schema_name);
          schema->oldName(schema_name);
          schema->modelOnly(1);
          schemata.insert(schema);
        }
        tables = schema->tables();
        prev_schema_name = schema_name;
      }
      table = find_named_object_in_list(tables, table_name);
      if (!table.is_valid()) {
        table = grt::GRT::get()->create_object<db_Table>(table_typename);
        table->owner(schema);
        table->name(table_name);
        table->oldName(table_name);
        table->modelOnly(1);
        table->isStub(1);
        tables.insert(table);
      }
    }

    db_object->customData().set("isLiveTableListLoaded", IntegerRef(1));
  }
  CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Create live table stub"));
}

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

bool SqlEditorTreeController::expand_live_table_stub(bec::DBObjectEditorBE *table_editor,
                                                     const std::string &schema_name, const std::string &obj_name) {
  db_CatalogRef catalog = table_editor->get_catalog();
  db_TableRef table;
  db_SchemaRef schema = find_named_object_in_list(catalog->schemata(), schema_name);
  if (schema.is_valid()) {
    table = find_named_object_in_list(schema->tables(), obj_name);
    if (table.is_valid() && table->customData().has_key("isStubExpanded"))
      return true; // stub table has already been expanded
  }

  std::string ddl_script = get_object_ddl_script(wb::LiveSchemaTree::Table, schema_name, obj_name);
  if (ddl_script.empty())
    return false;

  {
    SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms(_owner->rdbms());
    Sql_parser::Ref sql_parser = sql_facade->sqlParser();
    sql_parser->messages_enabled(false);
    grt::DictRef options(true);
    {
      std::string sql_mode;
      sql::Dbc_connection_handler::Ref conn;
      RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));
      if (conn && _owner->get_session_variable(conn->ref.get(), "sql_mode", sql_mode))
        options.gset("sql_mode", sql_mode);
      else
        logWarning("Unable to get sql_mode for connection\n");
    }
    db_SchemaRef old_default_schema = catalog->defaultSchema();
    if (!schema.is_valid()) {
      // target schema doesn't exist yet, create a stub for it
      schema = db_mysql_SchemaRef(grt::Initialized);
      schema->owner(catalog);
      schema->name(schema_name);
      schema->comment("stub");
      catalog->schemata().insert(schema);
    }
    catalog->defaultSchema(schema);
    sql_parser->parse_sql_script(catalog, ddl_script, options);
    catalog->defaultSchema(old_default_schema);
  }

  // find parsed table
  if (!schema.is_valid())
    schema = find_named_object_in_list(catalog->schemata(), schema_name);
  if (!table.is_valid() && schema.is_valid())
    table = find_named_object_in_list(schema->tables(), obj_name);

  if (table.is_valid() && table != table_editor->get_dbobject()) {
    table->modelOnly(0);
    table->isStub(1);
    table->customData().set("isStubExpanded", IntegerRef(1));
  }

  return table.is_valid();
}

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

bool SqlEditorTreeController::activate_live_object(GrtObjectRef object) {
  std::string obj_name = *object->name();
  std::string owner_name = *object->owner()->name();

  if (db_SchemaRef::can_wrap(object))
    schema_object_activated("activate", LiveSchemaTree::Schema, "", obj_name);
  else if (db_TableRef::can_wrap(object))
    schema_object_activated("activate", LiveSchemaTree::Table, owner_name, obj_name);
  else if (db_ViewRef::can_wrap(object))
    schema_object_activated("activate", LiveSchemaTree::View, owner_name, obj_name);
  else if (db_RoutineRef::can_wrap(object)) {
    db_RoutineRef routine = db_RoutineRef::cast_from(object);
    std::string type = routine->routineType();

    if (type == "function")
      schema_object_activated("activate", LiveSchemaTree::Function, owner_name, obj_name);
    else
      schema_object_activated("activate", LiveSchemaTree::Procedure, owner_name, obj_name);
  } else
    return false;

  return true;
}

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

void SqlEditorTreeController::on_active_schema_change(const std::string &schema) {
  _base_schema_tree.set_active_schema(schema);
  _filtered_schema_tree.set_active_schema(schema);

  if (_schema_side_bar != NULL)
    bec::GRTManager::get()->run_once_when_idle(
      this, std::bind(&mforms::View::set_needs_repaint, _schema_side_bar->get_schema_tree()));
}

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

void SqlEditorTreeController::mark_busy(bool busy) {
  if (_schema_side_bar != NULL)
    _schema_side_bar->mark_section_busy("", busy);
}

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

grt::StringRef SqlEditorTreeController::do_refresh_schema_tree_safe(SqlEditorForm::Ptr self_ptr) {
  RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR(SqlEditorForm, self_ptr, self, grt::StringRef(""))

  if (_is_refreshing_schema_tree)
    return grt::StringRef("");

  _is_refreshing_schema_tree = true;
  StringListPtr schema_list(new std::list<std::string>());

  std::vector<std::string> schemaList = fetch_schema_list();
  _owner->schemaListRefreshed(schemaList);

  schema_list->assign(schemaList.begin(), schemaList.end());
  bec::GRTManager::get()->run_once_when_idle(this,
                                             std::bind(&LiveSchemaTree::update_schemata, _schema_tree, schema_list));
  bec::GRTManager::get()->run_once_when_idle(this, std::bind(&SqlEditorForm::schema_tree_did_populate, _owner));

  _is_refreshing_schema_tree = false;

  return grt::StringRef("");
}

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

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

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

void SqlEditorTreeController::handle_grt_notification(const std::string &name, grt::ObjectRef sender,
                                                      grt::DictRef info) {
  if (name == "GRNDBObjectEditorCreated") {
    grt::ValueRef object = info.get("object");
    bec::DBObjectEditorBE *editor =
      dynamic_cast<bec::DBObjectEditorBE *>(bec::UIForm::form_with_id(info.get_string("form")));
    if (editor && db_DatabaseObjectRef::can_wrap(object)) {
      db_DatabaseObjectRef obj(db_DatabaseObjectRef::cast_from(object));
      if (obj->customData().get("ownerSqlEditor") == _owner->wbsql()->get_grt_editor_object(_owner)) {
        editor->on_apply_changes_to_live_object = std::bind(&SqlEditorTreeController::apply_changes_to_object, this,
                                                            std::placeholders::_1, std::placeholders::_2);
        editor->on_refresh_live_object =
          std::bind(&SqlEditorTreeController::refresh_live_object_in_editor, this, std::placeholders::_1, true);
        editor->on_create_live_table_stubs =
          std::bind(&SqlEditorTreeController::create_live_table_stubs, this, std::placeholders::_1);
        editor->on_expand_live_table_stub =
          std::bind(&SqlEditorTreeController::expand_live_table_stub, this, std::placeholders::_1,
                    std::placeholders::_2, std::placeholders::_3);
      }
    }
  } else if (name == "GRNSQLEditorReconnected") {
    if (sender == _owner->wbsql()->get_grt_editor_object(_owner)) {
      _session_info->set_markup_text(_owner->get_connection_info());
      tree_refresh();
    }
  } else if (name == "GNColorsChanged") {
    updateColors();
  }
}

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

const std::string objectInfoStyles = "<style>"
  "body { font-family: '" DEFAULT_FONT_FAMILY "'; color: »color1«; }"
  "tr.heading { font-weight: 700; }"
  "td.name { color: »color2«; }"
  "td.value { font-weight: 700; color: »color2«;}"
  "table { width: 100%; white-space: nowrap; }"
  "</style>"
;

void SqlEditorTreeController::updateColors() {
  _schema_side_bar->set_selection_color(base::HighlightColor);
  _side_splitter->set_back_color(base::Color::getApplicationColorAsString(AppColorMainBackground, false));

#ifdef _MSC_VER
  _object_info->set_back_color(base::Color::getApplicationColorAsString(base::AppColorPanelContentArea, false));
  _session_info->set_back_color(base::Color::getApplicationColorAsString(base::AppColorPanelContentArea, false));
#elif __APPLE__
  _object_info->set_back_color(base::Color::getSystemColor(base::WindowBackgroundColor).to_html());
  _session_info->set_back_color(base::Color::getSystemColor(base::WindowBackgroundColor).to_html());
  _schema_side_bar->set_back_color(base::Color::getSystemColor(base::WindowBackgroundColor).to_html());
#else
  _object_info->set_back_color("#ebebeb");
  _session_info->set_back_color("#ebebeb");
#endif

  schema_row_selected(); // Refresh object HTML.
  _object_info->set_needs_repaint();

#ifdef __linux__
  _session_info->set_markup_text(_owner->get_connection_info());
#else
  Color textColor = base::Color::getSystemColor(base::LabelColor);
  std::string html = base::replaceString(objectInfoStyles, "»color1«", textColor.to_html());
  textColor.alpha = 0.75;
  base::replaceStringInplace(html, "»color2«", textColor.to_html());
  html = "<html><head>" + html + "</head>";
  html += _owner->get_connection_info() + "</html>";
  _session_info->set_markup_text(html);
#endif

  _session_info->set_needs_repaint();
}

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

int SqlEditorTreeController::insert_text_to_active_editor(const std::string &str) {
  SqlEditorPanel *editor(_owner->active_sql_editor_panel());
  if (editor) {
    editor->editor_be()->insert_text(str);
    editor->editor_be()->focus();
  }
  return 0;
}

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

void SqlEditorTreeController::context_menu_will_show(mforms::MenuItem *parent_item) {
  if (!parent_item) {
    grt::DictRef info(true);

    db_query_EditorRef sender(_owner->wbsql()->get_grt_editor_object(_owner));

    grt::ListRef<db_query_LiveDBObject> selection(
      grt::ListRef<db_query_LiveDBObject>::cast_from(_schema_tree->get_selected_objects()));

    info.set("menu", mforms_to_grt(_schema_side_bar->get_context_menu()));
    info.gset("menu-plugins-index", _schema_side_bar->get_context_menu()->get_item_index(
                                      _schema_side_bar->get_context_menu()->find_item("refresh")) -
                                      2);
    info.set("selection", selection);

    grt::GRTNotificationCenter::get()->send_grt("GRNLiveDBObjectMenuWillShow", sender, info);
  }
}

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