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

#include "stdafx.h"

#include "grtpp_util.h"
#include "wb_sql_editor_tree_controller.h"
#include "wb_sql_editor_form.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 "workbench/wb_command_ui.h"
#include "workbench/wb_context_ui.h"

#include <boost/foreach.hpp>
#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_object_helpers.h"
#include "grtsqlparser/sql_facade.h"

#include "wb_db_schema.h"

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

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

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, "");\
log_error("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, "");\
log_error("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, "");\
log_error("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)\
{\
_grtm->get_grt()->send_error(strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), statement);\
}\
catch (std::exception &e)\
{\
_grtm->get_grt()->send_error(strfmt(EXCEPTION_MSG_FORMAT, e.what()), statement);\
}


boost::shared_ptr<SqlEditorTreeController> SqlEditorTreeController::create(SqlEditorForm *owner)
{
  boost::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), _grtm(owner->grt_manager()),
_side_bar(NULL),
_schema_tree(&_base_schema_tree),
_base_schema_tree(_grtm->get_grt()),
_filtered_schema_tree(_grtm->get_grt()),
live_schema_fetch_task(GrtThreadedTask::create(_grtm)),
live_schemata_refresh_task(GrtThreadedTask::create(_grtm)),
_is_refreshing_schema_tree(false),
_side_splitter(NULL),
_info_tabview(NULL),
_object_info(NULL),
_session_info(NULL)
{
  grt::GRTNotificationCenter::get()->add_grt_observer(this, "GRNDBObjectEditorCreated");
  
  _schema_contents_mutex= g_mutex_new();
  
  _base_schema_tree.is_schema_contents_enabled(_grtm->get_app_option_int("DbSqlEditor:ShowSchemaTreeSchemaContents", 1) != 0);
  _filtered_schema_tree.is_schema_contents_enabled(_grtm->get_app_option_int("DbSqlEditor:ShowSchemaTreeSchemaContents", 1) != 0);
  
  _base_schema_tree.sql_editor_text_insert_signal.connect(boost::bind(&SqlEditorTreeController::insert_text_to_active_editor, this, _1));
  _filtered_schema_tree.sql_editor_text_insert_signal.connect(boost::bind(&SqlEditorTreeController::insert_text_to_active_editor, this, _1));
  
  _base_schema_tree.object_plugin_items_slot= boost::bind(&SqlEditorTreeController::get_live_catalog_menu_items, this, _1, _2, _3);
  _filtered_schema_tree.object_plugin_items_slot= boost::bind(&SqlEditorTreeController::get_live_catalog_menu_items, this, _1, _2, _3);
  _base_schema_tree.call_object_plugin_items_slot= boost::bind(&SqlEditorTreeController::call_live_catalog_menu_item, this, _1, _2, _3, _4);
  _filtered_schema_tree.call_object_plugin_items_slot= boost::bind(&SqlEditorTreeController::call_live_catalog_menu_item, this, _1, _2, _3, _4);
  
  live_schemata_refresh_task->send_task_res_msg(false);
  live_schemata_refresh_task->msg_cb(boost::bind(&SqlEditorForm::add_log_message, _owner, _1, _2, _3, ""));
  
  live_schema_fetch_task->send_task_res_msg(false);
  live_schema_fetch_task->msg_cb(boost::bind(&SqlEditorForm::add_log_message, _owner, _1, _2, _3, ""));
}


SqlEditorTreeController::~SqlEditorTreeController()
{
  grt::GRTNotificationCenter::get()->remove_grt_observer(this);
  g_mutex_free(_schema_contents_mutex);
  
  delete _info_tabview;
  delete _side_bar;
  if (_side_splitter != NULL)
    _side_splitter->release();
  
  delete _session_info;
  delete _object_info;  
}


void SqlEditorTreeController::finish_init()
{
  // Left hand side bar (schema browser, object info).
  _side_bar = mforms::TaskSidebar::create();
  
//#if !defined(_WIN32) && !defined(__APPLE__)
//  _side_bar->get_schema_tree()->set_name("LiveSchemaTree");
//#endif
  _side_bar->set_schema_model(&_base_schema_tree);

  _side_bar->set_filtered_schema_model(&_filtered_schema_tree);
  //_side_bar->set_selection_color(mforms::App::get()->get_system_color(mforms::SystemColorHighlight).to_html());
  _side_bar->set_selection_color(mforms::SystemColorHighlight);
  
  //  _side_bar->set_collapse_states(_options.get_string("DbSqlEditor:SidebarCollapseState"));
  
  _side_splitter = mforms::manage(new mforms::Splitter(false, true));
#ifdef _WIN32
  mforms::Panel* panel;
  _side_splitter->set_back_color("#283752");
  panel = mforms::manage(new mforms::Panel(mforms::StyledHeaderPanel));
  panel->set_title("Object Browser");
  panel->add(_side_bar);
  _side_splitter->add(panel, 200);
#else
  _side_splitter->add(_side_bar, 200);
#endif
  
  _info_tabview = new mforms::TabView(mforms::TabViewSelectorSecondary);
  
#ifdef _WIN32
  panel = mforms::manage(new mforms::Panel(mforms::StyledHeaderPanel));
  panel->set_title(_("Information"));
  panel->add(_info_tabview);
  _side_splitter->add(panel, 30);
#else
  _side_splitter->add(_info_tabview, 30);
#endif
  
  _object_info = new mforms::HyperText();
  _session_info = new mforms::HyperText();
#ifdef _WIN32
  _object_info->set_back_color("#ffffff");
  _session_info->set_back_color("#ffffff");
#else
  _object_info->set_back_color("#ebebeb");
  _session_info->set_back_color("#ebebeb");
#endif
  
  _info_tabview->add_page(_object_info, _("Object Info"));
  _info_tabview->add_page(_session_info, _("Session"));
  
  _session_info->set_markup_text(_owner->get_connection_info());
  
  _side_splitter->set_position(500);
  
  scoped_connect(_side_bar->signal_filter_changed(),boost::bind(&SqlEditorTreeController::side_bar_filter_changed, this, _1));
  scoped_connect(_side_bar->tree_node_selected(),boost::bind(&SqlEditorTreeController::schema_row_selected, this));
  
  
  // update the info box
  schema_row_selected();  

  tree_refresh();
}

void SqlEditorTreeController::prepare_close()
{
  if (_side_bar)
    _grtm->set_app_option("DbSqlEditor:SidebarCollapseState", grt::StringRef(_side_bar->get_collapse_states()));
}


void SqlEditorTreeController::schema_row_selected()
{
  std::list<mforms::TreeNodeRef> nodes;
  std::string details;
  
  if (_side_bar)
  {
    nodes = _side_bar->get_schema_tree()->get_selection();
    if (nodes.empty())
      details= "<b><font color=\"#aaa\">No object selected</font></b>";
    else if (nodes.size() > 1)
      details= "<b><font color=\"#aaa\">Multiple objects selected</font></b>";
    else
      details= _schema_tree->get_field_description(nodes.front());
    
    _object_info->set_markup_text(details);
  }
}

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;
}

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

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 = _grtm->get_app_option_string("workbench:InternalSchema");
  
  sql::Dbc_connection_handler::Ref conn;

  GMutexLock 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= !_grtm->in_main_thread();
    log_debug3("Fetch data for filter %s.%s\n", schema_filter.c_str(), object_filter.c_str());
    live_schema_fetch_task->exec(sync,
                                 boost::bind(&SqlEditorTreeController::do_fetch_data_for_filter, this, _1,
                                             weak_ptr_from(this), schema_filter, object_filter, arrived_slot));
  
  }

  return true;
}

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


