backend/wbprivate/sqlide/wb_sql_editor_tree_controller.cpp (1,875 lines of code) (raw):

/* * Copyright (c) 2007, 2023, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, * as published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <regex> #include "grtpp_util.h" #include "wb_sql_editor_tree_controller.h" #include "wb_sql_editor_form.h" #include "execute_routine_wizard.h" #include "wb_sql_editor_panel.h" #include "base/sqlstring.h" #include "base/util_functions.h" #include "base/string_utilities.h" #include "base/boost_smart_ptr_helpers.h" #include "base/log.h" #include "base/notifications.h" #include "workbench/wb_command_ui.h" #include "workbench/wb_context_ui.h" #include <boost/signals2/connection.hpp> #include "diff/diffchange.h" #include "grtdb/diff_dbobjectmatch.h" #include "db.mysql/src/module_db_mysql.h" #include "grtdb/editor_table.h" #include "grtdb/db_helpers.h" #include "grtsqlparser/sql_facade.h" #include "workbench/wb_db_schema.h" #include "objimpl/wrapper/mforms_ObjectReference_impl.h" #include "advanced_sidebar.h" #include "mforms/textentry.h" #include "mforms/hypertext.h" #include "mforms/splitter.h" #include "mforms/tabview.h" #include "mforms/panel.h" #include "mforms/menubar.h" #include "mforms/utilities.h" using namespace grt; using namespace bec; using namespace wb; using namespace base; using namespace parsers; using boost::signals2::scoped_connection; DEFAULT_LOG_DOMAIN("SqlEditorSchemaTree"); static const char *SQL_EXCEPTION_MSG_FORMAT = _("Error Code: %i\n%s"); static const char *EXCEPTION_MSG_FORMAT = _("Error: %s"); //---------------------------------------------------------------------------------------------------------------------- #define CATCH_ANY_EXCEPTION_AND_DISPATCH(statement) \ catch (sql::SQLException & e) { \ _owner->add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), \ statement, ""); \ logError("SQLException executing %s: %s\n", std::string(statement).c_str(), \ strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()).c_str()); \ } \ CATCH_EXCEPTION_AND_DISPATCH(statement) #define CATCH_EXCEPTION_AND_DISPATCH(statement) \ catch (std::exception & e) { \ _owner->add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(EXCEPTION_MSG_FORMAT, e.what()), statement, ""); \ logError("Exception executing %s: %s\n", std::string(statement).c_str(), \ strfmt(EXCEPTION_MSG_FORMAT, e.what()).c_str()); \ } #define CATCH_ANY_EXCEPTION_AND_DISPATCH(statement) \ catch (sql::SQLException & e) { \ _owner->add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), \ statement, ""); \ logError("SQLException executing %s: %s\n", std::string(statement).c_str(), \ strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()).c_str()); \ } \ CATCH_EXCEPTION_AND_DISPATCH(statement) #define CATCH_ANY_EXCEPTION_AND_DISPATCH_TO_DEFAULT_LOG(statement) \ catch (sql::SQLException & e) { \ grt::GRT::get()->send_error(strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), statement); \ } \ catch (std::exception & e) { \ grt::GRT::get()->send_error(strfmt(EXCEPTION_MSG_FORMAT, e.what()), statement); \ } //---------------------------------------------------------------------------------------------------------------------- std::shared_ptr<SqlEditorTreeController> SqlEditorTreeController::create(SqlEditorForm *owner) { std::shared_ptr<SqlEditorTreeController> instance(new SqlEditorTreeController(owner)); instance->_base_schema_tree.set_delegate(instance); instance->_base_schema_tree.set_fetch_delegate(instance); instance->_filtered_schema_tree.set_delegate(instance); instance->_filtered_schema_tree.set_fetch_delegate(instance); instance->_filtered_schema_tree.set_base(&instance->_base_schema_tree); return instance; } //---------------------------------------------------------------------------------------------------------------------- SqlEditorTreeController::SqlEditorTreeController(SqlEditorForm *owner) : _owner(owner), _schema_side_bar(nullptr), _admin_side_bar(nullptr), _task_tabview(nullptr), _taskbar_box(nullptr), _schema_tree(&_base_schema_tree), _base_schema_tree(bec::versionToEnum(owner->rdbms_version())), _filtered_schema_tree(bec::versionToEnum(owner->rdbms_version())), live_schema_fetch_task(GrtThreadedTask::create()), live_schemata_refresh_task(GrtThreadedTask::create()), _is_refreshing_schema_tree(false), _use_show_procedure(false), _side_splitter(nullptr), _info_tabview(nullptr), _object_info(nullptr), _session_info(nullptr) { grt::GRTNotificationCenter::get()->add_grt_observer(this, "GRNDBObjectEditorCreated"); grt::GRTNotificationCenter::get()->add_grt_observer(this, "GRNPreferencesDidClose"); grt::GRTNotificationCenter::get()->add_grt_observer(this, "GRNSQLEditorReconnected"); base::NotificationCenter::get()->add_observer(this, "GNColorsChanged"); _base_schema_tree.is_schema_contents_enabled( bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ShowSchemaTreeSchemaContents", 1) != 0); _filtered_schema_tree.is_schema_contents_enabled( bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ShowSchemaTreeSchemaContents", 1) != 0); _base_schema_tree.sql_editor_text_insert_signal.connect( std::bind(&SqlEditorTreeController::insert_text_to_active_editor, this, std::placeholders::_1)); _filtered_schema_tree.sql_editor_text_insert_signal.connect( std::bind(&SqlEditorTreeController::insert_text_to_active_editor, this, std::placeholders::_1)); live_schemata_refresh_task->desc("Live Schema Refresh Task"); live_schemata_refresh_task->send_task_res_msg(false); live_schemata_refresh_task->msg_cb(std::bind(&SqlEditorForm::add_log_message, _owner, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, "")); live_schema_fetch_task->desc("Live Schema Fetch Task"); live_schema_fetch_task->send_task_res_msg(false); live_schema_fetch_task->msg_cb(std::bind(&SqlEditorForm::add_log_message, _owner, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, "")); } //---------------------------------------------------------------------------------------------------------------------- SqlEditorTreeController::~SqlEditorTreeController() { grt::GRTNotificationCenter::get()->remove_grt_observer(this); delete _taskbar_box; delete _info_tabview; delete _schema_side_bar; delete _admin_side_bar; delete _task_tabview; if (_side_splitter != NULL) _side_splitter->release(); delete _session_info; delete _object_info; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::finish_init() { // Box to host the management and SQL IDE task bars. _taskbar_box = new mforms::Box(false); // Left hand sidebar tabview with admin and schema tree pages. _task_tabview = new mforms::TabView(mforms::TabViewSelectorSecondary); _task_tabview->set_name("SQL IDE Sidebar Tabview"); _schema_side_bar = dynamic_cast<wb::AdvancedSidebar *>(mforms::TaskSidebar::create("SchemaTree")); scoped_connect(_schema_side_bar->on_section_command(), std::bind(&SqlEditorTreeController::sidebar_action, this, std::placeholders::_1)); _admin_side_bar = dynamic_cast<wb::SimpleSidebar *>(mforms::TaskSidebar::create("Simple")); scoped_connect(_admin_side_bar->on_section_command(), std::bind(&SqlEditorTreeController::sidebar_action, this, std::placeholders::_1)); _admin_side_bar->set_name("Administration Page"); _schema_side_bar->set_name("Schema Page"); mforms::TaskSectionFlags flags = mforms::TaskSectionRefreshable; _schema_side_bar->add_section("SchemaTree", "Schema Tree", _("SCHEMAS"), flags); _task_tabview->add_page(_admin_side_bar, _("Administration")); _task_tabview->add_page(_schema_side_bar, _("Schemas")); int i = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ActiveTaskTab", 0); if (i < 0) i = 0; else if (i >= 2) i = 1; _task_tabview->set_active_tab(i); _schema_side_bar->get_context_menu()->signal_will_show()->connect( std::bind(&SqlEditorTreeController::context_menu_will_show, this, std::placeholders::_1)); _schema_side_bar->set_schema_model(&_base_schema_tree); _schema_side_bar->set_filtered_schema_model(&_filtered_schema_tree); _schema_side_bar->set_selection_color(base::HighlightColor); int initial_splitter_pos = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:SidebarInitialSplitterPos", 500); _side_splitter = mforms::manage(new mforms::Splitter(false, false)); _side_splitter->set_name("SQL IDE Sidebar Splitter"); #ifdef _MSC_VER mforms::Panel *panel; _side_splitter->set_back_color(base::Color::getApplicationColorAsString(AppColorMainBackground, false)); panel = mforms::manage(new mforms::Panel(mforms::StyledHeaderPanel)); panel->set_title("Navigator"); panel->add(_taskbar_box); _taskbar_box->add(_task_tabview, true, true); _side_splitter->add(panel, 200); #else _taskbar_box->add(_task_tabview, true, true); _side_splitter->add(_taskbar_box, 200); #endif _info_tabview = new mforms::TabView(mforms::TabViewSelectorSecondary); _info_tabview->set_name("Info View"); #ifdef _MSC_VER panel = mforms::manage(new mforms::Panel(mforms::StyledHeaderPanel)); panel->set_title(_("Information")); panel->add(_info_tabview); _side_splitter->add(panel, 200); #else _side_splitter->add(_info_tabview, 200); #endif _object_info = new mforms::HyperText(); _session_info = new mforms::HyperText(); #ifdef _MSC_VER _object_info->set_padding(3, 3, 3, 3); _session_info->set_padding(3, 3, 3, 3); #endif _info_tabview->add_page(_object_info, _("Object Info")); _info_tabview->add_page(_session_info, _("Session")); updateColors(); scoped_connect(_schema_side_bar->signal_filter_changed(), std::bind(&SqlEditorTreeController::side_bar_filter_changed, this, std::placeholders::_1)); scoped_connect(_schema_side_bar->tree_node_selected(), std::bind(&SqlEditorTreeController::schema_row_selected, this)); // update the info box schema_row_selected(); tree_refresh(); // make sure to restore the splitter pos after layout is ready bec::GRTManager::get()->run_once_when_idle( this, std::bind(&mforms::Splitter::set_divider_position, _side_splitter, initial_splitter_pos)); // Connect the splitter change event after the setup is done to avoid wrong triggering. _splitter_connection = _side_splitter->signal_position_changed()->connect( std::bind(&SqlEditorTreeController::sidebar_splitter_changed, this)); // Setup grt access to sidebar. db_query_EditorRef editor(_owner->wbsql()->get_grt_editor_object(_owner)); if (editor.is_valid()) editor->sidebar(mforms_to_grt(_admin_side_bar, "TaskSidebar")); if (!_owner->connected()) _info_tabview->set_active_tab(1); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::prepare_close() { // Explicitly disconnect from the splitter change event as it sends unwanted change notifications // when controls are freed on shutdown. _splitter_connection.disconnect(); if (_schema_side_bar) bec::GRTManager::get()->set_app_option("DbSqlEditor:SidebarCollapseState", grt::StringRef(_schema_side_bar->get_collapse_states())); int tab = _task_tabview->get_active_tab(); bec::GRTManager::get()->set_app_option("DbSqlEditor:ActiveTaskTab", grt::IntegerRef(tab)); tab = _info_tabview->get_active_tab(); bec::GRTManager::get()->set_app_option("DbSqlEditor:ActiveInfoTab", grt::IntegerRef(tab)); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::schema_row_selected() { std::list<mforms::TreeNodeRef> nodes; std::string details; if (_schema_side_bar) { Color textColor = Color::getSystemColor(base::SystemColor::LabelColor); nodes = _schema_side_bar->get_schema_tree()->get_selection(); if (nodes.empty() || nodes.size() > 1) textColor.alpha = 0.75; details = "<html><body style='padding:15px; font-family:"; details += DEFAULT_FONT_FAMILY; details += "; font-size: 9pt; color: " + textColor.to_html() + ";'>"; if (nodes.empty()) { details += "<b>No object selected</b>"; } else if (nodes.size() > 1) { details += "<b>Multiple selected objects</b>"; } else { // When there's a single node selected, gets the details and tells it to notify if changes in it occur. details += _schema_tree->get_field_description(nodes.front()); _schema_tree->set_notify_on_reload(nodes.front()); } details += "</body></html>"; _object_info->set_markup_text(details); // send out notification about selection change grt::DictRef info(true); info.gset("selection-size", (int)nodes.size()); grt::GRTNotificationCenter::get()->send_grt("GRNLiveDBObjectSelectionDidChange", _owner->wbsql()->get_grt_editor_object(_owner), info); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::side_bar_filter_changed(const std::string &filter) { if (filter.length() > 0) _schema_tree = &_filtered_schema_tree; else _schema_tree = &_base_schema_tree; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::sidebar_splitter_changed() { int pos = _side_splitter->get_divider_position(); if (pos > 0) bec::GRTManager::get()->set_app_option("DbSqlEditor:SidebarInitialSplitterPos", grt::IntegerRef(pos)); } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorTreeController::fetch_data_for_filter( const std::string &schema_filter, const std::string &object_filter, const wb::LiveSchemaTree::NewSchemaContentArrivedSlot &arrived_slot) { std::string wb_internal_schema = bec::GRTManager::get()->get_app_option_string("workbench:InternalSchema"); sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); InternalSchema internal_schema(wb_internal_schema, conn); // Validates the remote search is available bool remote_search_enabled = internal_schema.is_remote_search_deployed(); if (!remote_search_enabled) { if (mforms::Utilities::show_message( _("Search Objects in Server"), base::strfmt(_("To enable searching objects in the remote server, MySQL Workbench needs to create a stored " "procedure in a custom schema (%s)."), wb_internal_schema.c_str()), _("Create"), _("Cancel")) == 1) { // Performs the deployment, any error will be returned std::string error = internal_schema.deploy_remote_search(); if (!error.length()) remote_search_enabled = true; else { std::string userName = _owner->connection_descriptor()->parameterValues().get_string("userName"); std::string msgFmt = _("The user %s has no privileges to create the required schema and stored procedures " "to enable remote search in this server. \n" "Ensure your database administrator creates a schema for internal use of MySQL Workbench" " with full privileges for the user %s, once created configure it in " "Preferences->General->Internal Workbench Schema and retry.\n\n%s."); std::string message = base::strfmt(msgFmt.c_str(), userName.c_str(), userName.c_str(), error.c_str()); mforms::Utilities::show_error("Search Objects in Server", message, "Ok"); } } } // If the remote search is available performs the search if (remote_search_enabled) { bool sync = !bec::GRTManager::get()->in_main_thread(); logDebug3("Fetch data for filter %s.%s\n", schema_filter.c_str(), object_filter.c_str()); live_schema_fetch_task->exec(sync, std::bind(&SqlEditorTreeController::do_fetch_data_for_filter, this, weak_ptr_from(this), schema_filter, object_filter, arrived_slot)); } return true; } //---------------------------------------------------------------------------------------------------------------------- std::vector<std::string> SqlEditorTreeController::fetch_schema_list() { static std::set<std::string> systemSchemaNames{"information_schema", "performance_schema", "mysql"}; std::vector<std::string> schemata_names; try { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); bool showSystemSchemas = bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ShowMetadataSchemata", 0) != 0; std::unique_ptr<sql::ResultSet> rs(conn->ref->getMetaData()->getSchemata()); while (rs->next()) { std::string name = rs->getString(1); if (name[0] == '.') continue; if (showSystemSchemas || systemSchemaNames.count(name) == 0) schemata_names.push_back(name); } } CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Get schemata")) return schemata_names; } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorTreeController::fetch_schema_contents( const std::string &schema_name, const wb::LiveSchemaTree::NewSchemaContentArrivedSlot &arrived_slot) { // in windows we use TreeViewAdv feature to expand nodes asynchronously // that is this function is already called from a separate thread // and it must have items loaded when it returns. bool sync = !bec::GRTManager::get()->in_main_thread(); logDebug3("Fetch schema contents for %s\n", schema_name.c_str()); live_schema_fetch_task->exec(sync, std::bind(&SqlEditorTreeController::do_fetch_live_schema_contents, this, weak_ptr_from(this), schema_name, arrived_slot)); return true; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::refresh_live_object_in_overview(wb::LiveSchemaTree::ObjectType type, const std::string schema_name, const std::string old_obj_name, const std::string new_obj_name) { try { // update schema tree even if no object was added/dropped, to clear details attribute which contents might to be // changed _schema_tree->update_live_object_state(type, schema_name, old_obj_name, new_obj_name); } CATCH_ANY_EXCEPTION_AND_DISPATCH_TO_DEFAULT_LOG(_("Refresh live schema object")) } //---------------------------------------------------------------------------------------------------------------------- mforms::View *SqlEditorTreeController::get_sidebar() { return _side_splitter; } //---------------------------------------------------------------------------------------------------------------------- grt::StringRef SqlEditorTreeController::do_fetch_live_schema_contents( std::weak_ptr<SqlEditorTreeController> self_ptr, const std::string &schema_name, wb::LiveSchemaTree::NewSchemaContentArrivedSlot arrived_slot) { RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR(SqlEditorTreeController, self_ptr, self, grt::StringRef("")) try { StringListPtr tables(new std::list<std::string>()); StringListPtr views(new std::list<std::string>()); StringListPtr procedures(new std::list<std::string>()); StringListPtr functions(new std::list<std::string>()); MutexLock schema_contents_mutex(_schema_contents_mutex); if (!arrived_slot) return grt::StringRef(""); { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); { std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs( stmt->executeQuery(std::string(sqlstring("SHOW FULL TABLES FROM !", 0) << schema_name))); while (rs->next()) { std::string name = rs->getString(1); std::string type = rs->getString(2); if (type == "VIEW") views->push_back(name); else tables->push_back(name); } } { std::unique_ptr<sql::ResultSet> rs( stmt->executeQuery(std::string(sqlstring("SHOW PROCEDURE STATUS WHERE Db=?", 0) << schema_name))); while (rs->next()) { std::string name = rs->getString(2); procedures->push_back(name); } } { std::unique_ptr<sql::ResultSet> rs( stmt->executeQuery(std::string(sqlstring("SHOW FUNCTION STATUS WHERE Db=?", 0) << schema_name))); while (rs->next()) { std::string name = rs->getString(2); functions->push_back(name); } } } if (arrived_slot) { std::function<void()> schema_contents_arrived = std::bind(arrived_slot, schema_name, tables, views, procedures, functions, false); bec::GRTManager::get()->run_once_when_idle(this, schema_contents_arrived); } // Let the owner form know we got fresh schema meta data. Can be used to update caches. _owner->schema_meta_data_refreshed(schema_name, tables, views, procedures, functions); } catch (const sql::SQLException &e) { _owner->add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), "Error loading schema content", ""); logError("SQLException executing %s: %s\n", std::string("Error loading schema content").c_str(), strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()).c_str()); if (arrived_slot) { StringListPtr empty_list; std::function<void()> schema_contents_arrived = std::bind(arrived_slot, schema_name, empty_list, empty_list, empty_list, empty_list, false); bec::GRTManager::get()->run_once_when_idle(this, schema_contents_arrived); } } return grt::StringRef(""); } //---------------------------------------------------------------------------------------------------------------------- grt::StringRef SqlEditorTreeController::do_fetch_data_for_filter( std::weak_ptr<SqlEditorTreeController> self_ptr, const std::string &schema_filter, const std::string &object_filter, wb::LiveSchemaTree::NewSchemaContentArrivedSlot arrived_slot) { RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR(SqlEditorTreeController, self_ptr, self, grt::StringRef("")) logDebug3("Searching data for %s.%s\n", schema_filter.c_str(), object_filter.c_str()); std::shared_ptr<sql::ResultSet> dbc_resultset; std::map<std::string, int> schema_directory; std::string last_schema; std::string wb_internal_schema = bec::GRTManager::get()->get_app_option_string("workbench:InternalSchema"); try { // Creates the template for the sqlstring std::string procedure(base::sqlstring("CALL !.SEARCH_OBJECTS(?,?,0)", 0) << wb_internal_schema << schema_filter << object_filter); // Gets the data std::string error = _owner->fetch_data_from_stored_procedure(procedure, dbc_resultset); if (dbc_resultset && !error.length()) { StringListPtr tables(new std::list<std::string>()); StringListPtr views(new std::list<std::string>()); StringListPtr procedures(new std::list<std::string>()); StringListPtr functions(new std::list<std::string>()); // Creates the needed schema/objects while (dbc_resultset->next()) { std::string schema = dbc_resultset->getString(1); std::string object = dbc_resultset->getString(2); std::string type = dbc_resultset->getString(3); // A schema change occurred, need to create the structure for the data loaded so far if (schema != last_schema && last_schema != "") { if (arrived_slot) bec::GRTManager::get()->run_once_when_idle( this, std::bind(arrived_slot, last_schema, tables, views, procedures, functions, true)); tables->clear(); views->clear(); procedures->clear(); functions->clear(); } last_schema = schema; if (type == "T") tables->push_back(object); else if (type == "V") views->push_back(object); else if (type == "P") procedures->push_back(object); else functions->push_back(object); } if (last_schema != "" && arrived_slot) bec::GRTManager::get()->run_once_when_idle( this, std::bind(arrived_slot, last_schema, tables, views, procedures, functions, true)); } else { std::string userName = _owner->connection_descriptor()->parameterValues().get_string("userName"); std::string msgFmt = _("The user %s has no privileges on %s to create temporal tables or execute required stored procedures " "used in remote search in this server.\n" "Ensure your database administrator grants you full access to the schema %s and retry.\n\n%s."); std::string message = base::strfmt(msgFmt.c_str(), userName.c_str(), wb_internal_schema.c_str(), wb_internal_schema.c_str(), error.c_str()); mforms::Utilities::show_error(_("Search Objects in Server"), message, _("Ok")); } } CATCH_ANY_EXCEPTION_AND_DISPATCH_TO_DEFAULT_LOG(_("Get data for filter")) return grt::StringRef(""); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::fetch_column_data(const std::string &schema_name, const std::string &obj_name, wb::LiveSchemaTree::ObjectType type, const wb::LiveSchemaTree::NodeChildrenUpdaterSlot &updater_slot) { // Searches for the target node... mforms::TreeNodeRef node = _schema_tree->get_node_for_object(schema_name, type, obj_name); LiveSchemaTree::ViewData *pdata = NULL; if (node) pdata = dynamic_cast<LiveSchemaTree::ViewData *>(node->get_data()); // Loads the information... StringListPtr columns(new std::list<std::string>); std::map<std::string, LiveSchemaTree::ColumnData> column_data; logDebug3("Fetching column data for %s.%s\n", schema_name.c_str(), obj_name.c_str()); try { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs( stmt->executeQuery(std::string(base::sqlstring("SHOW FULL COLUMNS FROM !.!", 0) << schema_name << obj_name))); while (rs->next()) { LiveSchemaTree::ColumnData col_node(type); std::string column_name = rs->getString(1); columns->push_back(column_name); std::string type = rs->getString(2); std::string collation = rs->isNull(3) ? "" : rs->getString(3); std::string nullable = rs->getString(4); std::string key = rs->getString(5); std::string default_value = rs->getString(6); std::string extra = rs->getString(7); base::replaceStringInplace(type, "unsigned", "UN"); if (extra == "auto_increment") type += " AI"; col_node.name = column_name; col_node.type = type; col_node.charset_collation = collation; col_node.is_pk = key == "PRI"; col_node.is_id = (col_node.is_pk || (nullable == "NO" && key == "UNI")); col_node.is_idx = key != ""; col_node.default_value = default_value; column_data[column_name] = col_node; } // If information was found, creates the TreeNode structure for it if (columns->size()) { // Creates the node if it didn't exist... if (!node) { node = _schema_tree->create_node_for_object(schema_name, type, obj_name); if (node) pdata = dynamic_cast<LiveSchemaTree::ViewData *>(node->get_data()); else logWarning("Error fetching column information for '%s'.'%s'\n", schema_name.c_str(), obj_name.c_str()); } if (pdata) { // Identifies the node that will be the parent for the loaded columns... mforms::TreeNodeRef target_parent; if (pdata->get_type() == LiveSchemaTree::Table) { target_parent = node->get_child(wb::LiveSchemaTree::TABLE_COLUMNS_NODE_INDEX); type = LiveSchemaTree::TableColumn; } else if (pdata->get_type() == LiveSchemaTree::View) { target_parent = node; type = LiveSchemaTree::ViewColumn; } if (target_parent) { updater_slot(target_parent, columns, type, false, false); for (int index = 0; index < target_parent->count(); index++) { mforms::TreeNodeRef child = target_parent->get_child(index); LiveSchemaTree::LSTData *pchilddata = dynamic_cast<LiveSchemaTree::LSTData *>(child->get_data()); LiveSchemaTree::LSTData *psource = &column_data[child->get_string(0)]; pchilddata->copy(psource); } pdata->columns_load_error = false; pdata->set_loaded_data(LiveSchemaTree::COLUMN_DATA); _schema_tree->notify_on_reload(target_parent); } } } } catch (const sql::SQLException &exc) { logWarning("Error fetching column information for '%s'.'%s': %s\n", schema_name.c_str(), obj_name.c_str(), exc.what()); // Sets flag indicating error loading columns ( Used for broken views ) if (pdata) { if (exc.getErrorCode() == 1356) pdata->columns_load_error = true; pdata->details = exc.what(); _schema_tree->update_node_icon(node); } } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::fetch_trigger_data(const std::string &schema_name, const std::string &obj_name, wb::LiveSchemaTree::ObjectType type, const wb::LiveSchemaTree::NodeChildrenUpdaterSlot &updater_slot) { // Loads the information... StringListPtr triggers(new std::list<std::string>); // std::list<std::string> triggers; std::map<std::string, LiveSchemaTree::TriggerData> trigger_data_dict; try { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs( stmt->executeQuery(std::string(base::sqlstring("SHOW TRIGGERS FROM ! LIKE ?", 0) << schema_name << obj_name))); while (rs->next()) { wb::LiveSchemaTree::TriggerData trigger_node; std::string name = rs->getString(1); trigger_node.event_manipulation = wb::LiveSchemaTree::internalize_token(rs->getString(2)); trigger_node.timing = wb::LiveSchemaTree::internalize_token(rs->getString(5)); triggers->push_back(name); trigger_data_dict[name] = trigger_node; } // If information was found, creates the TreeNode structure for it // Searches for the target node... mforms::TreeNodeRef node = _schema_tree->get_node_for_object(schema_name, type, obj_name); // Creates the node if it didn't exist... if (!node) node = _schema_tree->create_node_for_object(schema_name, type, obj_name); // Identifies the node that will be the parent for the loaded columns... mforms::TreeNodeRef target_parent = node->get_child(wb::LiveSchemaTree::TABLE_TRIGGERS_NODE_INDEX); updater_slot(target_parent, triggers, LiveSchemaTree::Trigger, false, false); for (int index = 0; index < target_parent->count(); index++) { mforms::TreeNodeRef child = target_parent->get_child(index); LiveSchemaTree::LSTData *pchilddata = dynamic_cast<LiveSchemaTree::LSTData *>(child->get_data()); LiveSchemaTree::LSTData *psource = &trigger_data_dict[child->get_string(0)]; pchilddata->copy(psource); } // Where there data or not the triggers were loaded LiveSchemaTree::ViewData *pdata = dynamic_cast<LiveSchemaTree::ViewData *>(node->get_data()); pdata->set_loaded_data(LiveSchemaTree::TRIGGER_DATA); _schema_tree->notify_on_reload(target_parent); } catch (const sql::SQLException &exc) { logWarning("Error fetching trigger information for '%s'.'%s': %s\n", schema_name.c_str(), obj_name.c_str(), exc.what()); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::fetch_index_data(const std::string &schema_name, const std::string &obj_name, wb::LiveSchemaTree::ObjectType type, const wb::LiveSchemaTree::NodeChildrenUpdaterSlot &updater_slot) { // Loads the information... StringListPtr indexes(new std::list<std::string>()); std::map<std::string, LiveSchemaTree::IndexData> index_data_dict; try { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs( stmt->executeQuery(std::string(base::sqlstring("SHOW INDEXES FROM !.!", 0) << schema_name << obj_name))); bool supportVisibility = _owner->rdbms_version().is_valid() && is_supported_mysql_version_at_least(_owner->rdbms_version(), 8, 0, 0); while (rs->next()) { LiveSchemaTree::IndexData index_data; std::string name = rs->getString(3); // Inserts the index to the list if (!index_data_dict.count(name)) { indexes->push_back(name); index_data.type = wb::LiveSchemaTree::internalize_token(rs->getString(11)); index_data.unique = (rs->getInt(2) == 0); if (supportVisibility) { index_data.visible = rs->getString(14) == "YES"; } index_data_dict[name] = index_data; } // Adds the column index_data_dict[name].columns.push_back(rs->getString(5)); } // Searches for the target node... mforms::TreeNodeRef node = _schema_tree->get_node_for_object(schema_name, type, obj_name); // Creates the node if it didn't exist... if (!node) node = _schema_tree->create_node_for_object(schema_name, type, obj_name); // Identifies the node that will be the parent for the loaded indexes... mforms::TreeNodeRef target_parent = node->get_child(wb::LiveSchemaTree::TABLE_INDEXES_NODE_INDEX); updater_slot(target_parent, indexes, LiveSchemaTree::Index, false, false); for (int index = 0; index < target_parent->count(); index++) { mforms::TreeNodeRef child = target_parent->get_child(index); LiveSchemaTree::LSTData *pchilddata = dynamic_cast<LiveSchemaTree::LSTData *>(child->get_data()); LiveSchemaTree::LSTData *psource = &index_data_dict[child->get_string(0)]; pchilddata->copy(psource); } LiveSchemaTree::ViewData *pdata = dynamic_cast<LiveSchemaTree::ViewData *>(node->get_data()); pdata->set_loaded_data(LiveSchemaTree::INDEX_DATA); _schema_tree->notify_on_reload(target_parent); } catch (const sql::SQLException &exc) { logWarning("Error fetching index information for '%s'.'%s': %s\n", schema_name.c_str(), obj_name.c_str(), exc.what()); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::fetch_foreign_key_data(const std::string &schema_name, const std::string &obj_name, wb::LiveSchemaTree::ObjectType type, const wb::LiveSchemaTree::NodeChildrenUpdaterSlot &updater_slot) { StringListPtr foreign_keys(new std::list<std::string>()); std::map<std::string, LiveSchemaTree::FKData> fk_data_dict; sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); try { std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs( stmt->executeQuery(std::string(base::sqlstring("SHOW CREATE TABLE !.!", 0) << schema_name << obj_name))); while (rs->next()) { std::string statement = rs->getString(2); size_t def_start = statement.find("("); size_t def_end = statement.rfind(")"); std::vector<std::string> def_lines = base::split(statement.substr(def_start, def_end - def_start), "\n"); const std::string pattern = "CONSTRAINT\\s*(\\S*)\\s*FOREIGN KEY\\s*\\((\\S*)\\)\\s*REFERENCES\\s*(\\S*)\\s*\\((\\S*)\\)\\s*((\\w*\\s*)*),?$"; std::regex selfRegex(pattern, std::regex_constants::icase); std::string fk_name; std::string fk_columns; std::string fk_ref_table; std::string fk_ref_columns; std::string fk_rules; for (size_t index = 0; index < def_lines.size(); index++) { auto resultBegin = std::sregex_iterator(def_lines[index].begin(), def_lines[index].end(), selfRegex); auto resultEnd = std::sregex_iterator(); auto count = std::distance(resultBegin, resultEnd); if (count > 0) { for (auto iter = resultBegin; iter != resultEnd; ++iter) { std::smatch match = *iter; for (int i = 0; i < match.length(); ++i) { switch (i) { case 1: fk_name = match[i].str(); fk_name = base::unquote_identifier(fk_name); break; case 2: fk_columns = match[i].str(); break; case 3: fk_ref_table = match[i].str(); fk_ref_table = base::unquote_identifier(fk_ref_table); ; break; case 4: fk_ref_columns = match[i].str(); break; case 5: fk_rules = match[i].str(); break; default: break; } } } // Parses the list fields std::vector<std::string> fk_column_list = base::split(fk_columns, ","); std::vector<std::string> fk_ref_column_list = base::split(fk_ref_columns, ","); std::vector<std::string> fk_rule_tokens = base::split(fk_rules, " "); // Create the foreign key node wb::LiveSchemaTree::FKData new_fk; foreign_keys->push_back(fk_name); new_fk.referenced_table = (fk_ref_table); // Set the default update and delete rules new_fk.update_rule = new_fk.delete_rule = wb::LiveSchemaTree::internalize_token("RESTRICT"); // A rule has at least 3 tokens so the number of tokens could be // 3 or 4 for 1 rule and 6,7,8 for two rules, so we get the number with this size_t rule_count = fk_rule_tokens.size() / 3; int token_offset = 0; for (size_t index = 0; index < rule_count; index++) { // Skips the ON token token_offset++; // Gets the UPDATE/DELETE token std::string rule = fk_rule_tokens[token_offset++]; // Gets the action std::string action = fk_rule_tokens[token_offset++]; if (action == "SET" || action == "NO") action += " " + fk_rule_tokens[token_offset++]; const unsigned char value = wb::LiveSchemaTree::internalize_token(action); if (rule == "UPDATE") new_fk.update_rule = value; else new_fk.delete_rule = value; } std::string from(""), to(""); for (size_t column_index = 0; column_index < fk_column_list.size(); column_index++) { std::string from_col = base::unquote_identifier(fk_column_list[column_index]); std::string to_col = base::unquote_identifier(fk_ref_column_list[column_index]); if (from.empty()) from = from_col; else from.append(", ").append(from_col); if (to.empty()) to = to_col; else to.append(", ").append(to_col); } new_fk.from_cols = from; new_fk.to_cols = to; fk_data_dict[fk_name] = new_fk; } } } // Searches for the target node... mforms::TreeNodeRef node = _schema_tree->get_node_for_object(schema_name, type, obj_name); // Creates the node if it didn't exist... if (!node) node = _schema_tree->create_node_for_object(schema_name, type, obj_name); // Identifies the node that will be the parent for the loaded columns... mforms::TreeNodeRef target_parent = node->get_child(wb::LiveSchemaTree::TABLE_FOREIGN_KEYS_NODE_INDEX); updater_slot(target_parent, foreign_keys, LiveSchemaTree::ForeignKey, false, false); for (int index = 0; index < target_parent->count(); index++) { mforms::TreeNodeRef child = target_parent->get_child(index); LiveSchemaTree::LSTData *pchilddata = dynamic_cast<LiveSchemaTree::LSTData *>(child->get_data()); LiveSchemaTree::LSTData *psource = &fk_data_dict[child->get_string(0)]; pchilddata->copy(psource); } LiveSchemaTree::ViewData *pdata = dynamic_cast<LiveSchemaTree::ViewData *>(node->get_data()); pdata->set_loaded_data(LiveSchemaTree::FK_DATA); _schema_tree->notify_on_reload(target_parent); } catch (const sql::SQLException &exc) { logWarning("Error fetching foreign key information for '%s'.'%s': %s\n", schema_name.c_str(), obj_name.c_str(), exc.what()); } } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorTreeController::fetch_object_details(const std::string &schema_name, const std::string &object_name, wb::LiveSchemaTree::ObjectType type, short flags, const wb::LiveSchemaTree::NodeChildrenUpdaterSlot &updater_slot) { // If the type has not been specified, pulls it from the database // Most of the time the type will be specified as it can bee retrieved from // the LST, this is to handle the case when a query is executed using direct SQL // and the tree hasn't been populated with the target object if (type == wb::LiveSchemaTree::Any) type = fetch_object_type(schema_name, object_name); if (type != wb::LiveSchemaTree::Any) { if (flags & wb::LiveSchemaTree::COLUMN_DATA) fetch_column_data(schema_name, object_name, type, updater_slot); if (flags & wb::LiveSchemaTree::INDEX_DATA) fetch_index_data(schema_name, object_name, type, updater_slot); if (flags & wb::LiveSchemaTree::TRIGGER_DATA) fetch_trigger_data(schema_name, object_name, type, updater_slot); if (flags & wb::LiveSchemaTree::FK_DATA) fetch_foreign_key_data(schema_name, object_name, type, updater_slot); } return false; } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorTreeController::fetch_routine_details(const std::string &schema_name, const std::string &obj_name, wb::LiveSchemaTree::ObjectType type) { bool ret_val = false; std::string object = type == LiveSchemaTree::Function ? "FUNCTION" : "PROCEDURE"; std::string statement = "SHOW CREATE " + object + " !.!"; // Loads the information... std::list<std::string> indexes; std::map<std::string, LiveSchemaTree::IndexData> index_data_dict; try { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs( stmt->executeQuery(std::string(base::sqlstring(statement.c_str(), 0) << schema_name << obj_name))); if (rs->next()) { LiveSchemaTree::IndexData index_data; std::string ddl = rs->getString(3); SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms(_owner->rdbms()); SqlFacade::String_tuple_list parameters; std::string ddl_type, ddl_name, ddl_ret, ddl_comments; ddl = "DELIMITER " + bec::GRTManager::get()->get_app_option_string("SqlDelimiter", "$$") + "\n" + ddl; sql_facade->parseRoutineDetails(ddl, ddl_type, ddl_name, parameters, ddl_ret, ddl_comments); std::string details = ""; if (parameters.size()) { details = _("<b>Parameters:</b>"); details += "<table border=0>"; SqlFacade::String_tuple_list::iterator index, end = parameters.end(); for (index = parameters.begin(); index != end; index++) { details += "<tr><td style=\"border:none; padding-left: 15px;\">" + index->first + ":</td>"; details += "<td style=\"border:none; padding-left: 15px;\"><font color='#717171'>" + index->second + "</td></tr>"; } details += "</table>"; } if (!ddl_ret.empty()) details += "<br><br><b><font color='#000000'>Returns: </b><font color='#717171'>" + ddl_ret; if (!ddl_comments.empty()) details += "<br><br><b><font color='#000000'>Comments: </b><font color='#717171'>" + ddl_comments; LiveSchemaTree::ObjectData new_data; new_data.details = details; // ddl.substr(start, end - start); new_data.fetched = true; // Searches for the target node... mforms::TreeNodeRef node = _schema_tree->get_node_for_object(schema_name, type, obj_name); // The node should exist as this method is only called for a selected node if (node) { LiveSchemaTree::ObjectData *ptargetdata = dynamic_cast<LiveSchemaTree::ObjectData *>(node->get_data()); ptargetdata->copy(&new_data); ret_val = true; } } } catch (const sql::SQLException &exc) { logWarning("Error fetching routine information for '%s'.'%s': %s\n", schema_name.c_str(), obj_name.c_str(), exc.what()); } return ret_val; } //---------------------------------------------------------------------------------------------------------------------- wb::LiveSchemaTree::ObjectType SqlEditorTreeController::fetch_object_type(const std::string &schema_name, const std::string &obj_name) { wb::LiveSchemaTree::ObjectType type = wb::LiveSchemaTree::Any; try { MutexLock schema_contents_mutex(_schema_contents_mutex); { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); { std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs( stmt->executeQuery(std::string(sqlstring("SHOW FULL TABLES FROM ! LIKE ?", 0) << schema_name << obj_name))); while (rs->next()) { std::string str_type = rs->getString(2); if (str_type == "VIEW") type = wb::LiveSchemaTree::View; else type = wb::LiveSchemaTree::Table; } } } } CATCH_ANY_EXCEPTION_AND_DISPATCH_TO_DEFAULT_LOG(_("Get schema contents")); return type; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::tree_refresh() { if (_owner->connected()) { live_schemata_refresh_task->exec(false, std::bind((grt::StringRef(SqlEditorTreeController::*)(SqlEditorForm::Ptr)) & SqlEditorTreeController::do_refresh_schema_tree_safe, this, weak_ptr_from(_owner))); _schema_tree->set_enabled(true); } else { _schema_tree->set_no_connection(); _schema_tree->set_enabled(false); } } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorTreeController::sidebar_action(const std::string &name) { return false; } //---------------------------------------------------------------------------------------------------------------------- /** * Activate one or more objects. The term "activate" is a bit misleading as we do other operations too * (like clipboard handling). */ void SqlEditorTreeController::tree_activate_objects(const std::string &action, const std::vector<wb::LiveSchemaTree::ChangeRecord> &changes) { // Most of the activations should lead to a single result (e.g. all clipboard ops go into one string). std::string action_modifier; // action can contain prefix denoting action modifier std::string real_action = action; // if action modifier is present real_action will store part after w/o modifier prefix if (real_action == "select_data_columns") { typedef std::string TableName; typedef std::string ColumnName; std::map<TableName, std::map<ColumnName, std::string> > table_column_types; std::string text; typedef std::map<TableName, std::string> TableStringMap; TableStringMap first_text; TableStringMap second_text; // cache default values for tables for (size_t i = 0; i < changes.size(); i++) { TableName full_table_name = sqlstring("!.!", 0) << changes[i].schema << changes[i].name; std::string column_type; if (!first_text[full_table_name].empty()) first_text[full_table_name].append(", "); first_text[full_table_name].append(changes[i].detail); } for (const TableStringMap::value_type &table_columns : first_text) text += strfmt("SELECT %s\nFROM %s;\n", table_columns.second.c_str(), table_columns.first.c_str()); _owner->run_sql_in_scratch_tab(text, false, true); } try { for (size_t i = 0; i < changes.size(); i++) { std::string sql; switch (changes[i].type) { case LiveSchemaTree::Schema: if (real_action == "filter") { _schema_side_bar->get_filter_entry()->set_value(changes[i].name); (*_schema_side_bar->get_filter_entry()->signal_changed())(); } else if (real_action == "inspect") _owner->inspect_object(changes[i].name, "", "db.Schema"); else if (real_action == "alter") do_alter_live_object(LiveSchemaTree::Schema, changes[i].name, changes[i].name); else _owner->active_schema(changes[i].name); break; case LiveSchemaTree::Table: if (real_action == "activate" || real_action == "edit_data" || real_action == "select_data") sql = sqlstring("SELECT * FROM !.!;", base::QuoteOnlyIfNeeded) << changes[i].schema << changes[i].name; else if (real_action == "inspect") _owner->inspect_object(changes[i].schema, changes[i].name, "db.Table"); else if (real_action == "alter") do_alter_live_object(changes[i].type, changes[i].schema, changes[i].name); break; case LiveSchemaTree::View: if (real_action == "activate" || real_action == "edit_data" || real_action == "select_data") sql = sqlstring("SELECT * FROM !.!;", base::QuoteOnlyIfNeeded) << changes[i].schema << changes[i].name; else if (real_action == "inspect") _owner->inspect_object(changes[i].schema, changes[i].name, "db.View"); else if (real_action == "alter") do_alter_live_object(changes[i].type, changes[i].schema, changes[i].name); break; case LiveSchemaTree::Procedure: case LiveSchemaTree::Function: if (real_action == "alter") do_alter_live_object(changes[i].type, changes[i].schema, changes[i].name); else if (real_action == "execute") sql = run_execute_routine_wizard(changes[i].type, changes[i].schema, changes[i].name); break; default: break; } if (!sql.empty()) { bool _autosave = _owner->get_autosave_disabled(); _owner->set_autosave_disabled(true); SqlEditorPanel *ed = _owner->run_sql_in_scratch_tab(sql, false, true); if (ed) ed->set_title(changes[i].name); _owner->set_autosave_disabled(_autosave); } } } catch (std::exception &exc) { mforms::Utilities::show_error("Error", exc.what(), "OK"); } } //---------------------------------------------------------------------------------------------------------------------- /** * Convenience API for the activation interface. */ void SqlEditorTreeController::schema_object_activated(const std::string &action, wb::LiveSchemaTree::ObjectType type, const std::string &schema, const std::string &name) { std::vector<wb::LiveSchemaTree::ChangeRecord> changes; wb::LiveSchemaTree::ChangeRecord record = {type, schema, name, ""}; changes.push_back(record); tree_activate_objects(action, changes); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::do_alter_live_object(wb::LiveSchemaTree::ObjectType type, const std::string &schema_name, const std::string &aobj_name) { std::string used_schema_name = schema_name; std::string obj_name = aobj_name; try { db_mgmt_RdbmsRef rdbms = _owner->rdbms(); // std::string database_package= *rdbms->databaseObjectPackage(); if (rdbms.is_valid()) { rdbms = grt::shallow_copy_object(rdbms); rdbms->version(grt::shallow_copy_object(_owner->rdbms_version())); rdbms->version()->owner(rdbms); } // reset_references on the catalog is called when we try to apply changes (generate the alter script). db_mysql_CatalogRef client_state_catalog = grt::GRT::get()->create_object<db_mysql_Catalog>("db.mysql.Catalog"); client_state_catalog->name("default"); client_state_catalog->oldName("default"); client_state_catalog->version(rdbms->version()); grt::replace_contents(client_state_catalog->simpleDatatypes(), rdbms->simpleDatatypes()); grt::replace_contents(client_state_catalog->characterSets(), rdbms->characterSets()); // XXX this should be changed when/if global userDatatypes are added // XXX grt::replace_contents(client_state_catalog->userDatatypes(), // XXX workbench_physical_ModelRef::cast_from(_live_physical_overview->get_model())->catalog()->userDatatypes()); db_mysql_SchemaRef schema; if (wb::LiveSchemaTree::Schema != type) { if (used_schema_name == "") used_schema_name = _owner->active_schema(); if (used_schema_name == "") { mforms::Utilities::show_warning( strfmt(_("No Schema Selected")), _("A default schema must be set by double clicking its name in the SCHEMA list."), _("OK")); return; } else { schema = grt::GRT::get()->create_object<db_mysql_Schema>("db.mysql.Schema"); schema->owner(client_state_catalog); schema->name(used_schema_name); schema->oldName(used_schema_name); client_state_catalog->schemata().insert(schema); client_state_catalog->defaultSchema(schema); } } bool is_object_new = obj_name.empty(); std::string ddl_script; std::string sql_mode; if (!is_object_new) { // parse selected object DDL into auxiliary catalog ddl_script = get_object_ddl_script(type, used_schema_name, obj_name); if (ddl_script.empty()) { logWarning("Unable to get DDL for %s.%s\n", used_schema_name.c_str(), obj_name.c_str()); return; } { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); if (conn) _owner->get_session_variable(conn->ref.get(), "sql_mode", sql_mode); } // if this is a View, then auto-reformat it before sending it to parser/editor if (type == wb::LiveSchemaTree::View && bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ReformatViewDDL", 0)) { try { grt::Module *module = grt::GRT::get()->get_module("SQLIDEUtils"); grt::BaseListRef args(true); args.ginsert(grt::StringRef(ddl_script)); ddl_script = grt::StringRef::cast_from(module->call_function("reformatSQLStatement", args)); } catch (std::exception &exc) { logWarning("Error reformatting view code: %s\n", exc.what()); } } if (!parse_ddl_into_catalog(client_state_catalog, strfmt("`%s`.`%s`", schema_name.c_str(), obj_name.c_str()), ddl_script, sql_mode, schema_name)) { logWarning("Error parsing DDL for %s.%s: %s\n", schema_name.c_str(), obj_name.c_str(), ddl_script.c_str()); return; } } // Make a copy of the catalog before we modify it with new/edited objects, // so we can later do a diff between both to find changes. db_CatalogRef server_state_catalog = grt::copy_object(client_state_catalog); // "reset_references" for the created object is called when the base editor is destroyed. db_DatabaseObjectRef db_object; switch (type) { case wb::LiveSchemaTree::Schema: db_object = is_object_new ? create_new_schema(client_state_catalog) : db_SchemaRef::cast_from(find_named_object_in_list(client_state_catalog->schemata(), obj_name)); break; case wb::LiveSchemaTree::Table: db_object = is_object_new ? create_new_table(schema) : db_TableRef::cast_from(find_named_object_in_list(schema->tables(), obj_name)); break; case wb::LiveSchemaTree::View: db_object = is_object_new ? create_new_view(schema) : db_ViewRef::cast_from(find_named_object_in_list(schema->views(), obj_name)); break; case wb::LiveSchemaTree::Procedure: case wb::LiveSchemaTree::Function: db_object = is_object_new ? create_new_routine(schema, type) : db_RoutineRef::cast_from(find_named_object_in_list(schema->routines(), obj_name)); break; default: break; } if (db_object.is_valid()) { db_object->customData().set("sqlMode", grt::StringRef(sql_mode)); db_object->customData().set("originalObjectDDL", grt::StringRef(ddl_script)); open_alter_object_editor(db_object, server_state_catalog); } else logError("Failed to create/alter `%s`.`%s`", used_schema_name.c_str(), obj_name.c_str()); } catch (const std::exception &e) { logError("Failed to create/alter `%s`.`%s`: %s", used_schema_name.c_str(), obj_name.c_str(), e.what()); mforms::Utilities::show_error( strfmt(_("Failed to create/alter `%s`.`%s`"), used_schema_name.c_str(), obj_name.c_str()), e.what(), _("OK")); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::open_alter_object_editor(db_DatabaseObjectRef object, db_CatalogRef server_state_catalog) { db_CatalogRef client_state_catalog; if (db_SchemaRef::can_wrap(object)) { if (!object->owner().is_valid()) throw std::invalid_argument("schema object does not have owner set to expected value"); client_state_catalog = db_CatalogRef::cast_from(object->owner()); } else { if (!object->owner().is_valid()) throw std::invalid_argument("object does not have owner set to expected schema value"); if (!object->owner()->owner().is_valid()) throw std::invalid_argument("object does not have owner set to expected schema value"); client_state_catalog = db_CatalogRef::cast_from(object->owner()->owner()); } sql::Dbc_connection_handler::Ref conn; grt::NormalizedComparer comparer; { RecMutexLock lock(_owner->ensure_valid_aux_connection(conn)); // db_object->customData().set("CaseSensitive",grt::IntegerRef(conn->ref->getMetaData()->storesMixedCaseIdentifiers())); // TODO use DB_Plugin here somehow comparer.load_db_options(conn->ref->getMetaData()); } db_mgmt_RdbmsRef rdbms = _owner->rdbms(); // std::string database_package= *rdbms->databaseObjectPackage(); if (rdbms.is_valid()) { rdbms = grt::shallow_copy_object(rdbms); rdbms->version(grt::shallow_copy_object(_owner->rdbms_version())); rdbms->version()->owner(rdbms); } if (!client_state_catalog->version().is_valid()) client_state_catalog->version(rdbms->version()); if (!server_state_catalog->version().is_valid()) server_state_catalog->version(rdbms->version()); object->customData().set("DBSettings", comparer.get_options_dict()); object->customData().set("liveRdbms", _owner->rdbms()); object->customData().set("ownerSqlEditor", _owner->wbsql()->get_grt_editor_object(_owner)); object->customData().set("clientStateCatalog", client_state_catalog); object->customData().set("serverStateCatalog", server_state_catalog); // TODO: make docking/non-docking switchable via preferences. //_context_ui->get_wb()->open_object_editor(db_object, bec::StandaloneWindowFlag); bec::GRTManager::get()->open_object_editor(object, bec::ForceNewWindowFlag); } //---------------------------------------------------------------------------------------------------------------------- std::string SqlEditorTreeController::run_execute_routine_wizard(wb::LiveSchemaTree::ObjectType type, const std::string &schema_name, const std::string &obj_name) { std::pair<std::string, std::string> script = get_object_create_script(type, schema_name, obj_name); if (script.second.empty()) return ""; // get_object_create_script() already showed an error. db_mysql_RoutineRef routine(grt::Initialized); parsers::MySQLParserServices::Ref services = parsers::MySQLParserServices::get(); db_mysql_CatalogRef catalog(grt::Initialized); catalog->version(_owner->rdbms_version()); grt::replace_contents(catalog->simpleDatatypes(), _owner->rdbms()->simpleDatatypes()); db_mysql_SchemaRef schema(grt::Initialized); schema->owner(catalog); schema->name(schema_name); catalog->schemata().insert(schema); routine->owner(schema); schema->routines().insert(routine); std::string previous_sql_mode; std::string sql_mode = _owner->work_parser_context()->sqlMode(); if (!script.first.empty()) { previous_sql_mode = sql_mode; sql_mode = script.first; _owner->work_parser_context()->updateSqlMode(script.first); } size_t error_count = services->parseRoutine(_owner->work_parser_context(), routine, script.second); if (!previous_sql_mode.empty()) _owner->work_parser_context()->updateSqlMode(previous_sql_mode); if (error_count > 0) { logWarning("Error parsing SQL code for %s.%s:\n%s\n", schema_name.c_str(), obj_name.c_str(), script.second.c_str()); std::vector<ParserErrorInfo> errors = _owner->work_parser_context()->errorsWithOffset(0); mforms::Utilities::show_error(_("Error parsing sql code for object"), errors[0].message, "OK"); return ""; } ExecuteRoutineWizard wizard(routine, sql_mode); wizard.center(); return wizard.run(); } //---------------------------------------------------------------------------------------------------------------------- db_SchemaRef SqlEditorTreeController::create_new_schema(db_CatalogRef owner) { db_SchemaRef object = grt::GRT::get()->create_object<db_Schema>(owner->schemata()->content_type_spec().object_class); object->owner(owner); object->name("new_schema"); owner->schemata().insert(object); owner->defaultSchema(object); return object; } //---------------------------------------------------------------------------------------------------------------------- db_TableRef SqlEditorTreeController::create_new_table(db_SchemaRef owner) { db_TableRef object = grt::GRT::get()->create_object<db_Table>(owner->tables()->content_type_spec().object_class); object->owner(owner); object->name("new_table"); owner->tables().insert(object); return object; } //---------------------------------------------------------------------------------------------------------------------- db_ViewRef SqlEditorTreeController::create_new_view(db_SchemaRef owner) { db_ViewRef object = grt::GRT::get()->create_object<db_View>(owner->views()->content_type_spec().object_class); object->owner(owner); object->name("new_view"); owner->views().insert(object); return object; } //---------------------------------------------------------------------------------------------------------------------- db_RoutineRef SqlEditorTreeController::create_new_routine(db_SchemaRef owner, wb::LiveSchemaTree::ObjectType type) { db_RoutineRef object = grt::GRT::get()->create_object<db_Routine>(owner->routines()->content_type_spec().object_class); object->owner(owner); if (type == wb::LiveSchemaTree::Procedure) { object->name("new_procedure"); object->routineType("procedure"); } else if (type == wb::LiveSchemaTree::Function) { object->name("new_function"); object->routineType("function"); } owner->routines().insert(object); return object; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::tree_create_object(wb::LiveSchemaTree::ObjectType type, const std::string &schema_name, const std::string &obj_name) { do_alter_live_object(type, schema_name, obj_name); } //---------------------------------------------------------------------------------------------------------------------- /** * Generates an alter script for the given db object using the specified online DDL options. * These are however only applied if the server version is >= 5.6. */ std::string SqlEditorTreeController::generate_alter_script(const db_mgmt_RdbmsRef &rdbms, db_DatabaseObjectRef db_object, std::string algorithm, std::string lock) { DbMySQLImpl *diffsql_module = grt::GRT::get()->find_native_module<DbMySQLImpl>("DbMySQL"); db_CatalogRef server_cat = db_CatalogRef::cast_from(db_object->customData().get("serverStateCatalog")); db_CatalogRef client_cat = db_CatalogRef::cast_from(db_object->customData().get("clientStateCatalog")); db_CatalogRef client_cat_copy = db_CatalogRef::cast_from(grt::copy_object(client_cat)); db_CatalogRef server_cat_copy = db_CatalogRef::cast_from(grt::copy_object(server_cat)); grt::DictRef diff_options(true); // diff_options.set("CaseSensitive",db_object->customData().get("CaseSensitive")); grt::DictRef db_settings = grt::DictRef::cast_from(db_object->customData().get("DBSettings")); if (_owner->rdbms_version().is_valid() && is_supported_mysql_version_at_least(_owner->rdbms_version(), 5, 6)) { db_settings.gset("AlterAlgorithm", algorithm != "DEFAULT" ? algorithm : ""); db_settings.gset("AlterLock", lock != "DEFAULT" ? lock : ""); } diff_options.set("DBSettings", db_settings); std::string alter_script = diffsql_module->makeAlterScriptForObject(server_cat_copy, client_cat_copy, db_object, diff_options); client_cat_copy->reset_references(); server_cat_copy->reset_references(); return alter_script; } //---------------------------------------------------------------------------------------------------------------------- std::string SqlEditorTreeController::get_object_ddl_script(wb::LiveSchemaTree::ObjectType type, const std::string &schema_name, const std::string &obj_name) { std::string delimiter = bec::GRTManager::get()->get_app_option_string("SqlDelimiter", "$$"); std::string ddl_script = "delimiter " + delimiter + "\n\n"; // Triggers are fetched prior to table ddl, but should appear after table created. std::string additional_ddls; try { sql::Dbc_connection_handler::Ref conn; std::string query; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); // Can't use getSchemaObjects() because it silently ignores errors. switch (type) { case wb::LiveSchemaTree::Schema: query = base::sqlstring("SHOW CREATE SCHEMA !", 0) << obj_name; break; case wb::LiveSchemaTree::Table: { // triggers std::vector<std::string> triggers; { std::string trigger_query = base::sqlstring("SHOW TRIGGERS FROM ! WHERE ! = ?", 0) << schema_name << "Table" << obj_name; std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(trigger_query)); if (rs.get()) { while (rs->next()) triggers.push_back(rs->getString(1)); } } for (size_t index = 0; index < triggers.size(); index++) { std::string trigger_query = base::sqlstring("SHOW CREATE TRIGGER !.!", 0) << schema_name << triggers[index]; std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(trigger_query)); if (rs.get() && rs->next()) { std::string trigger_ddl = (rs->getString(3)); additional_ddls += trigger_ddl; additional_ddls += delimiter + "\n\n"; } } } query = base::sqlstring("SHOW CREATE TABLE !.!", 0) << schema_name << obj_name; break; case wb::LiveSchemaTree::View: query = base::sqlstring("SHOW CREATE VIEW !.!", 0) << schema_name << obj_name; break; case wb::LiveSchemaTree::Procedure: query = base::sqlstring("SHOW CREATE PROCEDURE !.!", 0) << schema_name << obj_name; break; case wb::LiveSchemaTree::Function: query = base::sqlstring("SHOW CREATE FUNCTION !.!", 0) << schema_name << obj_name; break; default: break; } std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(query)); // Note: show create procedure includes the sql mode in the result before the actual DDL. if (rs.get() && rs->next()) { if (type == wb::LiveSchemaTree::Function || type == wb::LiveSchemaTree::Procedure) ddl_script += rs->getString(3) + delimiter + "\n\n"; else ddl_script += rs->getString(2) + delimiter + "\n\n"; } ddl_script += additional_ddls; } catch (const sql::SQLException &e) { // Error 1356 comes up when any of the referenced tables in a view are invalid (e.g. dropped) // or the definer/invoker has no rights to access it. // Solve this by using the I_S. if (type == wb::LiveSchemaTree::View && e.getErrorCode() == 1356) { sql::Dbc_connection_handler::Ref conn; std::string query, view; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); query = base::sqlstring( "SELECT DEFINER, SECURITY_TYPE, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = ? " "AND TABLE_NAME = ?", 0) << schema_name << obj_name; std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(query)); if (rs.get() && rs->next()) { std::string view, definer; std::vector<std::string> definer_tokens = base::split(rs->getString(1), "@", 2); view = base::sqlstring("!.!", 0) << schema_name << obj_name; definer = base::sqlstring("!@!", 0) << definer_tokens[0] << definer_tokens[1]; ddl_script += "CREATE ALGORITHM=UNDEFINED DEFINER=" + definer; ddl_script += " SQL SECURITY " + rs->getString(2); ddl_script += " VIEW " + view + " AS "; ddl_script += rs->getString(3) + delimiter + "\n\n"; } } else { ddl_script.clear(); std::string err = e.what(); logError("Error getting SQL definition for %s.%s: %s\n", schema_name.c_str(), obj_name.c_str(), e.what()); if (bec::GRTManager::get()->in_main_thread()) mforms::Utilities::show_error("Error getting DDL for object", e.what(), "OK", "", ""); else bec::GRTManager::get()->run_once_when_idle( std::bind(&mforms::Utilities::show_error, "Error getting DDL for object", err, "OK", "", "")); } } return ddl_script; } //---------------------------------------------------------------------------------------------------------------------- /** * Retrieves the original DDL text that was used to create the object. * Returns a tuple of <sql_mode, script>. The sql mode is what was used to create the object, * if it is a routine. Otherwise this value is empty. */ std::pair<std::string, std::string> SqlEditorTreeController::get_object_create_script( wb::LiveSchemaTree::ObjectType type, const std::string &schema_name, const std::string &obj_name) { std::pair<std::string, std::string> result; try { sql::Dbc_connection_handler::Ref conn; std::string query; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); // cant use getSchemaObjects() because it silently ignores errors switch (type) { case wb::LiveSchemaTree::Schema: query = base::sqlstring("SHOW CREATE SCHEMA !", 0) << obj_name; break; case wb::LiveSchemaTree::Table: query = base::sqlstring("SHOW CREATE TABLE !.!", 0) << schema_name << obj_name; break; case wb::LiveSchemaTree::View: query = base::sqlstring("SHOW CREATE VIEW !.!", 0) << schema_name << obj_name; break; case wb::LiveSchemaTree::Procedure: query = base::sqlstring("SHOW CREATE PROCEDURE !.!", 0) << schema_name << obj_name; break; case wb::LiveSchemaTree::Function: query = base::sqlstring("SHOW CREATE FUNCTION !.!", 0) << schema_name << obj_name; break; default: break; } std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(query)); if (rs.get() && rs->next()) { if (type == wb::LiveSchemaTree::Function || type == wb::LiveSchemaTree::Procedure) { result.first = rs->getString(2); result.second = rs->getString(3); } else result.second = rs->getString(2); } } catch (const sql::SQLException &e) { if (type == wb::LiveSchemaTree::View && e.getErrorCode() == 1356) { // Error for not being allowed to run SHOW CREATE VIEW. Use I_S instead to get the code. sql::Dbc_connection_handler::Ref conn; std::string query, view; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); query = base::sqlstring( "SELECT DEFINER, SECURITY_TYPE, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = ? " "AND TABLE_NAME = ?", 0) << schema_name << obj_name; std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(query)); if (rs.get() && rs->next()) { std::string view, definer; std::vector<std::string> definer_tokens = base::split(rs->getString(1), "@", 2); view = base::sqlstring("!.!", 0) << schema_name << obj_name; definer = base::sqlstring("!@!", 0) << definer_tokens[0] << definer_tokens[1]; result.second = "CREATE ALGORITHM = UNDEFINED DEFINER = " + definer; result.second += " SQL SECURITY " + rs->getString(2); result.second += " VIEW " + view + " AS\n"; result.second += rs->getString(3); } } else { logError("Error getting SQL definition for %s.%s: %s\n", schema_name.c_str(), obj_name.c_str(), e.what()); mforms::Utilities::show_error("Error getting DDL for object", e.what(), "OK"); } } return result; } //---------------------------------------------------------------------------------------------------------------------- /** * Returns a list of trigger create scripts for the given table. */ std::vector<std::string> SqlEditorTreeController::get_trigger_sql_for_table(const std::string &schema_name, const std::string &table_name) { std::vector<std::string> result; try { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); std::vector<std::string> triggers; { std::string trigger_query = base::sqlstring("SHOW TRIGGERS FROM ! WHERE ! = ?", 0) << schema_name << "Table" << table_name; std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(trigger_query)); if (rs.get()) { while (rs->next()) triggers.push_back(rs->getString(1)); } } for (size_t index = 0; index < triggers.size(); index++) { std::string trigger_query = base::sqlstring("SHOW CREATE TRIGGER !.!", 0) << schema_name << triggers[index]; std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement()); std::unique_ptr<sql::ResultSet> rs(stmt->executeQuery(trigger_query)); if (rs.get() && rs->next()) result.push_back(rs->getString(3)); } } catch (const sql::SQLException &e) { logError("Error getting SQL definition for %s.%s: %s\n", schema_name.c_str(), table_name.c_str(), e.what()); mforms::Utilities::show_error("Error getting DDL for object", e.what(), "OK"); } return result; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::refresh_live_object_in_editor(bec::DBObjectEditorBE *obj_editor, bool using_old_name) { db_DatabaseObjectRef db_object = obj_editor->get_dbobject(); db_mysql_CatalogRef client_state_catalog = db_mysql_CatalogRef::cast_from(db_object->customData().get("clientStateCatalog")); std::string obj_name = using_old_name ? db_object->oldName() : db_object->name(); // don't refresh new objects that where not applied yet if (obj_name.empty()) return; obj_editor->freeze_refresh_on_object_change(); if (obj_name != *db_object->name()) db_object->name(obj_name); obj_editor->thaw_refresh_on_object_change(true); std::string schema_name = db_SchemaRef::can_wrap(db_object) ? std::string() : *db_object->owner()->name(); db_SchemaRef schema; if (!schema_name.empty()) schema = db_SchemaRef::cast_from(db_object->owner()); wb::LiveSchemaTree::ObjectType db_object_type = wb::LiveSchemaTree::Any; if (db_SchemaRef::can_wrap(db_object)) { db_object_type = wb::LiveSchemaTree::Schema; } else { if (db_TableRef::can_wrap(db_object)) { db_object_type = wb::LiveSchemaTree::Table; // reset selection of fkeys/indices to avoid warnings bec::TableEditorBE *table_editor = dynamic_cast<bec::TableEditorBE *>(obj_editor); table_editor->get_fks()->select_fk(NodeId()); table_editor->get_indexes()->select_index(NodeId()); } else if (db_ViewRef::can_wrap(db_object)) { db_object_type = wb::LiveSchemaTree::View; } else if (db_RoutineRef::can_wrap(db_object)) { db_RoutineRef db_routine = db_RoutineRef::cast_from(db_object); std::string obj_type = db_routine->routineType(); if (obj_type == "function") db_object_type = wb::LiveSchemaTree::Function; else db_object_type = wb::LiveSchemaTree::Procedure; } } obj_editor->freeze_refresh_on_object_change(); client_state_catalog->schemata().remove_all(); // Clean up. We only want the single object in it, which we edit. // reparse object's DDL std::string ddl_script; std::string sql_mode; { ddl_script = get_object_ddl_script(db_object_type, schema_name, obj_name); if (!ddl_script.empty()) { if (db_object_type == wb::LiveSchemaTree::View && bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ReformatViewDDL", 0)) { try { grt::Module *module = grt::GRT::get()->get_module("SQLIDEUtils"); grt::BaseListRef args(true); args.ginsert(grt::StringRef(ddl_script)); ddl_script = grt::StringRef::cast_from(module->call_function("reformatSQLStatement", args)); } catch (std::exception &exc) { logWarning("Error reformatting view code: %s\n", exc.what()); } } try { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); if (conn) _owner->get_session_variable(conn->ref.get(), "sql_mode", sql_mode); } CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Get 'sql_mode' session variable")); parse_ddl_into_catalog(client_state_catalog, strfmt("`%s`.`%s`", schema_name.c_str(), obj_name.c_str()), ddl_script, sql_mode, schema_name); } } // Settings dict here only to copy it to the new db object. grt::DictRef dbSettings = grt::DictRef::cast_from(db_object->customData().get("DBSettings")); switch (db_object_type) { case wb::LiveSchemaTree::Table: if ((client_state_catalog->schemata()->count() > 0) && (client_state_catalog->schemata()[0]->tables()->count() > 0)) db_object = client_state_catalog->schemata()[0]->tables()[0]; break; case wb::LiveSchemaTree::View: if ((client_state_catalog->schemata()->count() > 0) && (client_state_catalog->schemata()[0]->views()->count() > 0)) db_object = client_state_catalog->schemata()[0]->views()[0]; break; case wb::LiveSchemaTree::Procedure: case wb::LiveSchemaTree::Function: if ((client_state_catalog->schemata()->count() > 0) && (client_state_catalog->schemata()[0]->routines()->count() > 0)) db_object = client_state_catalog->schemata()[0]->routines()[0]; break; default: // wb::LiveSchemaTree::Schema, there are more cases, but we only use those listed here. if (client_state_catalog->schemata()->count() > 0) db_object = client_state_catalog->schemata()[0]; break; } // The current name becomes now also the old name, so that we create alter scripts from the current name, // not the one from before this change. db_object->oldName(obj_name); { db_CatalogRef server_state_catalog(db_CatalogRef::cast_from(grt::copy_object(client_state_catalog))); db_object->customData().set("serverStateCatalog", server_state_catalog); db_object->customData().set("clientStateCatalog", client_state_catalog); } db_object->customData().set("originalObjectDDL", grt::StringRef(ddl_script)); db_object->customData().set("sqlMode", grt::StringRef(sql_mode)); db_object->customData().set("DBSettings", dbSettings); db_object->customData().set("liveRdbms", _owner->rdbms()); db_object->customData().set("ownerSqlEditor", _owner->wbsql()->get_grt_editor_object(_owner)); obj_editor->thaw_refresh_on_object_change(); // Update the editor's grt object (the one it is managing), as this has now been // recreated. Will cause a UI refresh as well. If there was a parse error (which can only be // an internal error, since we start freshly) stay with the old object. obj_editor->set_object(db_object); // Enable refresh of sql editor contents. MySQLEditor::Ref active_sql_editor; if (obj_editor->has_editor()) active_sql_editor = obj_editor->get_sql_editor(); if (active_sql_editor) active_sql_editor->set_refresh_enabled(true); } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorTreeController::parse_ddl_into_catalog(db_mysql_CatalogRef catalog, const std::string &objectDescription, const std::string &sql, std::string sqlMode, const std::string &schema) { std::string currentSqlMode = _owner->work_parser_context()->sqlMode(); grt::DictRef options(true); options.set("reuse_existing_objects", grt::IntegerRef(1)); options.set("schema", grt::StringRef(schema)); if (!sqlMode.empty()) _owner->work_parser_context()->updateSqlMode(sqlMode); parsers::MySQLParserServices::Ref services = parsers::MySQLParserServices::get(); size_t errorCount = services->parseSQLIntoCatalog(_owner->work_parser_context(), catalog, sql, options); bool haveErrors = false; if (options.has_key("sql_mode") && (errorCount > 0)) { if (sqlMode.find("ANSI_QUOTES") != std::string::npos) sqlMode = base::replaceString(sqlMode, "ANSI_QUOTES", ""); else sqlMode += ", ANSI_QUOTES"; _owner->work_parser_context()->updateSqlMode(sqlMode); errorCount = services->parseSQLIntoCatalog(_owner->work_parser_context(), catalog, sql, options); _owner->work_parser_context()->updateSqlMode(currentSqlMode); if (errorCount == 0) { // Error(s) solved by new sql mode -> inconsistency. if (mforms::Utilities::show_warning( strfmt(_("Error Parsing DDL for %s"), objectDescription.c_str()), _("The object's DDL retrieved from the server is inconsistent with respect to the SQL_MODE variable " "set for the connection. In particular the current state of the ANSI_QUOTES flag contradicts " "the value set when the object had been created. This may lead to errors when trying to " "apply changes. As a workaround you may want to temporarily change the SQL_MODE variable " "to its previous value.\nDo you want to view the DDL or cancel processing it?"), _("View DDL"), _("Cancel")) == mforms::ResultOk) { _owner->new_sql_scratch_area(); insert_text_to_active_editor(sql); } return false; } else haveErrors = true; } else haveErrors = errorCount > 0; _owner->work_parser_context()->updateSqlMode(currentSqlMode); if (haveErrors) { if (mforms::Utilities::show_error(strfmt(_("Error Parsing DDL for %s"), objectDescription.c_str()), _("There was an error while parsing the DDL retrieved from the server.\n" "Do you want to view the DDL or cancel processing it?"), _("View DDL"), _("Cancel")) == mforms::ResultOk) { _owner->new_sql_scratch_area(); insert_text_to_active_editor(sql); } return false; } return true; } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorTreeController::apply_changes_to_object(bec::DBObjectEditorBE *obj_editor, bool dry_run) { std::string log_descr; RowId log_id = -1; if (!dry_run) { log_descr = strfmt(_("Apply changes to %s"), obj_editor->get_name().c_str()); log_id = _owner->add_log_message(DbSqlEditorLog::BusyMsg, "Applying object changes ...", log_descr, ""); } try { if (!dry_run && obj_editor->has_editor() && obj_editor->get_sql_editor()->has_sql_errors()) { // We won't be able to apply with errors. Inform the user and return false. mforms::Utilities::show_error(_("Apply Changes to Object"), _("The object's DDL statement contains syntax errors.\n" "You cannot modify this object until you fix the errors."), _("OK")); return false; } db_DatabaseObjectRef db_object = obj_editor->get_dbobject(); if (!dry_run) { ValueRef hasErrors = db_object->customData().get("triggerInvalid"); if (hasErrors.is_valid() && (IntegerRef::cast_from(hasErrors) != 0)) { int res = mforms::Utilities::show_warning(_("Apply Changes to Object"), _("The tables's trigger SQL code contains errors.\n" "This will lead to invalid sql generated.\n" "Are you sure you want to apply the DDL statement as is?"), _("Yes"), _("No")); if (res != mforms::ResultOk) { _owner->set_log_message(log_id, DbSqlEditorLog::ErrorMsg, "Cancelled", log_descr, ""); return false; } } // check for name conflicts // if the object is new or its name was changed std::string obj_name = db_object->name(); std::string obj_old_name = db_object->oldName(); if (_owner->lower_case_table_names() != 0) // if 1 or 2, treat everything as case insensitive { obj_name = tolower(obj_name); obj_old_name = tolower(obj_old_name); if (_owner->lower_case_table_names() == 1 && obj_name != *db_object->name() && (db_TableRef::can_wrap(db_object) || db_ViewRef::can_wrap(db_object) || db_SchemaRef::can_wrap(db_object))) // server will force everything to be lowercase { mforms::Utilities::show_message_and_remember( "Apply Changes to Object", base::strfmt("The server is configured with lower_case_table_names=1, which only allows lowercase " "characters in schema and table names.\nThe object will be created as `%s`.", obj_name.c_str()), "OK", "", "", "sqlide:lower_case_table_names", ""); db_object->name(obj_name); } } // now here if (obj_name != obj_old_name) { std::list<std::string> obj_types; std::list<std::string> validation_queries; std::string schema_name = db_SchemaRef::can_wrap(db_object) ? std::string() : *db_object->owner()->name(); { if (db_SchemaRef::can_wrap(db_object)) { obj_types.push_back("schema"); validation_queries.push_back(sqlstring("SHOW DATABASES LIKE ?", 0) << db_object->name()); } else if (db_TableRef::can_wrap(db_object) || db_ViewRef::can_wrap(db_object)) { obj_types.push_back("table"); obj_types.push_back("view"); std::string tables_format = base::strfmt( "SHOW FULL TABLES FROM ! WHERE `Tables_in_%s` = ? AND Table_type != 'VIEW'", schema_name.c_str()); std::string views_format = base::strfmt( "SHOW FULL TABLES FROM ! WHERE `Tables_in_%s` = ? AND Table_type = 'VIEW'", schema_name.c_str()); validation_queries.push_back(sqlstring(tables_format.c_str(), 0) << schema_name << db_object->name()); validation_queries.push_back(sqlstring(views_format.c_str(), 0) << schema_name << db_object->name()); } else if (db_RoutineRef::can_wrap(db_object)) { db_RoutineRef db_routine = db_RoutineRef::cast_from(db_object); std::string type = db_routine->routineType(); if (type == "function") validation_queries.push_back(sqlstring("SHOW FUNCTION STATUS WHERE Db=? AND NAME = ?", 0) << schema_name << db_routine->name()); else validation_queries.push_back(sqlstring("SHOW PROCEDURE STATUS WHERE Db=? AND NAME = ?", 0) << schema_name << db_routine->name()); obj_types.push_back(type); } } sql::Dbc_connection_handler::Ref conn; RecMutexLock lock(_owner->ensure_valid_aux_connection(conn)); for (const std::string &obj_type : obj_types) { std::string query = validation_queries.front(); validation_queries.pop_front(); std::unique_ptr<sql::ResultSet> rs(conn->ref->createStatement()->executeQuery(query)); if (rs->next()) { mforms::Utilities::show_error(_("Apply Changes to Object"), strfmt(_("Selected name conflicts with existing %s `%s`."), obj_type.c_str(), (*db_object->name()).c_str()), _("OK")); _owner->set_log_message(log_id, DbSqlEditorLog::ErrorMsg, strfmt(_("Selected name conflicts with existing %s `%s`."), obj_type.c_str(), (*db_object->name()).c_str()), log_descr, ""); return false; } } } } // Generate the initial version of the alter script. This might be altered in the wizard // depending on the online DDL options. std::string algorithm; wb::WBContextUI::get()->get_wb_options_value("", "DbSqlEditor:OnlineDDLAlgorithm", algorithm); std::string lock; wb::WBContextUI::get()->get_wb_options_value("", "DbSqlEditor:OnlineDDLLock", lock); std::string alter_script = generate_alter_script(_owner->rdbms(), db_object, algorithm, lock); // The alter_script may contain a dummy USE statement. if (alter_script.empty() || (alter_script.find("CREATE") == std::string::npos && alter_script.find("ALTER") == std::string::npos && alter_script.find("DROP") == std::string::npos)) { if (!dry_run && !_owner->on_sql_script_run_error.empty()) _owner->on_sql_script_run_error(log_id, _("No changes to object were detected."), ""); if (!dry_run) { _owner->set_log_message(log_id, DbSqlEditorLog::NoteMsg, _("No changes detected"), log_descr, ""); // Because of message throttling the previous log message doesn't cause a refresh of the UI // (it comes quicker than the throttling timeout). So we explicitly do a refresh. _owner->refresh_log_messages(true); } return false; // no changes detected } if (dry_run) return true; // some changes were detected return _owner->run_live_object_alteration_wizard(alter_script, obj_editor, log_id, log_descr); } catch (const std::exception &e) { if (!_owner->on_sql_script_run_error.empty()) _owner->on_sql_script_run_error(log_id, e.what(), ""); _owner->set_log_message(log_id, DbSqlEditorLog::ErrorMsg, e.what(), log_descr, ""); logError("Exception applying changes to live object: %s\n", e.what()); } return true; // some changes were detected and applied } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::create_live_table_stubs(bec::DBObjectEditorBE *table_editor) { db_DatabaseObjectRef db_object = table_editor->get_dbobject(); if (db_object->customData().has_key("isLiveTableListLoaded")) return; try { sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); db_CatalogRef catalog = table_editor->get_catalog(); grt::ListRef<db_Schema> schemata = catalog->schemata(); db_SchemaRef schema; grt::ListRef<db_Table> tables; db_TableRef table; std::string database_package = *_owner->rdbms()->databaseObjectPackage(); std::string schema_typename = database_package + ".Schema"; std::string table_typename = database_package + ".Table"; std::string prev_schema_name; std::shared_ptr<sql::ResultSet> rs; { std::string schema_name = db_SchemaRef::cast_from(db_object->owner())->name(); std::list<sql::SQLString> table_types; table_types.push_back("TABLE"); rs.reset(conn->ref->getMetaData()->getTables("", schema_name, "%", table_types)); } while (rs->next()) { std::string schema_name = rs->getString(2); std::string table_name = rs->getString(3); if (prev_schema_name != schema_name) { schema = find_named_object_in_list(schemata, schema_name); if (!schema.is_valid()) { schema = grt::GRT::get()->create_object<db_Schema>(schema_typename); schema->owner(catalog); schema->name(schema_name); schema->oldName(schema_name); schema->modelOnly(1); schemata.insert(schema); } tables = schema->tables(); prev_schema_name = schema_name; } table = find_named_object_in_list(tables, table_name); if (!table.is_valid()) { table = grt::GRT::get()->create_object<db_Table>(table_typename); table->owner(schema); table->name(table_name); table->oldName(table_name); table->modelOnly(1); table->isStub(1); tables.insert(table); } } db_object->customData().set("isLiveTableListLoaded", IntegerRef(1)); } CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Create live table stub")); } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorTreeController::expand_live_table_stub(bec::DBObjectEditorBE *table_editor, const std::string &schema_name, const std::string &obj_name) { db_CatalogRef catalog = table_editor->get_catalog(); db_TableRef table; db_SchemaRef schema = find_named_object_in_list(catalog->schemata(), schema_name); if (schema.is_valid()) { table = find_named_object_in_list(schema->tables(), obj_name); if (table.is_valid() && table->customData().has_key("isStubExpanded")) return true; // stub table has already been expanded } std::string ddl_script = get_object_ddl_script(wb::LiveSchemaTree::Table, schema_name, obj_name); if (ddl_script.empty()) return false; { SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms(_owner->rdbms()); Sql_parser::Ref sql_parser = sql_facade->sqlParser(); sql_parser->messages_enabled(false); grt::DictRef options(true); { std::string sql_mode; sql::Dbc_connection_handler::Ref conn; RecMutexLock aux_dbc_conn_mutex(_owner->ensure_valid_aux_connection(conn)); if (conn && _owner->get_session_variable(conn->ref.get(), "sql_mode", sql_mode)) options.gset("sql_mode", sql_mode); else logWarning("Unable to get sql_mode for connection\n"); } db_SchemaRef old_default_schema = catalog->defaultSchema(); if (!schema.is_valid()) { // target schema doesn't exist yet, create a stub for it schema = db_mysql_SchemaRef(grt::Initialized); schema->owner(catalog); schema->name(schema_name); schema->comment("stub"); catalog->schemata().insert(schema); } catalog->defaultSchema(schema); sql_parser->parse_sql_script(catalog, ddl_script, options); catalog->defaultSchema(old_default_schema); } // find parsed table if (!schema.is_valid()) schema = find_named_object_in_list(catalog->schemata(), schema_name); if (!table.is_valid() && schema.is_valid()) table = find_named_object_in_list(schema->tables(), obj_name); if (table.is_valid() && table != table_editor->get_dbobject()) { table->modelOnly(0); table->isStub(1); table->customData().set("isStubExpanded", IntegerRef(1)); } return table.is_valid(); } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorTreeController::activate_live_object(GrtObjectRef object) { std::string obj_name = *object->name(); std::string owner_name = *object->owner()->name(); if (db_SchemaRef::can_wrap(object)) schema_object_activated("activate", LiveSchemaTree::Schema, "", obj_name); else if (db_TableRef::can_wrap(object)) schema_object_activated("activate", LiveSchemaTree::Table, owner_name, obj_name); else if (db_ViewRef::can_wrap(object)) schema_object_activated("activate", LiveSchemaTree::View, owner_name, obj_name); else if (db_RoutineRef::can_wrap(object)) { db_RoutineRef routine = db_RoutineRef::cast_from(object); std::string type = routine->routineType(); if (type == "function") schema_object_activated("activate", LiveSchemaTree::Function, owner_name, obj_name); else schema_object_activated("activate", LiveSchemaTree::Procedure, owner_name, obj_name); } else return false; return true; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::on_active_schema_change(const std::string &schema) { _base_schema_tree.set_active_schema(schema); _filtered_schema_tree.set_active_schema(schema); if (_schema_side_bar != NULL) bec::GRTManager::get()->run_once_when_idle( this, std::bind(&mforms::View::set_needs_repaint, _schema_side_bar->get_schema_tree())); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::mark_busy(bool busy) { if (_schema_side_bar != NULL) _schema_side_bar->mark_section_busy("", busy); } //---------------------------------------------------------------------------------------------------------------------- grt::StringRef SqlEditorTreeController::do_refresh_schema_tree_safe(SqlEditorForm::Ptr self_ptr) { RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR(SqlEditorForm, self_ptr, self, grt::StringRef("")) if (_is_refreshing_schema_tree) return grt::StringRef(""); _is_refreshing_schema_tree = true; StringListPtr schema_list(new std::list<std::string>()); std::vector<std::string> schemaList = fetch_schema_list(); _owner->schemaListRefreshed(schemaList); schema_list->assign(schemaList.begin(), schemaList.end()); bec::GRTManager::get()->run_once_when_idle(this, std::bind(&LiveSchemaTree::update_schemata, _schema_tree, schema_list)); bec::GRTManager::get()->run_once_when_idle(this, std::bind(&SqlEditorForm::schema_tree_did_populate, _owner)); _is_refreshing_schema_tree = false; return grt::StringRef(""); } //---------------------------------------------------------------------------------------------------------------------- wb::LiveSchemaTree *SqlEditorTreeController::get_schema_tree() { return _schema_tree; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::handle_grt_notification(const std::string &name, grt::ObjectRef sender, grt::DictRef info) { if (name == "GRNDBObjectEditorCreated") { grt::ValueRef object = info.get("object"); bec::DBObjectEditorBE *editor = dynamic_cast<bec::DBObjectEditorBE *>(bec::UIForm::form_with_id(info.get_string("form"))); if (editor && db_DatabaseObjectRef::can_wrap(object)) { db_DatabaseObjectRef obj(db_DatabaseObjectRef::cast_from(object)); if (obj->customData().get("ownerSqlEditor") == _owner->wbsql()->get_grt_editor_object(_owner)) { editor->on_apply_changes_to_live_object = std::bind(&SqlEditorTreeController::apply_changes_to_object, this, std::placeholders::_1, std::placeholders::_2); editor->on_refresh_live_object = std::bind(&SqlEditorTreeController::refresh_live_object_in_editor, this, std::placeholders::_1, true); editor->on_create_live_table_stubs = std::bind(&SqlEditorTreeController::create_live_table_stubs, this, std::placeholders::_1); editor->on_expand_live_table_stub = std::bind(&SqlEditorTreeController::expand_live_table_stub, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); } } } else if (name == "GRNSQLEditorReconnected") { if (sender == _owner->wbsql()->get_grt_editor_object(_owner)) { _session_info->set_markup_text(_owner->get_connection_info()); tree_refresh(); } } else if (name == "GNColorsChanged") { updateColors(); } } //---------------------------------------------------------------------------------------------------------------------- const std::string objectInfoStyles = "<style>" "body { font-family: '" DEFAULT_FONT_FAMILY "'; color: »color1«; }" "tr.heading { font-weight: 700; }" "td.name { color: »color2«; }" "td.value { font-weight: 700; color: »color2«;}" "table { width: 100%; white-space: nowrap; }" "</style>" ; void SqlEditorTreeController::updateColors() { _schema_side_bar->set_selection_color(base::HighlightColor); _side_splitter->set_back_color(base::Color::getApplicationColorAsString(AppColorMainBackground, false)); #ifdef _MSC_VER _object_info->set_back_color(base::Color::getApplicationColorAsString(base::AppColorPanelContentArea, false)); _session_info->set_back_color(base::Color::getApplicationColorAsString(base::AppColorPanelContentArea, false)); #elif __APPLE__ _object_info->set_back_color(base::Color::getSystemColor(base::WindowBackgroundColor).to_html()); _session_info->set_back_color(base::Color::getSystemColor(base::WindowBackgroundColor).to_html()); _schema_side_bar->set_back_color(base::Color::getSystemColor(base::WindowBackgroundColor).to_html()); #else _object_info->set_back_color("#ebebeb"); _session_info->set_back_color("#ebebeb"); #endif schema_row_selected(); // Refresh object HTML. _object_info->set_needs_repaint(); #ifdef __linux__ _session_info->set_markup_text(_owner->get_connection_info()); #else Color textColor = base::Color::getSystemColor(base::LabelColor); std::string html = base::replaceString(objectInfoStyles, "»color1«", textColor.to_html()); textColor.alpha = 0.75; base::replaceStringInplace(html, "»color2«", textColor.to_html()); html = "<html><head>" + html + "</head>"; html += _owner->get_connection_info() + "</html>"; _session_info->set_markup_text(html); #endif _session_info->set_needs_repaint(); } //---------------------------------------------------------------------------------------------------------------------- int SqlEditorTreeController::insert_text_to_active_editor(const std::string &str) { SqlEditorPanel *editor(_owner->active_sql_editor_panel()); if (editor) { editor->editor_be()->insert_text(str); editor->editor_be()->focus(); } return 0; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorTreeController::context_menu_will_show(mforms::MenuItem *parent_item) { if (!parent_item) { grt::DictRef info(true); db_query_EditorRef sender(_owner->wbsql()->get_grt_editor_object(_owner)); grt::ListRef<db_query_LiveDBObject> selection( grt::ListRef<db_query_LiveDBObject>::cast_from(_schema_tree->get_selected_objects())); info.set("menu", mforms_to_grt(_schema_side_bar->get_context_menu())); info.gset("menu-plugins-index", _schema_side_bar->get_context_menu()->get_item_index( _schema_side_bar->get_context_menu()->find_item("refresh")) - 2); info.set("selection", selection); grt::GRTNotificationCenter::get()->send_grt("GRNLiveDBObjectMenuWillShow", sender, info); } } //----------------------------------------------------------------------------------------------------------------------