std::list<std::string> SqlEditorTreeController::fetch_schema_list()
{
  std::list<std::string> schemata_names;
  try
  {
    sql::Dbc_connection_handler::Ref conn;
    GMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn));
    
    bool show_metadata_schemata= (0 != _grtm->get_app_option_int("DbSqlEditor:ShowMetadataSchemata", 0));
    
    std::auto_ptr<sql::ResultSet> rs(conn->ref->getMetaData()->getSchemata());
    while (rs->next())
    {
      std::string name= rs->getString(1);
      static std::map<std::string, bool> metadata_schemata_names;
      class MetadataSchemataNamesInitializer
      {
      public:
        MetadataSchemataNamesInitializer(std::map<std::string, bool> &metadata_schemata_names)
        {
          //! dbms-specific code
          metadata_schemata_names["information_schema"];
          metadata_schemata_names["performance_schema"];
          metadata_schemata_names["mysql"];
        }
      };
      static MetadataSchemataNamesInitializer metadata_schemata_initializer(metadata_schemata_names);
      
      if (show_metadata_schemata || 
         (metadata_schemata_names.end() == metadata_schemata_names.find(name) && 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= !_grtm->in_main_thread();
  log_debug3("Fetch schema contents for %s\n", schema_name.c_str());
  live_schema_fetch_task->exec(sync,
                               boost::bind(&SqlEditorTreeController::do_fetch_live_schema_contents, this, _1,
                                           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(grt::GRT *grt, boost::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
  {
    std::list<std::string> *tables = new std::list<std::string>();
    std::list<std::string> *views  = new std::list<std::string>();
    std::list<std::string> *procedures = new std::list<std::string>();
    std::list<std::string> *functions = new std::list<std::string>();
    std::vector<std::pair<std::string, bool> > table_list;
    std::vector<std::pair<std::string, bool> > routine_list;

    GMutexLock schema_contents_mutex(_schema_contents_mutex);
    
    if (arrived_slot.empty())
      return grt::StringRef("");
    
    {
      sql::Dbc_connection_handler::Ref conn;
      GMutexLock aux_dbc_conn_mutex= _owner->ensure_valid_aux_connection(conn);

      {
        std::auto_ptr<sql::ResultSet> rs(conn->ref->createStatement()->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);
          
          table_list.push_back(std::make_pair(name, type == "VIEW"));
        }
      }
      {
        std::auto_ptr<sql::ResultSet> rs(conn->ref->createStatement()->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);
          routine_list.push_back(std::make_pair(name, false));
        }
      }
      {
        std::auto_ptr<sql::ResultSet> rs(conn->ref->createStatement()->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);
          routine_list.push_back(std::make_pair(name, true));
        }
      }
    }
    
    if (arrived_slot)
    {
      boost::function<void ()> schema_contents_arrived = boost::bind(arrived_slot, schema_name, tables, views, procedures, functions, false);
      _grtm->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, table_list, routine_list, false);
  }
  CATCH_ANY_EXCEPTION_AND_DISPATCH_TO_DEFAULT_LOG(_("Get schema contents"))
  
  return grt::StringRef("");
}

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

/**
 * Helper function to generate an HTML line of a column entry in a table definition for the info output.
 */
std::string create_column_table_line(const std::string& name, const std::string& type, const std::string& extra,
  bool isPrimary, bool isIndex)
{
  std::string result = name;

  if (isPrimary || isIndex)
  {
    if (isPrimary)
      result = "<u>" + result + "</u>";
    result = "<b>" + result + "</b>";
  }

  result = "<tr><td style=\"border:none; padding-left: 15px;\">" + result + "</td>";
  result += "<td style=\"border:none; padding-left: 15px;\"><font color='#717171'>" + type +
    (isPrimary ? " PK " : " ") + extra + "</font></td></tr>";
  return result;
}

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

grt::StringRef SqlEditorTreeController::do_fetch_data_for_filter(grt::GRT *grt, boost::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(""))

  log_debug3("Searching data for %s.%s\n", schema_filter.c_str(), object_filter.c_str());  
  
  boost::shared_ptr<sql::ResultSet> dbc_resultset;
  std::map<std::string, int> schema_directory;
  std::string last_schema;
  
  std::string wb_internal_schema = _grtm->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())
    {
      std::list<std::string> *tables = new std::list<std::string>();
      std::list<std::string> *views = new std::list<std::string>();
      std::list<std::string> *procedures = new std::list<std::string>();
      std::list<std::string> *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)
            _grtm->run_once_when_idle(this, boost::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)
        _grtm->run_once_when_idle(this, boost::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...
  std::list<std::string> columns;
  std::map<std::string, LiveSchemaTree::ColumnData> column_data;

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

  try
  {
    sql::Dbc_connection_handler::Ref conn;
    GMutexLock aux_dbc_conn_mutex= _owner->ensure_valid_aux_connection(conn);
    
    std::auto_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::auto_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);
      // 3 collation
      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::replace(type, "unsigned", "UN");
      
      if (extra != "auto_increment")
        extra = "";
      else
        extra = "AI";
      
      //col_node.name = column_name;
      col_node.is_pk = key == "PRI";
      col_node.is_id = (col_node.is_pk || (nullable == "NO" && key == "UNI"));
      col_node.type = type;
      col_node.default_value = default_value;
      col_node.details = create_column_table_line(column_name, type, extra, key == "PRI", key != "");
      
      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
          log_warning("Error fetching column information for '%s'.'%s'", 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);
        }
      }
    }
  }
  catch (const sql::SQLException& exc)
  {
    log_warning("Error fetching column information for '%s'.'%s': %s", 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...
  std::list<std::string> triggers;
  std::map<std::string, LiveSchemaTree::TriggerData> trigger_data_dict;

  try
  {
    sql::Dbc_connection_handler::Ref conn;
    GMutexLock aux_dbc_conn_mutex= _owner->ensure_valid_aux_connection(conn);
    
    std::auto_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::auto_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);
  }
  catch (const sql::SQLException& exc)
  {
    g_warning("Error fetching trigger information for '%s'.'%s': %s", 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...
  std::list<std::string> indexes;
  std::map<std::string, LiveSchemaTree::IndexData> index_data_dict;

  try
  {
    sql::Dbc_connection_handler::Ref conn;
    GMutexLock aux_dbc_conn_mutex= _owner->ensure_valid_aux_connection(conn);
    
    std::auto_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::auto_ptr<sql::ResultSet> rs(stmt->executeQuery(std::string(base::sqlstring("SHOW INDEXES FROM !.!", 0) << schema_name << obj_name)));
    
    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);

        index_data_dict[name] = index_data;
      }
    }

    // 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_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);
  }
  catch (const sql::SQLException& exc)
  {
    g_warning("Error fetching index information for '%s'.'%s': %s", 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)
{
  std::list<std::string> foreign_keys;
  std::map<std::string, LiveSchemaTree::FKData> fk_data_dict;

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

    std::auto_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::auto_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 char *errptr;
      int erroffs=0;
      const char *pattern = "CONSTRAINT\\s*(\\S*)\\s*FOREIGN KEY\\s*\\((\\S*)\\)\\s*REFERENCES\\s*(\\S*)\\s*\\((\\S*)\\)\\s*((\\w*\\s*)*),?$";
      int patres[64];
      
      pcre *patre= pcre_compile(pattern, 0, &errptr, &erroffs, NULL);
      if (!patre)
        throw std::logic_error("error compiling regex "+std::string(errptr));
      
      std::string fk_name;
      std::string fk_columns;
      std::string fk_ref_table;
      std::string fk_ref_columns;
      std::string fk_rules;
      const char *value;
      
      for(size_t index = 0; index < def_lines.size(); index++)
      {
        int rc = pcre_exec(patre, NULL, def_lines[index].c_str(), def_lines[index].length(), 0, 0, patres, sizeof(patres)/sizeof(int));
        
        if ( rc > 0 )
        {
          // gets the values timestamp and 
          pcre_get_substring(def_lines[index].c_str(), patres, rc, 1, &value);
          fk_name = value;
          pcre_free_substring(value);
          fk_name = base::unquote_identifier(fk_name);
          
          pcre_get_substring(def_lines[index].c_str(), patres, rc, 2, &value);
          fk_columns = value;
          pcre_free_substring(value);
          
          pcre_get_substring(def_lines[index].c_str(), patres, rc, 3, &value);
          fk_ref_table = value;
          pcre_free_substring(value);
          fk_ref_table = base::unquote_identifier(fk_ref_table);
          
          pcre_get_substring(def_lines[index].c_str(), patres, rc, 4, &value);
          fk_ref_columns = value;
          pcre_free_substring(value);
          
          pcre_get_substring(def_lines[index].c_str(), patres, rc, 5, &value);
          fk_rules = value;
          pcre_free_substring(value);
          
          // 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
          int rule_count = fk_rule_tokens.size() / 3;

          int token_offset = 0;
          for (int 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.details = strfmt("<div style=\"color: #1a6cb9; padding-left: 15px\">%s (%s \xE2\x86\x92 %s)</div>", new_fk.referenced_table.c_str(), from.c_str(), to.c_str());

          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);
  }
  catch (const sql::SQLException& exc)
  {
    g_warning("Error fetching foreign key information for '%s'.'%s': %s", 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;
}

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
  {
    GMutexLock schema_contents_mutex(_schema_contents_mutex);
    
    {
      sql::Dbc_connection_handler::Ref conn;
      GMutexLock aux_dbc_conn_mutex= _owner->ensure_valid_aux_connection(conn);

      {
        std::auto_ptr<sql::ResultSet> rs(conn->ref->createStatement()->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()
{
  live_schemata_refresh_task->exec(false,
                                   boost::bind((grt::StringRef(SqlEditorTreeController::*)(grt::GRT *, SqlEditorForm::Ptr))&SqlEditorTreeController::do_refresh_schema_tree_safe, this, _1, 
                                               weak_ptr_from(_owner)));
}

/**
 * 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
  const char *action_modifier_clipboard= "clipboard_"; // denotes copy of the action result to clipboard
  const char *action_modifier_editor= "editor_"; // denotes insertion of the action result into editor at cursor position
  
  {
    const char* known_modifiers[] = { action_modifier_clipboard, action_modifier_editor };
    for (size_t n= 0, size= sizeof(known_modifiers)/sizeof(known_modifiers[0]); n < size; ++n)
    {
      if (action.find(known_modifiers[n]) == 0)
      {
        action_modifier = known_modifiers[n];
        real_action = action.substr(action_modifier.size());
      }
    }
  }
  
  const char* newline = (changes.size() > 1) ? "\n" : "";
  
  if (real_action == "object_name_short" || real_action == "object_name_long")
  {
    std::string text = "";
    
    for (size_t i = 0; i < changes.size(); i++)
    {
      if (changes[i].type == wb::LiveSchemaTree::Schema)
        text += changes[i].name + newline;
      else
        if (real_action == "object_name_short")
          text += changes[i].name + newline;
        else
          if (real_action == "object_name_long")
            text += strfmt("`%s`.`%s`%s", changes[i].schema.c_str(), changes[i].name.c_str(), newline);
    }
    
    if (action_modifier == action_modifier_clipboard)
      mforms::Utilities::set_clipboard_text(text);
    else if (action_modifier == action_modifier_editor)
      _owner->active_sql_editor_or_new_scratch()->insert_text(text);
    return;
  }
  else if (real_action == "object_name_short_columns" || real_action == "object_name_long_columns")
  {
    std::string text = "";
    
    for (size_t i = 0; i < changes.size(); i++)
    {
      if (!text.empty())
        text.append(", ");
      if (real_action == "object_name_short_columns")
        text += changes[i].detail;
      else
        if (real_action == "object_name_long_columns")
          text += strfmt("`%s`.`%s`", changes[i].name.c_str(), changes[i].detail.c_str());
    }
    
    if (action_modifier == action_modifier_clipboard)
      mforms::Utilities::set_clipboard_text(text);
    else if (action_modifier == action_modifier_editor)
      _owner->active_sql_editor_or_new_scratch()->insert_text(text);
    return;
  }
  else if (real_action == "table_create_statement")
  {
    std::string text = "";
    for (size_t i = 0; i < changes.size(); i++)
      text += get_object_ddl_script(changes[i].type, changes[i].schema, changes[i].name) + newline;
    
    if (action_modifier == action_modifier_clipboard)
      mforms::Utilities::set_clipboard_text(text);
    else if (action_modifier == action_modifier_editor)
      _owner->active_sql_editor_or_new_scratch()->insert_text(text);
  }
  else if (real_action == "table_column_info_statement")
  {
    std::string text = "";
    for (size_t i = 0; i < changes.size(); i++)
      text += strfmt("SHOW COLUMNS FROM `%s`.`%s`;\n", changes[i].schema.c_str(), changes[i].name.c_str());
    
    if (action_modifier == action_modifier_clipboard)
      mforms::Utilities::set_clipboard_text(text);
    else if (action_modifier == action_modifier_editor)
      _owner->active_sql_editor_or_new_scratch()->insert_text(text);
    return;
  }
  else if (real_action == "table_delete_statement")
  {
    std::string text = "";
    for (size_t i = 0; i < changes.size(); i++)
      text += strfmt("DELETE FROM `%s`.`%s`\nWHERE <{where_condition}>;\n", changes[i].schema.c_str(), changes[i].name.c_str());
    
    if (action_modifier == action_modifier_clipboard)
      mforms::Utilities::set_clipboard_text(text);
    else if (action_modifier == action_modifier_editor)
      _owner->active_sql_editor_or_new_scratch()->insert_text(text);
    return;
  }
  else if (real_action == "table_select_statement" || real_action == "table_insert_statement" ||
           real_action == "table_update_statement")
  {
    typedef std::list<std::pair<std::string, std::string> > ColumnDefinitions;
    ColumnDefinitions column_defs;
    std::string text = "";
    
    for (size_t i = 0; i < changes.size(); i++)
    {
      mforms::TreeNodeRef node = get_schema_tree()->get_node_for_object(changes[i].schema, changes[i].type, changes[i].name);
      LiveSchemaTree::ViewData* pdata = dynamic_cast<LiveSchemaTree::ViewData*>(node->get_data());

      if (pdata && pdata->is_data_loaded(LiveSchemaTree::COLUMN_DATA))
      {
        if (pdata->get_type() == LiveSchemaTree::Table)
          node = node->get_child(LiveSchemaTree::TABLE_COLUMNS_NODE_INDEX);

        int size = node->count();

        for (int index = 0; index < size; index++)
        {
          mforms::TreeNodeRef column_node = node->get_child(index);
          LiveSchemaTree::ColumnData *pcolumn_data = dynamic_cast<LiveSchemaTree::ColumnData*>(column_node->get_data());
          
          column_defs.push_back(std::make_pair(column_node->get_string(0), pcolumn_data->default_value));
        }
      }
      else
      {
        try
        {
          sql::Dbc_connection_handler::Ref conn;
          GMutexLock aux_dbc_conn_mutex= _owner->ensure_valid_aux_connection(conn);
          std::auto_ptr<sql::ResultSet> rs(conn->ref->getMetaData()->getColumns("", changes[i].schema, changes[i].name, "%"));
          while (rs->next())
            column_defs.push_back(std::make_pair(rs->getString(4), rs->getString(6)));
        }
        catch (const sql::SQLException& err)
        {
          log_warning("%s: Error fetching table data for %s.%s: %s", real_action.c_str(),
                      changes[i].schema.c_str(), changes[i].name.c_str(), err.what());
        }
      }

      if (real_action == "table_select_statement")
      {
        std::string columns;
        BOOST_FOREACH (const ColumnDefinitions::value_type &col_def, column_defs)
        columns+= (columns.empty() ? "" : ",\n") + strfmt("`%s`.`%s`", changes[i].name.c_str(), col_def.first.c_str());
        text += strfmt("SELECT\n%s\nFROM `%s`.`%s`;\n", columns.c_str(), changes[i].schema.c_str(), changes[i].name.c_str());
      }
      
      else if (real_action == "table_insert_statement")
      {
        std::string columns;
        std::string values;
        BOOST_FOREACH (const ColumnDefinitions::value_type &col_def, column_defs)
        {
          columns+= (columns.empty() ? "" : ",\n") + strfmt("`%s`", col_def.first.c_str());
          values+= (values.empty() ? "" : ",\n") + strfmt("<{%s: %s}>", col_def.first.c_str(), col_def.second.c_str());
        }
        text += strfmt("INSERT INTO `%s`.`%s`\n(%s)\nVALUES\n(\n%s\n);\n", changes[i].schema.c_str(), changes[i].name.c_str(), columns.c_str(), values.c_str());
      }
      
      else if (real_action == "table_update_statement")
      {
        std::string columns;
        BOOST_FOREACH (const ColumnDefinitions::value_type &col_def, column_defs)
        columns+= (columns.empty() ? "" : ",\n") + strfmt("`%s` = {%s: %s}", col_def.first.c_str(), col_def.first.c_str(), col_def.second.c_str());
        text += strfmt("UPDATE `%s`.`%s`\nSET\n%s\nWHERE <{where_condition}>;\n", changes[i].schema.c_str(), changes[i].name.c_str(), columns.c_str());
      }
      
      text += std::string(newline) + newline;
    }
    
    if (action_modifier == action_modifier_clipboard)
      mforms::Utilities::set_clipboard_text(text);
    else if (action_modifier == action_modifier_editor)
      _owner->active_sql_editor_or_new_scratch()->insert_text(text);
    
    return;
  }
  else if (real_action == "table_select_statement_columns" || real_action == "table_insert_statement_columns" ||
           real_action == "table_update_statement_columns" ||
           real_action == "edit_data_columns" || 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;
      std::map<TableName, std::map<ColumnName, std::string> >::iterator iter = table_column_types.find(full_table_name);
      
      if (real_action != "table_select_statement_columns" && real_action != "edit_data_columns" && real_action != "select_data_columns")
      {
        if (iter == table_column_types.end())
        {

          std::map<ColumnName, std::string> column_types;

          mforms::TreeNodeRef node = get_schema_tree()->get_node_for_object(changes[i].schema, changes[i].type, changes[i].name);
          LiveSchemaTree::ViewData* pdata = dynamic_cast<LiveSchemaTree::ViewData*>(node->get_data());

          if (pdata && pdata->is_data_loaded(LiveSchemaTree::COLUMN_DATA))
          {
            if (pdata->get_type() == LiveSchemaTree::Table)
              node = node->get_child(LiveSchemaTree::TABLE_COLUMNS_NODE_INDEX);

            int size = node->count();

            for (int index = 0; index < size; index++)
            {
              mforms::TreeNodeRef column_node = node->get_child(index);
              LiveSchemaTree::ColumnData *pcolumn_data = dynamic_cast<LiveSchemaTree::ColumnData*>(column_node->get_data());
          
              if (real_action == "table_insert_statement_columns")
                column_types[column_node->get_string(0)] = pcolumn_data->default_value;
              else
                column_types[column_node->get_string(0)] = pcolumn_data->type;
            }

            table_column_types[full_table_name] = column_types;
          }
          else
          {
            try
            {
              sql::Dbc_connection_handler::Ref conn;
              GMutexLock aux_dbc_conn_mutex= _owner->ensure_valid_aux_connection(conn);
              int idx = (real_action == "table_insert_statement_columns") ? 13 : 6; // default : type
              std::auto_ptr<sql::ResultSet> rs(conn->ref->getMetaData()->getColumns("", changes[i].schema, changes[i].name, "%"));
              while (rs->next())
                column_types[rs->getString(4)] = rs->getString(idx);
              table_column_types[full_table_name] = column_types;
            }
            catch (const sql::SQLException& err)
            {
              log_warning("%s: Error fetching table data for %s.%s: %s", real_action.c_str(),
                          changes[i].schema.c_str(), changes[i].name.c_str(), err.what());
            }
          }
          
          if (column_types.find(changes[i].detail) != column_types.end())
            column_type = column_types[changes[i].detail];
        }
        else
        {
          if (iter->second.find(changes[i].detail) != iter->second.end())
            column_type = iter->second[changes[i].detail];
        }
      }

      if (real_action == "table_select_statement_columns" || real_action == "edit_data_columns" || real_action == "select_data_columns")
      {
        if (!first_text[full_table_name].empty())
          first_text[full_table_name].append(", ");
        first_text[full_table_name].append(changes[i].detail);
      }
      else if (real_action == "table_insert_statement_columns")
      {
        if (!first_text[full_table_name].empty())
          first_text[full_table_name].append(", ");
        first_text[full_table_name].append(changes[i].detail);
        
        if (!second_text[full_table_name].empty())
          second_text[full_table_name].append(", ");
        second_text[full_table_name].append(strfmt("<{%s}>", column_type.c_str())); // actually is default value
      }
      else if (real_action == "table_update_statement_columns")
      {
        if (!first_text[full_table_name].empty())
          first_text[full_table_name].append(", ");
        first_text[full_table_name].append(strfmt("%s = <{%s}>", changes[i].detail.c_str(), column_type.c_str()));        
      }
    }

    if (real_action == "table_select_statement_columns" || real_action == "edit_data_columns" || real_action == "select_data_columns")
    {
      BOOST_FOREACH(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());
    }
    else if (real_action == "table_insert_statement_columns")
    {
      BOOST_FOREACH (const TableStringMap::value_type &table_columns, first_text)
        text += strfmt("INSERT INTO %s (%s)\nVALUES\n(\n%s\n);\n", table_columns.first.c_str(), table_columns.second.c_str(),
                       second_text[table_columns.first].c_str());
    }
    else if (real_action == "table_update_statement_columns")
    {
      BOOST_FOREACH (const TableStringMap::value_type &table_columns, first_text)
        text += strfmt("UPDATE %s\nSET\n%s\nWHERE <{where_condition}>;\n", table_columns.first.c_str(), table_columns.second.c_str());
    }
    
    if (real_action == "edit_data_columns" || real_action == "select_data_columns")
      _owner->run_sql_in_scratch_tab(text, false, real_action == "edit_data_columns");
    else
    {
      text += std::string(newline) + newline;
      
      if (action_modifier == action_modifier_clipboard)
        mforms::Utilities::set_clipboard_text(text);
      else if (action_modifier == action_modifier_editor)
        _owner->active_sql_editor_or_new_scratch()->insert_text(text);
      
      return;
    }
  }
  else
    log_error("Unhandled live schema tree action: %s", real_action.c_str());
  
  for (size_t i = 0; i < changes.size(); i++)
  {
    std::string sql;
    switch (changes[i].type)
    {
      case wb::LiveSchemaTree::Schema:
        if (real_action == "filter")
        {
          _side_bar->get_filter_entry()->set_value(changes[i].name);
          (*_side_bar->get_filter_entry()->signal_changed())();
        }
        else
        {
          _owner->active_schema(changes[i].name);
        }
        break;
      case wb::LiveSchemaTree::Table:
      case wb::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;
        break;
      default:
        break;
    }
    
    if (!sql.empty())
    {
      if (real_action == "edit_data")
      {
        int ed = _owner->run_sql_in_scratch_tab(sql, false, true);
        _owner->sql_editor_caption(ed, changes[i].name);
        _owner->do_partial_ui_refresh(SqlEditorForm::RefreshEditorTitle);
      }
      else
        _owner->run_sql_in_scratch_tab(sql, false, false);
    }
  }
}

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

/**
 * 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);
}

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

/**
 * Starts altering more than one db object.
 */
void SqlEditorTreeController::tree_alter_objects(const std::vector<wb::LiveSchemaTree::ChangeRecord>& changes)
{
  for (size_t i = 0; i < changes.size(); i++)
    do_alter_live_object(changes[i].type, changes[i].schema, changes[i].name);
}

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

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())
    {
      std::string version_string = _owner->connection_details()["dbmsProductVersion"];
      GrtVersionRef version = CatalogHelper::parse_version(_grtm->get_grt(), version_string);
      
      rdbms = grt::shallow_copy_object(rdbms);
      version->owner(rdbms);
      rdbms->version(version);
    }
    
    //TODO needs reset_references to be called on editor close to prevent leaks
    db_CatalogRef client_state_catalog= _grtm->get_grt()->create_object<db_Catalog>(database_package + ".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());
    //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_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= _grtm->get_grt()->create_object<db_Schema>(database_package + ".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())
      {
        log_warning("Unable to get DDL for %s.%s", used_schema_name.c_str(), obj_name.c_str());
        return;
      }
      {
        sql::Dbc_connection_handler::Ref conn;
        GMutexLock aux_dbc_conn_mutex= _owner->ensure_valid_aux_connection(conn);
        _owner->get_session_variable(conn, "sql_mode", sql_mode);
      }
      
      // if this is a View, then auto-reformat it before sending it to parser/editor
      if (type == wb::LiveSchemaTree::View && _grtm->get_app_option_int("DbSqlEditor:ReformatViewDDL", 0))
      {
        try 
        {  
          grt::Module *module = _grtm->get_grt()->get_module("SQLIDEUtils");
          grt::BaseListRef args(_grtm->get_grt());
          args.ginsert(grt::StringRef(ddl_script));
          ddl_script = grt::StringRef::cast_from(module->call_function("reformatSQLStatement", args));
        }
        catch (std::exception &exc) 
        {
          log_warning("Error reformatting view code: %s", exc.what());
        }
      }
      
      if (!parse_ddl_into_catalog(rdbms, client_state_catalog, strfmt("`%s`.`%s`", schema_name.c_str(), obj_name.c_str()), ddl_script, sql_mode))
      {
        log_warning("Error parsing DDL for %s.%s: %s", schema_name.c_str(), obj_name.c_str(), ddl_script.c_str());
        return;
      }
    }
    
    //TODO needs reset_references to be called on editor close to prevent leaks
    db_CatalogRef server_state_catalog(db_CatalogRef::cast_from(grt::copy_object(client_state_catalog)));
    
  retry_search:
    //TODO needs reset_references to be called on editor close to prevent leaks
    db_DatabaseObjectRef db_object;
    switch (type)
    {
      case wb::LiveSchemaTree::Schema:
        db_object= is_object_new ?
          create_new_schema(client_state_catalog) :
          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) :
          find_named_object_in_list(schema->tables(), obj_name);
        break;
      case wb::LiveSchemaTree::View:
        db_object= is_object_new ?
          create_new_view(schema) :
          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) :
          find_named_object_in_list(schema->routines(), obj_name);
        break;
      default:
        break;
    }
    if (!db_object.is_valid())
    {
      std::string lower;
      // There is a bug in server that causes get_object_ddl_script() for uppercase named tables
      // to be returned in lowercase, in osx. http://bugs.mysql.com/bug.php?id=57830
      // So, if you try to alter FOOBAR, it will not find it, since the table will be revenged
      // in lowercase. To work around that, we convert the object name to lowercase and repeat
      // the search if the 1st try didn't work.
      lower = tolower(obj_name);
      if (lower != obj_name)
      {
        log_warning("Object name %s was not found in catalog, trying to search it as %s",
                  obj_name.c_str(), lower.c_str());
        obj_name = lower;
        goto retry_search;
      }
      else
        log_warning("Object name %s was not found in catalog.", aobj_name.c_str());
      return;
    }
    sql::Dbc_connection_handler::Ref conn;
    grt::NormalizedComparer comparer(_grtm->get_grt());
    {
      GMutexLock 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_object->customData().set("DBSettings", comparer.get_options_dict());    
    db_object->customData().set("liveRdbms", rdbms);
    db_object->customData().set("clientStateCatalog", client_state_catalog);
    db_object->customData().set("serverStateCatalog", server_state_catalog);
    db_object->customData().set("originalObjectDDL", grt::StringRef(ddl_script));
    db_object->customData().set("sqlMode", grt::StringRef(sql_mode));
    db_object->customData().set("ownerSqlEditor", _owner->wbsql()->get_grt_editor_object(_owner));
    
    // TODO: make docking/non-docking switchable via preferences.
    //_context_ui->get_wb()->open_object_editor(db_object, bec::StandaloneWindowFlag);
    _grtm->open_object_editor(db_object, bec::ForceNewWindowFlag);
  }
  catch (const std::exception & e)
  {
    log_error("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"));
  }
}


db_SchemaRef SqlEditorTreeController::create_new_schema(db_CatalogRef owner)
{
  db_SchemaRef object= _grtm->get_grt()->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= _grtm->get_grt()->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= _grtm->get_grt()->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= _grtm->get_grt()->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(db_DatabaseObjectRef db_object,
  std::string algorithm, std::string lock)
{
  DbMySQLImpl *diffsql_module = _grtm->get_grt()->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_mgmt_RdbmsRef rdbms= db_mgmt_RdbmsRef::cast_from(db_object->customData().get("liveRdbms"));

  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(_grtm->get_grt());
  //diff_options.set("CaseSensitive",db_object->customData().get("CaseSensitive"));
  grt::DictRef db_settings = grt::DictRef::cast_from(db_object->customData().get("DBSettings"));

  static GrtVersionRef version56 = CatalogHelper::parse_version(_grtm->get_grt(), "5.6");
  if (rdbms->version().is_valid() && !CatalogHelper::version_greater(version56, rdbms->version()))
  {
    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;
}

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

/**
 * Drops one or more objects of any supported type and from multiple schemas.
 */
void SqlEditorTreeController::tree_drop_objects(const std::vector<wb::LiveSchemaTree::ChangeRecord>& changes)
{
  std::string sql = "";
  RowId log_id = _owner->add_log_message(DbSqlEditorLog::BusyMsg, "Preparing...", _("Drop selected objects"), "");
  
  try
  {
    // Do two loops here: one without schemas and another one only schemas. This way we don't try to
    // delete objects twice. This could be made smarter by not dropping objects whose owning schema
    // is dropped. But due to case sensitivity issue this is more complex, so we go the simple route.
    for (size_t i = 0; i < changes.size(); i++)
    {
      switch (changes[i].type)
      {
        case wb::LiveSchemaTree::Schema:
          // Handle in second loop.
          break;
        case wb::LiveSchemaTree::Table:
          sql += sqlstring("drop table !.!;\n", 0) << changes[i].schema << changes[i].name;
          break;
        case wb::LiveSchemaTree::View:
          sql += sqlstring("drop view !.!;\n", 0) << changes[i].schema << changes[i].name;
          break;
        case wb::LiveSchemaTree::Procedure:
          sql += sqlstring("drop procedure !.!;\n", 0) << changes[i].schema << changes[i].name;
          break;
        case wb::LiveSchemaTree::Function:
          sql += sqlstring("drop function !.!;\n", 0) << changes[i].schema << changes[i].name;
          break;
        default:
          break;
      }
    }
    
    for (size_t i = 0; i < changes.size(); i++)
    {
      if (changes[i].type== LiveSchemaTree::Schema)
        sql += sqlstring("drop schema !;\n", 0) << changes[i].name;
    }
    
    if (sql.size() > 0)
    {
      if (_owner->run_live_object_alteration_wizard(sql, NULL, log_id, sql))
      {
        std::string last_schema = "";
        for (size_t i = 0; i < changes.size(); i++)
        {
          refresh_live_object_in_overview(changes[i].type, changes[i].schema, changes[i].name.c_str(), "");
          if (changes[i].type == LiveSchemaTree::Schema)
            last_schema = changes[i].name;
        }
        
        if ((last_schema.size() > 0) && (_owner->active_schema() == last_schema) && _owner->connection_descriptor().is_valid())
        {
          std::string default_schema= _owner->connection_descriptor()->parameterValues().get_string("schema", "");
          if (!default_schema.empty())
          {
            _grtm->get_dispatcher()->call_from_main_thread<void>(
                                                                 boost::bind((void(SqlEditorForm::*)(const std::string&))&SqlEditorForm::active_schema, _owner, boost::cref(default_schema)),
                                                                 false/*wait*/,
                                                                 true/*force_queue*/);
          }
        }
      }
    }
  }
  catch (const std::exception &e)
  {
    log_error("Error executing drop: %s", e.what());
    _owner->set_log_message(log_id, DbSqlEditorLog::ErrorMsg, 
                    strfmt(_("An error occured while processing a drop request.\n\nQuery: %s\n\nError: %s"), sql.c_str(), e.what()),
                    sql, "");
    mforms::Utilities::show_error(_("Running drop query failed"),
                                  strfmt(_("An error occured while processing a drop request.\n\nQuery: %s\n\nError: %s"), sql.c_str(), e.what()), _("OK"));
  }
}

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

std::string SqlEditorTreeController::get_object_ddl_script(wb::LiveSchemaTree::ObjectType type, const std::string &schema_name, const std::string &obj_name)
{
  const size_t DDL_COLUMN= 5;
  std::string delimiter;
  {
    SqlFacade::Ref sql_facade= SqlFacade::instance_for_rdbms(_owner->rdbms());
    Sql_specifics::Ref sql_specifics= sql_facade->sqlSpecifics();
    delimiter= sql_specifics->non_std_sql_delimiter();
  }
  std::string ddl_script;
  //triggers are fetched prior to table ddl, but should appear after table created
  std::string additional_ddls;
  ddl_script+= strfmt("delimiter %s\n\n", delimiter.c_str());
  
  try
  {
    sql::Dbc_connection_handler::Ref conn;
    std::string query;
    GMutexLock 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:
      {
        // triggers
        std::auto_ptr<sql::ResultSet> rs(conn->ref->getMetaData()->getSchemaObjects("", schema_name, "trigger", true, "", obj_name));
        while (rs->next())
        {
          additional_ddls+= rs->getString(DDL_COLUMN);
          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::auto_ptr<sql::Statement> stmt(conn->ref->createStatement());
    std::auto_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)
  {
    if (type == wb::LiveSchemaTree::View && e.getErrorCode() == 1356)
    {
      sql::Dbc_connection_handler::Ref conn;
      std::string query, view;
      GMutexLock 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::auto_ptr<sql::Statement> stmt(conn->ref->createStatement());
      std::auto_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
    {
      log_error("Error getting SQL definition for %s.%s: %s", schema_name.c_str(), obj_name.c_str(), e.what());
      mforms::Utilities::show_error("Error getting DDL for object", e.what(), "OK", "", "");
      ddl_script.clear();
    }
  }
  return ddl_script;
}


void SqlEditorTreeController::refresh_live_object_in_editor(bec::DBObjectEditorBE* obj_editor, bool using_old_name)
{
  db_DatabaseObjectRef db_object= obj_editor->get_dbobject();
  
  db_CatalogRef client_state_catalog= db_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;
  if (obj_name != *db_object->name())
    db_object->name(obj_name);
  db_object->oldName("");
  
  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;
  
  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());
      
      obj_editor->freeze_refresh_on_object_change();
      
      db_TableRef table= db_TableRef::cast_from(db_object);
      table->isStub(1);
      table->triggers().remove_all();
      table->foreignKeys().remove_all();
      table->indices().remove_all();
      table->columns().remove_all();
      
      obj_editor->thaw_refresh_on_object_change();
    }
    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;
    }
  }
  
  // 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())
    {
      db_object->oldName(obj_name);
      
      try
      {
        sql::Dbc_connection_handler::Ref conn;
        GMutexLock aux_dbc_conn_mutex= _owner->ensure_valid_aux_connection(conn);
        _owner->get_session_variable(conn, "sql_mode", sql_mode);
      }
      CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Get 'sql_mode' session variable"));
      
      obj_editor->freeze_refresh_on_object_change();
      
      parse_ddl_into_catalog(obj_editor->get_rdbms(), client_state_catalog, 
                             strfmt("`%s`.`%s`", schema_name.c_str(), obj_name.c_str()),
                             ddl_script, sql_mode);
            
      obj_editor->thaw_refresh_on_object_change();
    }
  }
  
  {
    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("originalObjectDDL", grt::StringRef(ddl_script));
  db_object->customData().set("sqlMode", grt::StringRef(sql_mode));
  
  // enable refresh of sql editor contents
  Sql_editor::Ref active_sql_editor= obj_editor->get_sql_editor();
  if (active_sql_editor)
  {
    active_sql_editor->set_refresh_enabled(true);
    // provoke database object to refresh FE control contents
    (*db_object->signal_changed())("", grt::ValueRef());
  }
}


bool SqlEditorTreeController::parse_ddl_into_catalog(db_mgmt_RdbmsRef rdbms, db_CatalogRef client_state_catalog, 
                                           const std::string &obj_descr, const std::string &ddl_script, std::string sql_mode)
{
  SqlFacade::Ref sql_facade= SqlFacade::instance_for_rdbms(rdbms);
  Sql_parser::Ref sql_parser= sql_facade->sqlParser();
  sql_parser->messages_enabled(false);
  grt::DictRef options(_grtm->get_grt());
  options.set("reuse_existing_objects", grt::IntegerRef(1));
  if (!sql_mode.empty())
    options.gset("sql_mode", sql_mode);
  
  int err_count= sql_parser->parse_sql_script(client_state_catalog, ddl_script, options);
  bool generic_parse_error = false;
  
  //! dbms-specific code
  if (options.has_key("sql_mode") && (0 < err_count))
  {
    if (std::string::npos != sql_mode.find("ANSI_QUOTES"))
      sql_mode= replace_string(sql_mode, "ANSI_QUOTES", "");
    else
      sql_mode+= ",ANSI_QUOTES";
    options.gset("sql_mode", sql_mode);
    options.set("reuse_existing_objects", grt::IntegerRef(1));
    err_count= sql_parser->parse_sql_script(client_state_catalog, ddl_script, options);
    if (0 == err_count)
    {
      if (mforms::Utilities::show_warning(strfmt(_("Error Parsing DDL for %s"), obj_descr.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(ddl_script);
      }
      return false;
    }
    else
      generic_parse_error = true;
  } 
  else if (err_count > 0)
    generic_parse_error = true;
  
  if (generic_parse_error)
  {
    if (mforms::Utilities::show_error(strfmt(_("Error Parsing DDL for %s"), obj_descr.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(ddl_script);
    }
    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, "Preparing...", log_descr, "");
  }
  try
  {
    if (!dry_run && obj_editor->get_sql_editor()->has_sql_errors())
    {
      int res= mforms::Utilities::show_warning(
                                               _("Apply Changes to Object"),
                                               _("The object's DDL statement contains syntax errors.\n"
                                                 "Are you sure you want to apply the DDL statement unchanged?"),
                                               _("Yes"),
                                               _("No"));
      
      if (res != mforms::ResultOk)
      {
        _owner->set_log_message(log_id, DbSqlEditorLog::ErrorMsg, "Cancelled", log_descr, "");
        return false;
      }
    }

    db_DatabaseObjectRef db_object= obj_editor->get_dbobject();

    if (!dry_run)
    {
      ValueRef is_trigger_changed= db_object->customData().get("NonTriggerSQLFound");
      if (is_trigger_changed.is_valid() && (IntegerRef::cast_from(is_trigger_changed) != 0))
      {
        int res= mforms::Utilities::show_warning(
          _("Apply Changes to Object"),
          _("The object's trigger DDL statement contains sql definitions without matching CREATE TRIGGER.\n"
          "This will lead to invalid sql generated.\n"
          "Ensure that you have only trigger creation sql in Triggers tab.\n"
          "Are you sure you want to apply the DDL statement unchanged?"),
          _("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
    if (!dry_run)
    {
      bool case_sensitive_identifiers= (_grtm->get_app_option_int("SqlIdentifiersCS", 1) != 0);
      std::string obj_name= db_object->name();
      std::string obj_old_name= db_object->oldName();
      if (!case_sensitive_identifiers)
      {
        obj_name = tolower(obj_name);
        obj_old_name = tolower(obj_old_name);
      }
      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;
        GMutexLock lock(_owner->ensure_valid_aux_connection(conn));
        
        BOOST_FOREACH (const std::string &obj_type, obj_types)
        {
          std::string query = validation_queries.front();
          validation_queries.pop_front();

          std::auto_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;
    _owner->wbsql()->get_wbui()->get_wb_options_value("", "DbSqlEditor:OnlineDDLAlgorithm", algorithm);
    std::string lock;
    _owner->wbsql()->get_wbui()->get_wb_options_value("", "DbSqlEditor:OnlineDDLLock", lock);
    std::string alter_script = generate_alter_script(db_object, algorithm, lock);

    if (alter_script.empty())
    {
      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
    
    //bool is_live_object_alteration_wizard_enabled= (0 != _options.get_int("DbSqlEditor:IsLiveObjectAlterationWizardEnabled", 1));
    if (true/*is_live_object_alteration_wizard_enabled*/)
    {
      return _owner->run_live_object_alteration_wizard(alter_script, obj_editor, log_id, log_descr);
    }
    else
    {
      _owner->apply_object_alter_script(alter_script, obj_editor, log_id);
    }
  }
  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, "");
    log_error("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;
    GMutexLock 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= *table_editor->get_rdbms()->databaseObjectPackage();
    std::string schema_typename= database_package + ".Schema";
    std::string table_typename= database_package + ".Table";
    grt::GRT *grt= _grtm->get_grt();
    
    std::string prev_schema_name;
    
    boost::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->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->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(table_editor->get_rdbms());
    Sql_parser::Ref sql_parser= sql_facade->sqlParser();
    sql_parser->messages_enabled(false);
    grt::DictRef options(_grtm->get_grt());
    {
      std::string sql_mode;
      sql::Dbc_connection_handler::Ref conn;
      GMutexLock aux_dbc_conn_mutex= _owner->ensure_valid_aux_connection(conn);
      if (_owner->get_session_variable(conn, "sql_mode", sql_mode))
        options.gset("sql_mode", sql_mode);
      else
        log_warning("Unable to get sql_mode for connection");
    }
    sql_parser->parse_sql_script(catalog, ddl_script, options);
  }
  
  // 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;
}


bool SqlEditorTreeController::create_live_object(GrtObjectRef object_type, std::string owner_name, std::string obj_name)
{
  if (db_SchemaRef::can_wrap(object_type))
    tree_create_object(LiveSchemaTree::Schema, "", "");
  else if (db_TableRef::can_wrap(object_type))
    tree_create_object(LiveSchemaTree::Table, owner_name, "");
  else if (db_ViewRef::can_wrap(object_type))
    tree_create_object(LiveSchemaTree::View, owner_name, "");
  else if (db_RoutineRef::can_wrap(object_type))
  {
    db_RoutineRef routine = db_RoutineRef::cast_from(object_type);
    std::string type = routine->routineType();

    if (type == "function")
      tree_create_object(LiveSchemaTree::Function, owner_name, "");
    else
      tree_create_object(LiveSchemaTree::Procedure, owner_name, "");
  }
  else
    return false;
  
  return true;
}


bool SqlEditorTreeController::drop_live_object(GrtObjectRef object_type, std::string owner_name, std::string obj_name)
{
  wb::LiveSchemaTree::ChangeRecord record = { LiveSchemaTree::Schema, owner_name, obj_name };
  std::vector<wb::LiveSchemaTree::ChangeRecord> changes;
  
  if (db_TableRef::can_wrap(object_type))
    record.type = LiveSchemaTree::Table;
  else if (db_ViewRef::can_wrap(object_type))
    record.type = LiveSchemaTree::View;

  else if (db_RoutineRef::can_wrap(object_type))
  {
    db_RoutineRef routine = db_RoutineRef::cast_from(object_type);
    std::string type = routine->routineType();

    if (type == "function")
      record.type = LiveSchemaTree::Function;
    else
      record.type = LiveSchemaTree::Procedure;
  }
  else
    if (!db_SchemaRef::can_wrap(object_type))
      return false;
  
  changes.push_back(record);
  tree_drop_objects(changes);
  
  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 (_side_bar)
    _grtm->run_once_when_idle(this, boost::bind(&mforms::View::set_needs_repaint, _side_bar->get_schema_tree()));
}

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

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

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

grt::StringRef SqlEditorTreeController::do_refresh_schema_tree_safe(grt::GRT *grt, 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;
  
  std::list<std::string> schema_list = fetch_schema_list();
  _grtm->run_once_when_idle(this, boost::bind(&LiveSchemaTree::update_schemata, _schema_tree, schema_list));

  _is_refreshing_schema_tree= false;
  
  return grt::StringRef("");
}



bec::MenuItemList SqlEditorTreeController::get_live_catalog_menu_items(const std::string &schema, 
                                                                       const std::string &object, 
                                                                       const std::string &type)
{
  grt::ListRef<GrtObject> list(_grtm->get_grt());
  std::list<std::string> groups;
  
  db_query_LiveDBObjectRef dbobject(list.get_grt());
  dbobject->owner(_owner->wbsql()->get_grt_editor_object(_owner));
  dbobject->schemaName(schema);
  dbobject->type(type);
  dbobject->name(object);
  if (type == "schema")
    dbobject->name(schema);
  list.insert(dbobject);
  
  bec::ArgumentPool argument_pool;
  _grtm->update_plugin_arguments_pool(argument_pool);
  
  argument_pool.add_entries_for_object("", dbobject, "");
  argument_pool.add_entries_for_object(type, dbobject, "");
  
  groups.push_back("Menu/SQL/Catalog");
  
  return _grtm->get_plugin_context_menu_items(groups, argument_pool);
}


bool SqlEditorTreeController::call_live_catalog_menu_item(const std::string &item,
                                                const std::string &schema, const std::string &object, 
                                                const std::string &type)
{  
  db_query_LiveDBObjectRef dbobject(_grtm->get_grt());
  dbobject->owner(_owner->wbsql()->get_grt_editor_object(_owner));
  dbobject->schemaName(schema);
  dbobject->type(type);
  dbobject->name(object);
  if (type == "schema")
    dbobject->name(schema);
  
  bec::ArgumentPool argument_pool;
  _grtm->update_plugin_arguments_pool(argument_pool);
  
  argument_pool.add_entries_for_object("", dbobject, "");
  argument_pool.add_entries_for_object(type, dbobject, "");
  
  _owner->wbsql()->get_cmdui()->activate_command(item, argument_pool);
  return true;
}


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


void SqlEditorTreeController::handle_grt_notification(const std::string &name, void *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= boost::bind(&SqlEditorTreeController::apply_changes_to_object, this, _1, _2);
        editor->on_refresh_live_object= boost::bind(&SqlEditorTreeController::refresh_live_object_in_editor, this, _1, true);
        editor->on_create_live_table_stubs= boost::bind(&SqlEditorTreeController::create_live_table_stubs, this, _1);
        editor->on_expand_live_table_stub= boost::bind(&SqlEditorTreeController::expand_live_table_stub, this, _1, _2, _3);
      }
    }
  }
}

/* 
* Retrieves from the three the column information for a table node
*/
void *SqlEditorTreeController::get_table_column_data(const std::string& schema_name, const std::string& table_name, std::vector<std::string>* columns, std::vector<wb::LiveSchemaTree::ColumnData*>*column_data)
{


  // By default we assume we don't know the type of object being queried.
  // This method is used on the editable select validation, however
  // selects can be done to views too, so we need to identify that case
  LiveSchemaTree::ObjectType target_type = LiveSchemaTree::Any;
  

  // At this point we do not know if the request is to pull data from a table or a view, 
  // This is the first attempt to identify that.
  mforms::TreeNodeRef target_node = _schema_tree->get_node_for_object(schema_name, LiveSchemaTree::Table, table_name);
  if (target_node && target_node->is_valid())
    target_type = LiveSchemaTree::Table;
  else
  {
    target_node = _schema_tree->get_node_for_object(schema_name, LiveSchemaTree::View, table_name);

    if (target_node && target_node->is_valid())
      target_type = LiveSchemaTree::View;
  }

  // If the node is a view then we are done
  if (target_type != LiveSchemaTree::View)
  {
    bool found_target_node = false;
    bool fetch_target_data = true;

    // If the node was found on the tree, and it is a table node 
    // the load will be done ONLY if needed and using the 
    if ( target_type == LiveSchemaTree::Table )
    {
      found_target_node = true;
      LiveSchemaTree::TableData *table_data = dynamic_cast<LiveSchemaTree::TableData*>(target_node->get_data());

      // If the column info is not available it must be fetched
      fetch_target_data = !table_data->is_data_loaded(LiveSchemaTree::COLUMN_DATA);
    }

    // If the node was not found in the tree we still don't know if it is a table
    // or a view, the columns will be loaded in any case...
    if (fetch_target_data)
    {
      // If no target type is specified, load table details will determine that from the database
      // and will create the appropiate object in the tree
      _schema_tree->load_table_details(target_type, schema_name, table_name,LiveSchemaTree::COLUMN_DATA);

      if (!found_target_node)
        target_node = _schema_tree->get_node_for_object(schema_name, LiveSchemaTree::Table, table_name);
    }
    
    if (target_node && target_node->is_valid())
    {
      int size = target_node->get_child(LiveSchemaTree::TABLE_COLUMNS_NODE_INDEX)->count();
      for(int index = 0; index < size; index++)
      {
        mforms::TreeNodeRef column_node = target_node->get_child(LiveSchemaTree::TABLE_COLUMNS_NODE_INDEX)->get_child(index);
        LiveSchemaTree::ColumnData *col_data = dynamic_cast<LiveSchemaTree::ColumnData*>(column_node->get_data());

        columns->push_back(column_node->get_string(0));
        column_data->push_back(col_data);
      }
    }
  }

  return NULL;
}

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

  mforms::TreeNodeRef schema_node;
  mforms::TreeNodeRef table_node;
  
  error = "";
  
  // Inserts the key columns if not already present included
  if (sql_facade->parseSelectStatementForEdit(statement, schema_name, table_name, column_names))
  {
    if ( schema_name.length() == 0 )
      schema_name = _owner->active_schema();

    std::vector<std::string> columns;
    std::vector<wb::LiveSchemaTree::ColumnData*> col_data;

    // Pulls the information from the tree on the main thread
    mforms::Utilities::perform_from_main_thread(
                                  boost::bind(&SqlEditorTreeController::get_table_column_data, this,
                                  schema_name, table_name, &columns, &col_data));

    // Performs the validation against the column information...
    if (columns.size())
    {
      int size = columns.size();
      for(int index = 0; index < size; index++)
      {
        std::string column_name = columns[index];
        LiveSchemaTree::ColumnData *column_data = col_data[index];

        if (column_data->is_id)
        {
          // It could be ID and not PK
          bool is_pk = column_data->is_pk;

          //std::string column_name = column_node->get_string(0);

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

            if (found && !is_pk)
              found_id = true;
          }
        }
      }
      
      if (!found_id)
      {
        if(pk_count == 0)
          error = base::strfmt("Table %s has no primary keys or unique non nullable columns defined. Only tables with primary keys or unique non nullable columns can be edited.", table_name.c_str());
        else if(missing_pk)
          error = base::strfmt("The select statement is missing primary key or unique non nullable columns on table %s.", table_name.c_str());
      }
    }
    else
      error =base::strfmt("The column information for '%s' was not found. Make sure it is a valid table.", table_name.c_str());
  }
  else
    error = "The statement is not editable.";
  // The return value is true if:
  //     All the columns included on the PK were found on the select statement (in case of a compound PK)
  //     A unique non nullable column was found on the select statement
  return ((pk_count > 0) && !missing_pk) || found_id;
}


int SqlEditorTreeController::insert_text_to_active_editor(const std::string& str)
{
  Sql_editor::Ref editor(_owner->active_sql_editor());
  if (editor)
    editor->insert_text(str);
  return 0;
}
