backend/wbprivate/sqlide/wb_sql_editor_form.cpp (2,415 lines of code) (raw):
/*
* Copyright (c) 2007, 2022, Oracle and/or its affiliates.
*
* 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 "wb_sql_editor_form.h"
#include "grtdb/db_helpers.h"
#include "grtsqlparser/sql_facade.h"
#include "grtdb/editor_dbobject.h"
#include "grtdb/db_object_helpers.h"
#include "grtui/confirm_save_dialog.h"
#include "sqlide/recordset_be.h"
#include "sqlide/recordset_cdbc_storage.h"
#include "sqlide/wb_sql_editor_snippets.h"
#include "sqlide/wb_sql_editor_panel.h"
#include "sqlide/wb_sql_editor_result_panel.h"
#include "sqlide/wb_sql_editor_tree_controller.h"
#include "sqlide/sql_script_run_wizard.h"
#include "sqlide/column_width_cache.h"
#include "objimpl/db.query/db_query_Resultset.h"
#include "objimpl/wrapper/mforms_ObjectReference_impl.h"
#include "base/string_utilities.h"
#include "base/notifications.h"
#include "base/sqlstring.h"
#include "base/file_functions.h"
#include "base/file_utilities.h"
#include "base/log.h"
#include "base/boost_smart_ptr_helpers.h"
#include "base/util_functions.h"
#include "base/scope_exit_trigger.h"
#include "base/threading.h"
#include "workbench/wb_command_ui.h"
#include "workbench/wb_context_names.h"
#include "workbench/SSHSessionWrapper.h"
#include "workbench/wb_context_ui.h"
#include "workbench/wb_context.h"
#include <mysql_connection.h>
#include <boost/signals2/connection.hpp>
#include "query_side_palette.h"
#include "mforms/menubar.h"
#include "mforms/hypertext.h" // needed for d-tor
#include "mforms/tabview.h" // needed for d-tor
#include "mforms/splitter.h" // needed for d-tor
#include "mforms/toolbar.h"
#include "mforms/code_editor.h"
#include "grtsqlparser/mysql_parser_services.h"
#include "wb_tunnel.h"
#include <math.h>
#include <mutex>
#include <thread>
using namespace bec;
using namespace grt;
using namespace wb;
using namespace base;
using namespace parsers;
using boost::signals2::scoped_connection;
using namespace std::string_literals;
DEFAULT_LOG_DOMAIN("SQL Editor Form")
static const char *SQL_EXCEPTION_MSG_FORMAT = _("Error Code: %i\n%s");
static const char *EXCEPTION_MSG_FORMAT = _("Error: %s");
#define CATCH_SQL_EXCEPTION_AND_DISPATCH(statement, log_message_index, duration) \
catch (sql::SQLException & e) { \
set_log_message(log_message_index, DbSqlEditorLog::ErrorMsg, \
strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), statement, duration); \
}
#define CATCH_EXCEPTION_AND_DISPATCH(statement) \
catch (std::exception & e) { \
add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(EXCEPTION_MSG_FORMAT, e.what()), statement, ""); \
}
#define CATCH_ANY_EXCEPTION_AND_DISPATCH(statement) \
catch (sql::SQLException & e) { \
add_log_message(DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()), statement, \
""); \
} \
CATCH_EXCEPTION_AND_DISPATCH(statement)
#define CATCH_ANY_EXCEPTION_AND_DISPATCH_TO_DEFAULT_LOG(statement) \
catch (sql::SQLException & e) { \
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); \
}
db_mgmt_ServerInstanceRef getServerInstance(const db_mgmt_ConnectionRef &connection) {
grt::ValueRef ret = grt::GRT::get()->get("/wb/rdbmsMgmt/storedInstances");
if (grt::ListRef<db_mgmt_ServerInstance>::can_wrap(ret))
{
auto list = grt::ListRef<db_mgmt_ServerInstance>::cast_from(ret);
for (const auto &item: list) {
if (item->connection() == connection)
return item;
}
}
return db_mgmt_ServerInstanceRef();
}
//----------------------------------------------------------------------------------------------------------------------
class Timer {
public:
Timer(bool run_immediately) : _is_running(false), _start_timestamp(0), _duration(0) {
if (run_immediately)
run();
}
void reset() {
_is_running = false;
_start_timestamp = 0;
_duration = 0;
}
void run() {
if (_is_running)
return;
_is_running = true;
_start_timestamp = timestamp();
}
void stop() {
if (!_is_running)
return;
_is_running = false;
_duration += timestamp() - _start_timestamp;
}
double duration() {
return _is_running ? (_duration + timestamp() - _start_timestamp) : _duration;
}
std::string duration_formatted() {
double d = duration(), dd;
dd = d;
int zeroes = 1;
while (dd < 1.0 && dd > 0.0) {
zeroes++;
dd *= 10;
}
return strfmt(strfmt(_("%%.%if sec"), std::max(3, zeroes)).c_str(), d);
}
private:
bool _is_running;
double _start_timestamp;
double _duration;
};
//----------------------------------------------------------------------------------------------------------------------
struct SqlEditorForm::PrivateMutex {
std::mutex _symbolsMutex;
};
//----------------------------------------------------------------------------------------------------------------------
SqlEditorForm::Ref SqlEditorForm::create(wb::WBContextSQLIDE *wbsql, const db_mgmt_ConnectionRef &conn) {
SqlEditorForm::Ref instance(new SqlEditorForm(wbsql));
if (conn.is_valid())
instance->set_connection(conn);
return instance;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::set_tab_dock(mforms::DockingPoint *dp) {
_tabdock = dp;
grtobj()->dockingPoint(mforms_to_grt(dp));
scoped_connect(_tabdock->signal_view_switched(), std::bind(&SqlEditorForm::sql_editor_panel_switched, this));
scoped_connect(_tabdock->signal_view_undocked(),
std::bind(&SqlEditorForm::sql_editor_panel_closed, this, std::placeholders::_1));
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::report_connection_failure(const std::string &error, const db_mgmt_ConnectionRef &target) {
std::string message;
logError("SQL editor could not be connected: %s\n", error.c_str());
mforms::App::get()->set_status_text(_("Could not connect to target database."));
if (error.find("exceeded the 'max_user_connections' resource") != std::string::npos) {
mforms::Utilities::show_error(_("Could not Connect to Database Server"),
base::strfmt("%s\n\nMySQL Workbench requires at least 2 connections to the server, "
"one for management purposes and another for user queries.",
error.c_str()),
"OK");
return;
}
message =
"Your connection attempt failed for user '%user%' to the MySQL server at %server%:%port%:\n %error%\n"
"\n"
"Please:\n"
"1 Check that MySQL is running on address %server%\n"
"2 Check that MySQL is reachable on port %port% (note: 3306 is the default, but this can be changed)\n"
"3 Check the user %user% has rights to connect to %server% from your address (MySQL rights define what clients can "
"connect to the server and from which machines) \n"
"4 Make sure you are both providing a password if needed and using the correct password for %server% connecting "
"from the host address you're connecting from";
message = base::replaceString(message, "%user%", target->parameterValues().get_string("userName"));
message = base::replaceString(message, "%port%", target->parameterValues().get("port").toString());
message = base::replaceString(message, "%server%", target->parameterValues().get_string("hostName", "localhost"));
message = base::replaceString(message, "%error%", error);
logError("%s", (message + '\n').c_str());
mforms::Utilities::show_error(_("Cannot Connect to Database Server"), message, _("Close"));
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::report_connection_failure(const grt::server_denied &info, const db_mgmt_ConnectionRef &target) {
std::string message;
logError("Server is alive, but has login restrictions: %d, %s\n", info.errNo, info.what());
mforms::App::get()->set_status_text(_("Connection restricted"));
message = "Your connection attempt failed for user '";
message += target->parameterValues().get_string("userName");
message += "' from your host to server at "; //%server%:%port%\n";
message += target->parameterValues().get_string("hostName", "localhost");
message += ":";
message += target->parameterValues().get("port").toString() + "\n";
if (info.errNo == 3159)
message += "Only connections with enabled SSL support are accepted.\n";
else if (info.errNo == 3032)
message += "The server is in super-user mode and does not accept any other connection.\n";
message += "\nThe server response was:\n";
message += info.what();
mforms::Utilities::show_error(_("Cannot Connect to Database Server"), message, _("Close"));
}
//----------------------------------------------------------------------------------------------------------------------
SqlEditorForm::SqlEditorForm(wb::WBContextSQLIDE *wbsql)
: exec_sql_task(GrtThreadedTask::create()),
_history(DbSqlEditorHistory::create()),
_wbsql(wbsql),
_version(grt::Initialized),
_live_tree(SqlEditorTreeController::create(this)),
_aux_dbc_conn(new sql::Dbc_connection_handler()),
_usr_dbc_conn(new sql::Dbc_connection_handler()),
_pimplMutex (new PrivateMutex) {
_log = DbSqlEditorLog::create(this, 500);
NotificationCenter::get()->add_observer(this, "GNApplicationActivated");
NotificationCenter::get()->add_observer(this, "GNMainFormChanged");
NotificationCenter::get()->add_observer(this, "GNFormTitleDidChange");
NotificationCenter::get()->add_observer(this, "GNColorsChanged");
GRTNotificationCenter::get()->add_grt_observer(this, "GRNServerStateChanged");
exec_sql_task->desc("execute sql queries");
exec_sql_task->send_task_res_msg(false);
exec_sql_task->msg_cb(std::bind(&SqlEditorForm::add_log_message, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, ""));
_last_log_message_timestamp = timestamp();
long keep_alive_interval = bec::GRTManager::get()->get_app_option_int("DbSqlEditor:KeepAliveInterval", 600);
if (keep_alive_interval != 0) {
logDebug3("Creating KeepAliveInterval timer...\n");
_keep_alive_task_id = ThreadedTimer::add_task(
TimerTimeSpan, keep_alive_interval, false, std::bind(&SqlEditorForm::send_message_keep_alive_bool_wrapper, this));
}
_lower_case_table_names = 0;
_continueOnError = (bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ContinueOnError", 0) != 0);
// set initial autocommit mode value
_usr_dbc_conn->autocommit_mode = (bec::GRTManager::get()->get_app_option_int("DbSqlEditor:AutocommitMode", 1) != 0);
_databaseSymbols.addDependencies({ &_staticServerSymbols });
}
//----------------------------------------------------------------------------------------------------------------------
SqlEditorForm::~SqlEditorForm() {
if (_editorRefreshPending.connected())
_editorRefreshPending.disconnect();
if (_overviewRefreshPending.connected())
_overviewRefreshPending.disconnect();
// We need to remove it from cache, if not someone will be able to login without providing PW
if (_connection.is_valid())
mforms::Utilities::forget_cached_password(_connection->hostIdentifier(),
_connection->parameterValues().get_string("userName"));
delete _column_width_cache;
// debug: ensure that close() was called when the tab is closed
if (_toolbar != nullptr)
logFatal("SqlEditorForm::close() was not called\n");
NotificationCenter::get()->remove_observer(this);
GRTNotificationCenter::get()->remove_grt_observer(this);
delete _autosave_lock;
_autosave_lock = 0;
// Destructor can be called before the startup was finished.
// On Windows the side palette is a child of the palette host and hence gets freed when we
// free the host. On other platforms both are the same. In any case, double freeing it is
// not a good idea.
if (_side_palette_host != nullptr)
_side_palette_host->release();
delete _toolbar;
delete _menu;
reset_keep_alive_thread();
_sshConnection.release();
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::cancel_connect() {
_cancel_connect = true;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::check_server_problems() {
//_lower_case_table_names
std::string compile_os;
if (_usr_dbc_conn && get_session_variable(_usr_dbc_conn->ref.get(), "version_compile_os", compile_os)) {
if ((_lower_case_table_names == 0 && (base::hasPrefix(compile_os, "Win") || base::hasPrefix(compile_os, "osx"))) ||
(_lower_case_table_names == 2 && base::hasPrefix(compile_os, "Win")))
mforms::Utilities::show_message_and_remember(
_("Server Configuration Problems"),
"A server configuration problem was detected.\nThe server is in a system that does not properly support the "
"selected lower_case_table_names option value. Some problems may occur.\nPlease consult the MySQL server "
"documentation.",
_("OK"), "", "", "SQLIDE::check_server_problems::lower_case_table_names", "");
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::finish_startup() {
setup_side_palette();
_live_tree->finish_init();
std::string cache_dir = bec::GRTManager::get()->get_user_datadir() + "/cache/";
try {
base::create_directory(cache_dir, 0700); // No-op if the folder already exists.
} catch (std::exception &e) {
logError("Could not create %s: %s\n", cache_dir.c_str(), e.what());
}
_column_width_cache = new ColumnWidthCache(sanitize_file_name(get_session_name()), cache_dir);
if (_usr_dbc_conn && !_usr_dbc_conn->active_schema.empty())
_live_tree->on_active_schema_change(_usr_dbc_conn->active_schema);
readStaticServerSymbols();
bec::GRTManager::get()->run_once_when_idle(this, std::bind(&SqlEditorForm::update_menu_and_toolbar, this));
this->check_server_problems();
// We need to check this before sending GRNSQLEditorOpened cause offline() function that's called
// from python which is connected to this notification will deadlock on PythonLock.
checkIfOffline();
// refresh snippets again, in case the initial load from DB is pending for shared snippets
_side_palette->refresh_snippets();
GRTNotificationCenter::get()->send_grt("GRNSQLEditorOpened", grtobj(), grt::DictRef());
int keep_alive_interval = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:KeepAliveInterval", 600);
// We have to set these variables so that the server doesn't timeout before we ping everytime
// From http://dev.mysql.com/doc/refman/5.7/en/communication-errors.html for reasones to loose the connection
// - The client had been sleeping more than wait_timeout or interactive_timeout seconds without issuing any requests
// to the server
// We're adding 10 seconds for communication delays
{
std::string value;
if (get_session_variable(_usr_dbc_conn->ref.get(), "wait_timeout", value) &&
base::atoi<int>(value) < keep_alive_interval)
exec_main_sql(base::strfmt("SET @@SESSION.wait_timeout=%d", keep_alive_interval + 10), false);
if (get_session_variable(_usr_dbc_conn->ref.get(), "interactive_timeout", value) &&
base::atoi<int>(value) < keep_alive_interval)
exec_main_sql(base::strfmt("SET @@SESSION.interactive_timeout=%d", keep_alive_interval + 10), false);
}
_startup_done = true;
}
//----------------------------------------------------------------------------------------------------------------------
base::RecMutexLock SqlEditorForm::getAuxConnection(sql::Dbc_connection_handler::Ref &conn, bool lockOnly) {
RecMutexLock lock(ensure_valid_aux_connection(false, lockOnly));
conn = _aux_dbc_conn;
return lock;
}
//----------------------------------------------------------------------------------------------------------------------
base::RecMutexLock SqlEditorForm::getUserConnection(sql::Dbc_connection_handler::Ref &conn, bool lockOnly) {
RecMutexLock lock(ensure_valid_usr_connection(false, lockOnly));
conn = _usr_dbc_conn;
return lock;
}
//----------------------------------------------------------------------------------------------------------------------
db_query_EditorRef SqlEditorForm::grtobj() {
return wbsql()->get_grt_editor_object(this);
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Returns the name for this WQE instance derived from the connection it uses.
* Used for workspace and action log.
*/
std::string SqlEditorForm::get_session_name() {
if (_connection.is_valid()) {
std::string name = _connection->name();
if (name.empty())
name = _connection->hostIdentifier();
return name;
}
return "unconnected";
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::restore_last_workspace() {
std::string name = get_session_name();
if (!name.empty())
load_workspace(sanitize_file_name(name));
if (_tabdock->view_count() == 0)
new_sql_scratch_area(false);
// Gets the title for a NEW editor.
_title = create_title();
title_changed();
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::title_changed() {
base::NotificationInfo info;
info["form"] = form_id();
info["title"] = _title;
info["connection"] = _connection.is_valid() ? _connection->name() : "";
base::NotificationCenter::get()->send("GNFormTitleDidChange", this, info);
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::handle_grt_notification(const std::string &name, grt::ObjectRef sender, grt::DictRef info) {
if (name == "GRNServerStateChanged") {
db_mgmt_ConnectionRef conn(db_mgmt_ConnectionRef::cast_from(info.get("connection")));
ServerState new_state = UnknownState;
if (info.get_int("state") == 1) {
_serverIsOffline = false;
new_state = RunningState;
} else if (info.get_int("state") == -1) {
_serverIsOffline = true;
new_state = OfflineState;
} else {
_serverIsOffline = false;
new_state = PossiblyStoppedState;
}
if (_last_server_running_state != new_state) {
_last_server_running_state = new_state;
if ((new_state == RunningState || new_state == OfflineState) && ping()) {
// if new state is running but we're already connected, don't do anything
return;
}
// reconnect when idle, to avoid any deadlocks
if (conn.is_valid() && conn == connection_descriptor())
bec::GRTManager::get()->run_once_when_idle(this, std::bind(&WBContextSQLIDE::reconnect_editor, wbsql(), this));
}
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::handle_notification(const std::string &name, void *sender, base::NotificationInfo &info) {
if (name == "GNMainFormChanged") {
if (_side_palette)
_side_palette->close_popover();
if (info["form"] == form_id())
update_menu_and_toolbar();
} else if (name == "GNFormTitleDidChange") {
// Validates only if another editor to the same connection has sent the notification
if (info["form"] != form_id() && _connection.is_valid() && _connection->name() == info["connection"]) {
// This code is reached when at least 2 editors to the same host
// have been opened, so the label of the old editor (which may not
// contain the schema name should be updated with it).
update_title();
}
} else if (name == "GNColorsChanged") {
// Single colors or the entire color scheme changed.
update_toolbar_icons();
} else if (name == "GNApplicationActivated") {
check_external_file_changes();
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::reset_keep_alive_thread() {
MutexLock keep_alive_thread_lock(_keep_alive_thread_mutex);
if (_keep_alive_task_id) {
ThreadedTimer::remove_task(_keep_alive_task_id);
_keep_alive_task_id = 0;
}
}
//----------------------------------------------------------------------------------------------------------------------
grt::StringRef SqlEditorForm::do_disconnect() {
if (_usr_dbc_conn->ref.get()) {
{
RecMutexLock lock(_usr_dbc_conn_mutex);
close_connection(_usr_dbc_conn);
_usr_dbc_conn->ref.reset();
}
{
RecMutexLock lock(_aux_dbc_conn_mutex);
close_connection(_aux_dbc_conn);
_aux_dbc_conn->ref.reset();
}
}
return grt::StringRef();
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::close() {
grt::ValueRef option(bec::GRTManager::get()->get_app_option("workbench:SaveSQLWorkspaceOnClose"));
if (option.is_valid() && *grt::IntegerRef::cast_from(option)) {
bec::GRTManager::get()->replace_status_text("Saving workspace state...");
if (_autosave_path.empty()) {
save_workspace(sanitize_file_name(get_session_name()), false);
delete _autosave_lock;
} else {
auto_save();
// Remove auto lock first or renaming the folder will fail.
delete _autosave_lock;
std::string new_name(base::strip_extension(_autosave_path) + ".workspace");
int try_count = 0;
// Rename our temporary workspace if one exists to make it a persistent one.
if (base::file_exists(_autosave_path)) {
for (;;) {
try {
if (base::file_exists(new_name))
base::remove_recursive(new_name);
base::rename(_autosave_path, new_name);
} catch (base::file_error &err) {
std::string path(dirname(_autosave_path));
do {
++try_count;
new_name =
base::makePath(path, sanitize_file_name(get_session_name()).append(strfmt("-%i.workspace", try_count)));
} while (file_exists(new_name));
if (err.code() == base::already_exists)
continue;
logWarning("Could not rename autosave directory %s: %s\n", _autosave_path.c_str(), err.what());
}
break;
}
}
}
_autosave_lock = 0;
} else {
delete _autosave_lock;
_autosave_lock = 0;
if (!_autosave_path.empty())
base_rmdir_recursively(_autosave_path.c_str());
}
// Ensure all processing is stopped before freeing the info structure, otherwise references
// are kept that prevent the correct deletion of the editor.
if (_tabdock) {
for (std::size_t c = _tabdock->view_count(), i = 0; i < c; i++) {
SqlEditorPanel *p = sql_editor_panel((int)i);
if (p)
p->editor_be()->stop_processing();
}
_closing = true;
_tabdock->close_all_views();
_closing = false;
}
bec::GRTManager::get()->replace_status_text("Closing SQL Editor...");
wbsql()->editor_will_close(this);
exec_sql_task->exec(true, std::bind(&SqlEditorForm::do_disconnect, this));
exec_sql_task->disconnect_callbacks();
reset_keep_alive_thread();
bec::GRTManager::get()->replace_status_text("SQL Editor closed");
delete _menu;
_menu = nullptr;
delete _toolbar;
_toolbar = nullptr;
}
//----------------------------------------------------------------------------------------------------------------------
std::string SqlEditorForm::get_form_context_name() const {
return WB_CONTEXT_QUERY;
}
//----------------------------------------------------------------------------------------------------------------------
db_mgmt_SSHConnectionRef SqlEditorForm::getSSHConnection() {
try {
if (!_sshConnection.is_valid()) {
if (_connection.is_valid()) {
auto val = getServerInstance(_connection);
if (val.is_valid()) {
db_mgmt_SSHConnectionRef object(grt::Initialized);
object->owner(wb::WBContextUI::get()->get_wb()->get_root());
object->name(_connection->name());
object->set_data(new ssh::SSHSessionWrapper(val));
_sshConnection = object;
}
}
}
} catch (std::runtime_error &) {
logError("Unable to create db_mgmt_SSHConnectionRef object\n");
}
return _sshConnection;
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::get_session_variable(sql::Connection *dbc_conn, const std::string &name, std::string &value) {
if (dbc_conn) {
SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms(rdbms());
Sql_specifics::Ref sql_specifics = sql_facade->sqlSpecifics();
std::string query = sql_specifics->query_variable(name);
if (query.empty())
return false;
const std::unique_ptr<sql::Statement> statement(dbc_conn->createStatement());
const std::unique_ptr<sql::ResultSet> rs(statement->executeQuery(query));
if (rs->next()) {
value = rs->getString(2);
return true;
}
}
return false;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::schema_tree_did_populate() {
if (!_pending_expand_nodes.empty() &&
bec::GRTManager::get()->get_app_option_int("DbSqlEditor:SchemaTreeRestoreState", 1)) {
std::string schema, groups;
base::partition(_pending_expand_nodes, ":", schema, groups);
mforms::TreeNodeRef node =
_live_tree->get_schema_tree()->get_node_for_object(schema, wb::LiveSchemaTree::Schema, "");
if (node) {
static const char *nodes[] = {"tables", "views", "procedures", "functions", nullptr};
node->expand();
for (int i = 0; nodes[i]; i++)
if (strstr(groups.c_str(), nodes[i])) {
mforms::TreeNodeRef child = node->get_child(i);
if (child)
child->expand();
}
}
_pending_expand_nodes.clear();
}
}
//----------------------------------------------------------------------------------------------------------------------
std::string SqlEditorForm::fetch_data_from_stored_procedure(std::string proc_call,
std::shared_ptr<sql::ResultSet> &rs) {
std::string ret_val("");
try {
RecMutexLock aux_dbc_conn_mutex(ensure_valid_aux_connection());
std::unique_ptr<sql::Statement> stmt(_aux_dbc_conn->ref->createStatement());
stmt->execute(std::string(proc_call));
do {
rs.reset(stmt->getResultSet());
} while (stmt->getMoreResults());
} catch (const sql::SQLException &exc) {
logWarning("Error retrieving data from stored procedure '%s': Error %d : %s", proc_call.c_str(), exc.getErrorCode(),
exc.what());
ret_val = base::strfmt("MySQL Error : %s (code %d)", exc.what(), exc.getErrorCode());
}
return ret_val;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::update_sql_mode_for_editors() {
for (int c = sql_editor_count(), i = 0; i < c; i++) {
SqlEditorPanel *panel = sql_editor_panel(i);
if (panel)
panel->editor_be()->set_sql_mode(_sql_mode);
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::cache_sql_mode() {
std::string sql_mode;
if (_usr_dbc_conn && get_session_variable(_usr_dbc_conn->ref.get(), "sql_mode", sql_mode)) {
if (sql_mode != _sql_mode) {
_sql_mode = sql_mode;
bec::GRTManager::get()->run_once_when_idle(this, std::bind(&SqlEditorForm::update_sql_mode_for_editors, this));
}
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::query_ps_statistics(std::int64_t conn_id, std::map<std::string, std::int64_t> &stats) {
static const char *stat_fields[] = {"EVENT_ID",
"THREAD_ID",
"TIMER_WAIT",
"LOCK_TIME",
"ERRORS",
"WARNINGS",
"ROWS_AFFECTED",
"ROWS_SENT",
"ROWS_EXAMINED",
"CREATED_TMP_DISK_TABLES",
"CREATED_TMP_TABLES",
"SELECT_FULL_JOIN",
"SELECT_FULL_RANGE_JOIN",
"SELECT_RANGE",
"SELECT_RANGE_CHECK",
"SELECT_SCAN",
"SORT_MERGE_PASSES",
"SORT_RANGE",
"SORT_ROWS",
"SORT_SCAN",
"NO_INDEX_USED",
"NO_GOOD_INDEX_USED",
nullptr};
RecMutexLock lock(ensure_valid_aux_connection());
std::unique_ptr<sql::Statement> stmt(_aux_dbc_conn->ref->createStatement());
try {
std::unique_ptr<sql::ResultSet> result(stmt->executeQuery(base::strfmt(
"SELECT st.* FROM performance_schema.events_statements_current st JOIN performance_schema.threads thr"
" ON thr.thread_id = st.thread_id WHERE thr.processlist_id = %lli",
(long long int)conn_id)));
while (result->next()) {
for (const char **field = stat_fields; *field; ++field) {
stats[*field] = result->getInt64(*field);
}
}
} catch (sql::SQLException &exc) {
logException("Error querying performance_schema.events_statements_current\n", exc);
}
}
//----------------------------------------------------------------------------------------------------------------------
std::vector<SqlEditorForm::PSStage> SqlEditorForm::query_ps_stages(std::int64_t stmt_event_id) {
RecMutexLock lock(ensure_valid_aux_connection());
std::unique_ptr<sql::Statement> stmt(_aux_dbc_conn->ref->createStatement());
std::vector<PSStage> stages;
try {
std::unique_ptr<sql::ResultSet> result(stmt->executeQuery(
base::strfmt("SELECT st.* FROM performance_schema.events_stages_history_long st WHERE st.nesting_event_id = %lli",
(long long int)stmt_event_id)));
while (result->next()) {
double wait_time = (double)result->getInt64("TIMER_WAIT") / 1000000000.0; // ps to ms
std::string event = result->getString("EVENT_NAME");
// rename the stage/sql/Sending data event to something more suitable
if (event == "stage/sql/Sending data")
event = "executing (storage engine)";
bool flag = false;
for (std::vector<PSStage>::iterator iter = stages.begin(); iter != stages.end(); ++iter) {
if (iter->name == event) {
flag = true;
iter->wait_time += wait_time;
break;
}
}
if (!flag) {
PSStage stage;
stage.name = event;
stage.wait_time = wait_time;
stages.push_back(stage);
}
}
} catch (sql::SQLException &exc) {
logException("Error querying performance_schema.event_stages_history\n", exc);
}
return stages;
}
//----------------------------------------------------------------------------------------------------------------------
std::vector<SqlEditorForm::PSWait> SqlEditorForm::query_ps_waits(std::int64_t stmt_event_id) {
RecMutexLock lock(ensure_valid_aux_connection());
std::unique_ptr<sql::Statement> stmt(_aux_dbc_conn->ref->createStatement());
std::vector<PSWait> waits;
try {
std::unique_ptr<sql::ResultSet> result(stmt->executeQuery(
base::strfmt("SELECT st.* FROM performance_schema.events_waits_history_long st WHERE st.nesting_event_id = %lli",
(long long int)stmt_event_id)));
while (result->next()) {
double wait_time = (double)result->getInt64("TIMER_WAIT") / 1000000000.0; // ps to ms
std::string event = result->getString("EVENT_NAME");
bool flag = false;
for (std::vector<PSWait>::iterator iter = waits.begin(); iter != waits.end(); ++iter) {
if (iter->name == event) {
flag = true;
iter->wait_time += wait_time;
break;
}
}
if (!flag) {
PSWait wait;
wait.name = event;
wait.wait_time = wait_time;
waits.push_back(wait);
}
}
} catch (sql::SQLException &exc) {
logException("Error querying performance_schema.event_waits_history\n", exc);
}
return waits;
}
//----------------------------------------------------------------------------------------------------------------------
SqlEditorPanel *SqlEditorForm::run_sql_in_scratch_tab(const std::string &sql, bool reuse_if_possible,
bool start_collapsed) {
SqlEditorPanel *editor;
if (!(editor = active_sql_editor_panel()) || !reuse_if_possible || !editor->is_scratch())
editor = new_sql_scratch_area(start_collapsed);
editor->editor_be()->get_editor_control()->set_text(sql.c_str());
run_editor_contents(false);
editor->editor_be()->get_editor_control()->reset_dirty();
return editor;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::reset() {
SqlEditorPanel *panel = active_sql_editor_panel();
if (panel)
panel->editor_be()->cancel_auto_completion();
}
//----------------------------------------------------------------------------------------------------------------------
void logToWorkbenchLog(int messageType, std::string const &msg) {
switch (messageType) {
case DbSqlEditorLog::ErrorMsg:
logError("%s\n", msg.c_str());
break;
case DbSqlEditorLog::WarningMsg:
logWarning("%s\n", msg.c_str());
break;
case DbSqlEditorLog::NoteMsg:
logDebug("%s\n", msg.c_str());
break;
case DbSqlEditorLog::OKMsg:
logDebug2("%s\n", msg.c_str());
break;
case DbSqlEditorLog::BusyMsg:
logDebug3("%s\n", msg.c_str());
break;
}
}
//----------------------------------------------------------------------------------------------------------------------
int SqlEditorForm::add_log_message(int messageType, const std::string &msg, const std::string &context,
const std::string &duration) {
RowId new_log_message_index = _log->add_message(messageType, context, msg, duration);
_has_pending_log_messages = true;
refresh_log_messages(false);
if (messageType == DbSqlEditorLog::ErrorMsg || messageType == DbSqlEditorLog::WarningMsg)
_exec_sql_error_count++;
logToWorkbenchLog(messageType, msg);
return (int)new_log_message_index;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::set_log_message(RowId log_message_index, int messageType, const std::string &msg,
const std::string &context, const std::string &duration) {
if (log_message_index != (RowId)-1) {
_log->set_message(log_message_index, messageType, context, msg, duration);
_has_pending_log_messages = true;
if (messageType == DbSqlEditorLog::ErrorMsg || messageType == DbSqlEditorLog::WarningMsg)
++_exec_sql_error_count;
refresh_log_messages(messageType == DbSqlEditorLog::BusyMsg); // Force refresh only for busy messages.
}
logToWorkbenchLog(messageType, msg);
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::refresh_log_messages(bool ignore_last_message_timestamp) {
if (_has_pending_log_messages) {
bool is_refresh_needed = ignore_last_message_timestamp;
if (!ignore_last_message_timestamp) {
double now = timestamp();
int progress_status_update_interval =
(int)(bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ProgressStatusUpdateInterval", 500) / 1000.);
if (_last_log_message_timestamp + progress_status_update_interval < now)
is_refresh_needed = true;
_last_log_message_timestamp = now;
}
if (is_refresh_needed) {
_log->refresh();
_has_pending_log_messages = false;
}
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::init_connection(sql::Connection *dbc_conn_ref, const db_mgmt_ConnectionRef &connectionProperties,
sql::Dbc_connection_handler::Ref &dbc_conn, bool user_connection) {
db_mgmt_RdbmsRef rdbms = db_mgmt_RdbmsRef::cast_from(_connection->driver()->owner());
SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms(rdbms);
Sql_specifics::Ref sql_specifics = sql_facade->sqlSpecifics();
// connection startup script
{
std::list<std::string> sql_script;
{
sql_specifics->get_connection_startup_script(sql_script);
bool use_ansi_quotes = (connectionProperties->parameterValues().get_int("useAnsiQuotes", 0) != 0);
if (use_ansi_quotes) {
std::string sql = sql_specifics->setting_ansi_quotes();
if (!sql.empty())
sql_script.push_back(sql);
}
}
// check if SQL_SAFE_UPDATES should be enabled (only for user connections, don't do it for the aux connection)
if (bec::GRTManager::get()->get_app_option_int("DbSqlEditor:SafeUpdates", 1) && user_connection)
sql_script.push_back("SET SQL_SAFE_UPDATES=1");
std::unique_ptr<sql::Statement> stmt(dbc_conn_ref->createStatement());
sql::SqlBatchExec sql_batch_exec;
sql_batch_exec(stmt.get(), sql_script);
if (!user_connection) {
std::string sql_mode;
if (get_session_variable(dbc_conn_ref, "sql_mode", sql_mode) && sql_mode.find("MYSQL40") != std::string::npos) {
// MYSQL40 used CREATE TABLE ... TYPE=<engine> instead of ENGINE=<engine>, which is not supported by our reveng
// code
std::vector<std::string> options(base::split(sql_mode, ","));
for (std::vector<std::string>::iterator i = options.begin(); i != options.end(); ++i) {
if (*i == "MYSQL40") {
options.erase(i);
break;
}
}
std::unique_ptr<sql::Statement> stmt(dbc_conn_ref->createStatement());
std::string query = base::sqlstring("SET SESSION SQL_MODE=?", 0) << base::join(options, ",");
stmt->execute(query);
}
}
}
// remember connection id
{
std::string query_connection_id = sql_specifics->query_connection_id();
if (!query_connection_id.empty()) {
std::unique_ptr<sql::Statement> stmt(dbc_conn_ref->createStatement());
stmt->execute(query_connection_id);
std::shared_ptr<sql::ResultSet> rs(stmt->getResultSet());
rs->next();
dbc_conn->id = rs->getInt(1);
}
}
}
//----------------------------------------------------------------------------------------------------------------------
static void set_active_schema(SqlEditorForm::Ptr self, const std::string &schema) {
SqlEditorForm::Ref ed(self.lock());
if (ed)
ed->active_schema(schema);
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::create_connection(sql::Dbc_connection_handler::Ref &dbc_conn, db_mgmt_ConnectionRef db_mgmt_conn,
std::shared_ptr<SSHTunnel> tunnel, sql::Authentication::Ref auth,
bool autocommit_mode, bool user_connection) {
dbc_conn->is_stop_query_requested = false;
sql::DriverManager *dbc_drv_man = sql::DriverManager::getDriverManager();
db_mgmt_ConnectionRef temp_connection = db_mgmt_ConnectionRef::cast_from(grt::CopyContext().copy(db_mgmt_conn));
int read_timeout = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ReadTimeOut");
if (read_timeout < 0) {
bec::GRTManager::get()->set_app_option("DbSqlEditor:ReadTimeOut", grt::IntegerRef((int)0));
read_timeout = 0;
}
temp_connection->parameterValues().set("OPT_READ_TIMEOUT", grt::IntegerRef(read_timeout));
int connect_timeout = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ConnectionTimeOut");
if (connect_timeout > 0)
temp_connection->parameterValues().set("OPT_CONNECT_TIMEOUT", grt::IntegerRef(connect_timeout));
temp_connection->parameterValues().set("CLIENT_INTERACTIVE", grt::IntegerRef(1));
try {
dbc_conn->ref = dbc_drv_man->getConnection(temp_connection, tunnel, auth,
std::bind(&SqlEditorForm::init_connection, this, std::placeholders::_1,
std::placeholders::_2, dbc_conn, user_connection));
note_connection_open_outcome(0); // success
} catch (sql::SQLException &exc) {
note_connection_open_outcome(exc.getErrorCode());
throw;
}
//! dbms-specific code
if (dbc_conn->ref->getMetaData()->getDatabaseMajorVersion() < 5) {
throw std::runtime_error("MySQL Server version is older than 5.x, which is not supported");
}
// get SSL enabled info
{
std::unique_ptr<sql::Statement> stmt(dbc_conn->ref->createStatement());
std::unique_ptr<sql::ResultSet> result(stmt->executeQuery("SHOW SESSION STATUS LIKE 'Ssl_cipher'"));
if (result->next()) {
dbc_conn->ssl_cipher = result->getString(2);
}
}
// Activate default schema, if it's empty, use last active
if (dbc_conn->active_schema.empty()) {
std::string default_schema = temp_connection->parameterValues().get_string("schema");
if (default_schema.empty())
default_schema = temp_connection->parameterValues().get_string("DbSqlEditor:LastDefaultSchema");
if (!default_schema.empty()) {
try {
dbc_conn->ref->setSchema(default_schema);
dbc_conn->active_schema = default_schema;
bec::GRTManager::get()->run_once_when_idle(this,
std::bind(&set_active_schema, shared_from_this(), default_schema));
} catch (std::exception &exc) {
logError("Can't restore default schema (%s): %s\n", default_schema.c_str(), exc.what());
temp_connection->parameterValues().gset("DbSqlEditor:LastDefaultSchema", "");
}
}
} else
dbc_conn->ref->setSchema((dbc_conn->active_schema));
dbc_conn->ref->setAutoCommit(autocommit_mode);
dbc_conn->autocommit_mode = dbc_conn->ref->getAutoCommit();
}
//----------------------------------------------------------------------------------------------------------------------
struct ConnectionErrorInfo {
sql::AuthenticationError *auth_error;
bool password_expired;
bool server_probably_down;
bool serverIsOffline;
grt::server_denied *serverException;
ConnectionErrorInfo()
: auth_error(nullptr),
password_expired(false),
server_probably_down(false),
serverIsOffline(false),
serverException(nullptr) {
}
~ConnectionErrorInfo() {
delete auth_error;
delete serverException;
}
};
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::set_connection(db_mgmt_ConnectionRef conn) {
if (_connection.is_valid())
logWarning("Setting connection on an editor with a connection already set\n");
_connection = conn;
_dbc_auth = sql::Authentication::create(_connection, "");
// initialize the password with a cached value
{
std::string password = conn->parameterValues().get_string("password");
bool ok = true;
if (password.empty()) {
if (!mforms::Utilities::find_password(conn->hostIdentifier(), conn->parameterValues().get_string("userName"),
password)) {
if (!mforms::Utilities::find_cached_password(conn->hostIdentifier(),
conn->parameterValues().get_string("userName"), password)) {
ok = false;
}
}
}
if (ok)
_dbc_auth->set_password(password.c_str());
}
// send editor open notification again, in case the connection is being set after the connection
// tab is opened. this will be caught by the admin code to init itself
if (_startup_done)
GRTNotificationCenter::get()->send_grt("GRNSQLEditorOpened", grtobj(), grt::DictRef());
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::connect(std::shared_ptr<SSHTunnel> tunnel) {
sql::Authentication::Ref auth = _dbc_auth; // sql::Authentication::create(_connection, "");
enum PasswordMethod { NoPassword, KeychainPassword, InteractivePassword } current_method = NoPassword;
reset();
// In the 1st connection attempt, no password is supplied
// If it fails, keychain is checked and used if it exists
// If it fails, an interactive password request is made
// connect
for (;;) {
// if an error happens in the worker thread, this ptr will be set
ConnectionErrorInfo error_ptr;
// connection must happen in the worker thread
try {
exec_sql_task->exec(true, std::bind(&SqlEditorForm::do_connect, this, tunnel, auth, &error_ptr));
// check if user cancelled
if (_cancel_connect) // return false, so it looks like the server is down
{
close();
return false;
}
} catch (grt::grt_runtime_error &) {
if (error_ptr.serverException != nullptr)
throw grt::server_denied(*error_ptr.serverException);
if (error_ptr.password_expired)
throw std::runtime_error(":PASSWORD_EXPIRED");
if (!error_ptr.auth_error)
throw;
else if (error_ptr.server_probably_down || error_ptr.serverIsOffline)
return false;
// check if user cancelled
if (_cancel_connect) // return false, so it looks like the server is down
{
close();
return false;
}
if (current_method == NoPassword) {
// lookup in keychain
std::string pwd;
if (sql::DriverManager::getDriverManager()->findStoredPassword(auth->connectionProperties(), pwd)) {
auth->set_password(pwd.c_str());
current_method = KeychainPassword;
} else {
// not in keychain, go straight to interactive
pwd = sql::DriverManager::getDriverManager()->requestPassword(auth->connectionProperties(), true);
auth->set_password(pwd.c_str());
current_method = InteractivePassword;
}
} else if (current_method == KeychainPassword) {
// now try interactive
std::string pwd = sql::DriverManager::getDriverManager()->requestPassword(auth->connectionProperties(), true);
auth->set_password(pwd.c_str());
} else // if interactive failed, pass the exception higher up to be displayed to the user
throw;
continue;
}
break;
}
// XXX: ouch, what if we ever change the init sequence, *nobody* will look here to note the side effect.
// we should only send this after the initial connection
// assumes setup_side_palette() is called in finish_init(), signalizing that the editor was already initialized once
if (_side_palette) // we're in a thread here, so make sure the notification is sent from the main thread
{
bec::GRTManager::get()->run_once_when_idle(this, std::bind(&SqlEditorForm::update_connected_state, this));
}
return true;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::update_connected_state() {
grt::DictRef args(true);
args.gset("connected", connected());
GRTNotificationCenter::get()->send_grt("GRNSQLEditorReconnected", grtobj(), args);
update_menu_and_toolbar();
}
//----------------------------------------------------------------------------------------------------------------------
std::string SqlEditorForm::get_client_lib_version() {
std::string version;
sql::DriverManager *dbc_driver_man = sql::DriverManager::getDriverManager();
if (dbc_driver_man != nullptr)
version = dbc_driver_man->getClientLibVersion();
return version;
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Formats a single text line for the connection info output.
*/
std::string createTableRow(const std::string &name, const std::string &value = "") {
if (value.empty()) // Empty value means: heading row.
return "<tr class='heading'>"s +
"<td style='border:none; padding-left: 0px;' colspan=2>" + name + "</td>"
"</tr>";
return "<tr>"s +
"<td style='border:none; padding-left: 15px;'>" + name + "</td>"
"<td style='border:none;'>" + value + "</td>"
"</tr>";
}
//----------------------------------------------------------------------------------------------------------------------
grt::StringRef SqlEditorForm::do_connect(std::shared_ptr<SSHTunnel> tunnel, sql::Authentication::Ref &auth,
ConnectionErrorInfo *err_ptr) {
try {
RecMutexLock aux_dbc_conn_mutex(_aux_dbc_conn_mutex);
RecMutexLock usr_dbc_conn_mutex(_usr_dbc_conn_mutex);
_aux_dbc_conn->ref.reset();
_usr_dbc_conn->ref.reset();
_connection_details["name"] = _connection->name();
_connection_details["hostName"] = _connection->parameterValues().get_string("hostName");
_connection_details["port"] = strfmt("%li\n", (long)_connection->parameterValues().get_int("port"));
_connection_details["socket"] = _connection->parameterValues().get_string("socket");
_connection_details["driverName"] = _connection->driver()->name();
_connection_details["userName"] = _connection->parameterValues().get_string("userName");
// During the connection process create also a description about the connection details that can be shown
// in the SQL IDE. Only the body content is created here. Surrounding code is added by the tree controller.
// Start with the default content, in case we cannot connect.
_connectionInfo = "<b><span style='color: red'>No connection established</span></b>";
std::string newConnectionInfo = "<body><table style='border: none; border-collapse: collapse;'>" +
createTableRow("Connection Details") + createTableRow("Name: ", _connection->name());
_tunnel = tunnel;
if (_tunnel == nullptr) {
if (_connection->driver()->name() == "MysqlNativeSocket") {
#ifdef _MSC_VER
std::string name = _connection->parameterValues().get_string("socket", "");
if (name.empty())
name = "pipe";
#else
std::string name = _connection->parameterValues().get_string("socket", "");
if (name.empty())
name = "UNIX socket";
#endif
newConnectionInfo += createTableRow("Host:", "localhost (" + name + ")");
} else {
newConnectionInfo += createTableRow("Host:", _connection->parameterValues().get_string("hostName"));
newConnectionInfo += createTableRow("Port:", std::to_string(_connection->parameterValues().get_int("port")));
}
}
// open connections
create_connection(_aux_dbc_conn, _connection, tunnel, auth, _aux_dbc_conn->autocommit_mode, false);
create_connection(_usr_dbc_conn, _connection, tunnel, auth, _usr_dbc_conn->autocommit_mode, true);
_serverIsOffline = false;
cache_sql_mode();
try {
std::string value;
get_session_variable(_usr_dbc_conn->ref.get(), "version_comment", value);
_connection_details["dbmsProductName"] = value;
get_session_variable(_usr_dbc_conn->ref.get(), "version", value);
_connection_details["dbmsProductVersion"] = value;
logInfo("Opened connection '%s' to %s version %s\n", _connection->name().c_str(),
_connection_details["dbmsProductName"].c_str(), _connection_details["dbmsProductVersion"].c_str());
_version = parse_version(_connection_details["dbmsProductVersion"]);
_version->name(grt::StringRef(_connection_details["dbmsProductName"]));
db_query_EditorRef editor(grtobj());
if (editor.is_valid()) // this will be valid only on reconnections
editor->serverVersion(_version);
newConnectionInfo += createTableRow("Login User:", _connection->parameterValues().get_string("userName"));
// check the actual user we're logged in as
if (_usr_dbc_conn && _usr_dbc_conn->ref.get()) {
const std::unique_ptr<sql::Statement> statement(_usr_dbc_conn->ref->createStatement());
const std::unique_ptr<sql::ResultSet> rs(statement->executeQuery("SELECT current_user()"));
if (rs->next())
newConnectionInfo += createTableRow("Current User:", rs->getString(1));
}
newConnectionInfo += createTableRow(
"SSL cipher:", _usr_dbc_conn->ssl_cipher.empty() ? "SSL not used" : _usr_dbc_conn->ssl_cipher);
if (_tunnel != nullptr) {
auto &config = _tunnel->getConfig();
newConnectionInfo += createTableRow("SSH Tunnel");
newConnectionInfo += createTableRow("Target:", config.remoteSSHhost + ":" + std::to_string(config.remoteSSHport));
newConnectionInfo += createTableRow("Local port:", std::to_string(config.localport));
newConnectionInfo += createTableRow("Remote port:", std::to_string(config.remoteport));
newConnectionInfo += createTableRow("Remote host:", config.remotehost);
newConnectionInfo += createTableRow("Config file:", config.configFile);
}
newConnectionInfo += createTableRow("Server");
newConnectionInfo += createTableRow("Product:", _connection_details["dbmsProductName"]);
newConnectionInfo += createTableRow("Version:", _connection_details["dbmsProductVersion"]);
newConnectionInfo += createTableRow("Connector");
newConnectionInfo += createTableRow("Version:", get_client_lib_version());
if (_usr_dbc_conn && get_session_variable(_usr_dbc_conn->ref.get(), "lower_case_table_names", value))
_lower_case_table_names = base::atoi<int>(value, 0);
parsers::MySQLParserServices::Ref services = parsers::MySQLParserServices::get();
_work_parser_context =
services->createParserContext(rdbms()->characterSets(), _version, _sql_mode, _lower_case_table_names != 0);
// If we came so far, we probably have a valid connection.
_connectionInfo = newConnectionInfo + "</table>";
}
CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Get connection information"));
} catch (sql::AuthenticationError &authException) {
err_ptr->auth_error = new sql::AuthenticationError(authException);
throw;
} catch (sql::SQLException &exc) {
logException("SqlEditorForm: exception in do_connect method", exc);
_version = bec::intToVersion(50717); // Set a meaningful default version if we cannot open a connection.
switch (exc.getErrorCode()) {
case 1820: // ER_MUST_CHANGE_PASSWORD_LOGIN
err_ptr->password_expired = true;
break;
case 2013:
case 2003:
case 2002: { // ERROR 2003 (HY000): Can't connect to MySQL server on X.Y.Z.W (or via socket)
add_log_message(WarningMsg, exc.what(), "Could not connect, server may not be running.", "");
err_ptr->server_probably_down = true;
if (_connection.is_valid()) {
// if there's no connection, then we continue anyway if this is a local connection or
// a remote connection with remote admin enabled..
grt::Module *m = grt::GRT::get()->get_module("WbAdmin");
grt::BaseListRef args(true);
args.ginsert(_connection);
if (!m || *grt::IntegerRef::cast_from(m->call_function("checkConnectionForRemoteAdmin", args)) == 0) {
logError("Connection failed but remote admin does not seem to be available, rethrowing exception...\n");
throw;
}
logInfo("Error %i connecting to server, assuming server is down and opening editor with no connection\n",
exc.getErrorCode());
}
logInfo("Error %i connecting to server, assuming server is down and opening editor with no connection\n",
exc.getErrorCode());
// Create a parser with some sensible defaults if we cannot connect.
// We specify no charsets here, disabling parsing of repertoires.
parsers::MySQLParserServices::Ref services = parsers::MySQLParserServices::get();
_work_parser_context = services->createParserContext(GrtCharacterSetsRef(true), _version, _sql_mode, true);
return grt::StringRef();
}
case 3032: {
err_ptr->serverIsOffline = true;
_serverIsOffline = true;
add_log_message(WarningMsg, exc.what(), "Could not connect, server is in offline mode.", "");
if (_connection.is_valid()) {
// if there's no connection, then we continue anyway if this is a local connection or
// a remote connection with remote admin enabled..
grt::GRT::get()->get_module("WbAdmin");
grt::BaseListRef args(true);
args.ginsert(_connection);
}
logInfo("Error %i connecting to server, server is in offline mode. Only superuser connection are allowed. "
"Opening editor with no connection\n", exc.getErrorCode());
// Create a parser with some sensible defaults if we cannot connect.
// We specify no charsets here, disabling parsing of repertoires.
parsers::MySQLParserServices::Ref services = parsers::MySQLParserServices::get();
_work_parser_context = services->createParserContext(GrtCharacterSetsRef(true), _version, _sql_mode, true);
return grt::StringRef();
}
case 3159: {
// require SSL, offline mode
// we need to change exception type so we can properly handle it in
err_ptr->serverException = new grt::server_denied(exc.what(), exc.getErrorCode());
break;
}
}
_connectionInfo += "</body>";
throw;
}
_connectionInfo += "</body>";
return grt::StringRef();
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::connected() const {
// If the conn mutex is locked by someone else, then we assume the conn is in use and thus,
// there's a connection.
bool busy = !_usr_dbc_conn_mutex.tryLock();
if (!busy)
_usr_dbc_conn_mutex.unlock();
if (_usr_dbc_conn && (busy || _usr_dbc_conn->ref.get_ptr()))
return true; // we don't need to PING the server every time we want to check if the editor is connected
return false;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::checkIfOffline() {
bool locked = _usr_dbc_conn_mutex.tryLock();
size_t counter = 1;
while (!locked) {
if (counter >= 30) {
logError("Can't lock conn mutex for 30 seconds, assuming server is not offline.\n");
break;
}
logDebug3("Can't lock connection mutex, trying again in one sec.\n");
std::this_thread::sleep_for(std::chrono::seconds(1));
counter++;
locked = _usr_dbc_conn_mutex.tryLock();
}
if (locked) {
std::string result;
if (_usr_dbc_conn && get_session_variable(_usr_dbc_conn->ref.get(), "offline_mode", result)) {
if (base::string_compare(result, "ON") == 0)
_serverIsOffline = true;
}
_usr_dbc_conn_mutex.unlock();
}
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::offline() {
if (_serverIsOffline)
return true;
if (!connected())
return false;
return _serverIsOffline;
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::ping() const {
// If the conn mutex is locked by someone else, then we assume the conn is in use and thus,
// there's a connection.
bool locked = _usr_dbc_conn_mutex.tryLock();
if (!locked)
return true;
if (_usr_dbc_conn && _usr_dbc_conn->ref.get_ptr()) {
std::unique_ptr<sql::Statement> stmt(_usr_dbc_conn->ref->createStatement());
try {
stmt->execute("do 1");
_usr_dbc_conn_mutex.unlock();
return true;
} catch (const std::exception &ex) {
logError("Failed to ping the server: %s\n", ex.what());
}
}
_usr_dbc_conn_mutex.unlock();
return false;
}
//----------------------------------------------------------------------------------------------------------------------
base::RecMutexLock SqlEditorForm::ensure_valid_aux_connection(sql::Dbc_connection_handler::Ref &conn, bool lockOnly) {
RecMutexLock lock(ensure_valid_dbc_connection(_aux_dbc_conn, _aux_dbc_conn_mutex, lockOnly));
conn = _aux_dbc_conn;
return lock;
}
//----------------------------------------------------------------------------------------------------------------------
RecMutexLock SqlEditorForm::ensure_valid_aux_connection(bool throw_on_block, bool lockOnly) {
return ensure_valid_dbc_connection(_aux_dbc_conn, _aux_dbc_conn_mutex, throw_on_block, lockOnly);
}
//----------------------------------------------------------------------------------------------------------------------
RecMutexLock SqlEditorForm::ensure_valid_usr_connection(bool throw_on_block, bool lockOnly) {
return ensure_valid_dbc_connection(_usr_dbc_conn, _usr_dbc_conn_mutex, throw_on_block, lockOnly);
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::close_connection(sql::Dbc_connection_handler::Ref &dbc_conn) {
sql::Dbc_connection_handler::Ref myref(dbc_conn);
if (dbc_conn && dbc_conn->ref.get_ptr()) {
try {
dbc_conn->ref->close();
} catch (sql::SQLException &) {
// ignore if the connection is already closed
}
}
}
//----------------------------------------------------------------------------------------------------------------------
RecMutexLock SqlEditorForm::ensure_valid_dbc_connection(sql::Dbc_connection_handler::Ref &dbc_conn,
base::RecMutex &dbc_conn_mutex, bool throw_on_block,
bool lockOnly) {
RecMutexLock mutex_lock(dbc_conn_mutex, throw_on_block);
bool valid = false;
sql::Dbc_connection_handler::Ref myref(dbc_conn);
if (dbc_conn && dbc_conn->ref.get_ptr()) {
if (lockOnly) // this is a special case, we need it in some situations like for example recordset_cdbc
return mutex_lock;
try {
// use connector::isValid to check if server connection is valid
// this will also ping the server and reconnect if needed
valid = dbc_conn->ref->isValid();
} catch (std::exception &exc) {
logError("CppConn::isValid exception: %s", exc.what());
valid = false;
}
if (!valid) {
bool user_connection = _usr_dbc_conn ? dbc_conn->ref.get_ptr() == _usr_dbc_conn->ref.get_ptr() : false;
if (dbc_conn->autocommit_mode) {
sql::AuthenticationSet authset;
std::shared_ptr<SSHTunnel> tunnel = sql::DriverManager::getDriverManager()->getTunnel(_connection);
create_connection(dbc_conn, _connection, tunnel, sql::Authentication::Ref(), dbc_conn->autocommit_mode,
user_connection);
if (!dbc_conn->ref->isClosed())
valid = true;
}
} else
valid = true;
}
if (!valid)
throw grt::db_not_connected("DBMS connection is not available");
return mutex_lock;
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::auto_commit() {
if (_usr_dbc_conn)
return _usr_dbc_conn->autocommit_mode;
return false;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::auto_commit(bool value) {
if (!_usr_dbc_conn)
return;
{
const char *STATEMENT = value ? "AUTOCOMMIT=1" : "AUTOCOMMIT=0";
try {
RecMutexLock usr_dbc_conn_mutex = ensure_valid_usr_connection();
_usr_dbc_conn->ref->setAutoCommit(value);
_usr_dbc_conn->autocommit_mode = _usr_dbc_conn->ref->getAutoCommit();
}
CATCH_ANY_EXCEPTION_AND_DISPATCH(STATEMENT)
}
update_menu_and_toolbar();
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::toggle_autocommit() {
auto_commit(!auto_commit());
update_menu_and_toolbar();
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::toggle_collect_field_info() {
if (_connection.is_valid())
_connection->parameterValues().set("CollectFieldMetadata", grt::IntegerRef(collect_field_info() ? 0 : 1));
update_menu_and_toolbar();
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::collect_field_info() const {
if (_connection.is_valid())
return _connection->parameterValues().get_int("CollectFieldMetadata", 1) != 0;
return false;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::toggle_collect_ps_statement_events() {
if (_connection.is_valid())
_connection->parameterValues().set("CollectPerfSchemaStatsForQueries",
grt::IntegerRef(collect_ps_statement_events() ? 0 : 1));
update_menu_and_toolbar();
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::collect_ps_statement_events() const {
if (_connection.is_valid() && is_supported_mysql_version_at_least(rdbms_version(), 5, 6))
return _connection->parameterValues().get_int("CollectPerfSchemaStatsForQueries", 1) != 0;
return false;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::cancel_query() {
std::string query_kill_query;
{
db_mgmt_RdbmsRef rdbms = db_mgmt_RdbmsRef::cast_from(_connection->driver()->owner());
SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms(rdbms);
Sql_specifics::Ref sql_specifics = sql_facade->sqlSpecifics();
query_kill_query = sql_specifics->query_kill_query(_usr_dbc_conn->id);
}
if (query_kill_query.empty())
return;
const char *STATEMENT = "INTERRUPT";
RowId log_message_index = add_log_message(DbSqlEditorLog::BusyMsg, _("Running..."), STATEMENT, "");
Timer timer(false);
try {
{
RecMutexLock aux_dbc_conn_mutex(ensure_valid_aux_connection());
std::unique_ptr<sql::Statement> stmt(_aux_dbc_conn->ref->createStatement());
{
base::ScopeExitTrigger schedule_timer_stop(std::bind(&Timer::stop, &timer));
timer.run();
stmt->execute(query_kill_query);
// this can potentially cause threading issues, since connector driver isn't thread-safe
// close_connection(_usr_dbc_conn);
// connection drop doesn't interrupt fetching stage (surprisingly)
// to workaround that we set special flag and check it periodically during fetching
_usr_dbc_conn->is_stop_query_requested = is_running_query();
}
}
if (_usr_dbc_conn->is_stop_query_requested) {
bec::GRTManager::get()->replace_status_text("Query Cancelled");
set_log_message(log_message_index, DbSqlEditorLog::NoteMsg, _("OK - Query cancelled"), STATEMENT,
timer.duration_formatted());
} else
set_log_message(log_message_index, DbSqlEditorLog::NoteMsg, _("OK - Query already completed"), STATEMENT,
timer.duration_formatted());
// reconnect but only if in autocommit mode
if (_usr_dbc_conn->autocommit_mode) {
// this will restore connection if it was established previously
exec_sql_task->execute_in_main_thread(std::bind(&SqlEditorForm::send_message_keep_alive, this), false, true);
}
}
CATCH_SQL_EXCEPTION_AND_DISPATCH(STATEMENT, log_message_index, "")
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::commit() {
exec_sql_retaining_editor_contents("COMMIT", nullptr, false);
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::rollback() {
exec_sql_retaining_editor_contents("ROLLBACK", nullptr, false);
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::explain_current_statement() {
SqlEditorPanel *panel = active_sql_editor_panel();
if (panel) {
SqlEditorResult *result = panel->add_panel_for_recordset(Recordset::Ref());
result->set_title("Explain");
grt::BaseListRef args(true);
args.ginsert(panel->grtobj());
args.ginsert(result->grtobj());
// run the visual explain plugin, so it will fill the result panel
grt::GRT::get()->call_module_function("SQLIDEQueryAnalysis", "visualExplain", args);
}
}
//----------------------------------------------------------------------------------------------------------------------
// Should actually be called _retaining_old_recordsets
void SqlEditorForm::exec_sql_retaining_editor_contents(const std::string &sql_script, SqlEditorPanel *editor, bool sync,
bool dont_add_limit_clause) {
auto_save();
if (!connected())
throw grt::db_not_connected("Not connected");
if (editor) {
editor->query_started(true);
exec_sql_task->finish_cb(std::bind(&SqlEditorPanel::query_finished, editor), true);
exec_sql_task->fail_cb(std::bind(&SqlEditorPanel::query_failed, editor, std::placeholders::_1), true);
}
exec_sql_task->exec(sync, std::bind(&SqlEditorForm::do_exec_sql, this, weak_ptr_from(this),
std::shared_ptr<std::string>(new std::string(sql_script)), editor,
(ExecFlags)(dont_add_limit_clause ? DontAddLimitClause : 0), RecordsetsRef()));
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::run_editor_contents(bool current_statement_only) {
SqlEditorPanel *panel(active_sql_editor_panel());
if (panel) {
exec_editor_sql(panel, false, current_statement_only, current_statement_only);
}
}
//----------------------------------------------------------------------------------------------------------------------
RecordsetsRef SqlEditorForm::exec_sql_returning_results(const std::string &sql_script, bool dont_add_limit_clause) {
if (!connected())
throw grt::db_not_connected("Not connected");
RecordsetsRef rsets(new Recordsets());
do_exec_sql(weak_ptr_from(this), std::shared_ptr<std::string>(new std::string(sql_script)), nullptr,
(ExecFlags)(dont_add_limit_clause ? DontAddLimitClause : 0), rsets);
return rsets;
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Runs the current content of the given editor on the target server and returns true if the query
* was actually started (useful for the platform layers to show a busy animation).
*
* @param editor The editor whose content is to be executed.
* @param sync If true wait for completion.
* @param current_statement_only If true then only the statement where the cursor is in is executed.
* Otherwise the current selection is executed (if there is one) or
* the entire editor content.
* @param use_non_std_delimiter If true the code is wrapped with a non standard delimiter to
* allow running the sql regardless of the delimiters used by the
* user (e.g. for view/sp definitions).
* @param dont_add_limit_clause If true the automatic addition of the LIMIT clause is suppressed, which
* is used to limit on the number of return rows (avoid huge result sets
* by accident).
* @param into_result If not nullptr, the resultset grid will be displayed inside it, instead of creating
* a new one in editor. The query/script must return at most one recordset.
*/
bool SqlEditorForm::exec_editor_sql(SqlEditorPanel *editor, bool sync, bool current_statement_only,
bool use_non_std_delimiter, bool dont_add_limit_clause,
SqlEditorResult *into_result) {
logDebug("Executing SQL in editor: %s (current statement only: %s)...\n", editor->get_title().c_str(),
current_statement_only ? "yes" : "no");
std::shared_ptr<std::string> shared_sql;
if (current_statement_only)
shared_sql.reset(new std::string(editor->editor_be()->current_statement()));
else {
std::string sql = editor->editor_be()->selected_text();
if (sql.empty()) {
std::pair<const char *, std::size_t> text = editor->text_data();
shared_sql.reset(new std::string(text.first, text.second));
} else
shared_sql.reset(new std::string(sql));
}
if (shared_sql->empty())
return false;
ExecFlags flags = (ExecFlags)0;
if (use_non_std_delimiter)
flags = (ExecFlags)(flags | NeedNonStdDelimiter);
if (dont_add_limit_clause)
flags = (ExecFlags)(flags | DontAddLimitClause);
if (bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ShowWarnings", 1))
flags = (ExecFlags)(flags | ShowWarnings);
auto_save();
// If we're filling an already existing result panel, we shouldn't close the old result sets.
editor->query_started(into_result ? true : false);
exec_sql_task->finish_cb(std::bind(&SqlEditorPanel::query_finished, editor), true);
exec_sql_task->fail_cb(std::bind(&SqlEditorPanel::query_failed, editor, std::placeholders::_1), true);
if (into_result) {
logDebug2("Running into existing rsets\n");
RecordsetsRef rsets(new Recordsets());
exec_sql_task->exec(sync, std::bind(&SqlEditorForm::do_exec_sql, this, weak_ptr_from(this), shared_sql,
(SqlEditorPanel *)nullptr, flags, rsets));
if (rsets->size() > 1)
logError("Statement returns too many resultsets\n");
if (!rsets->empty())
into_result->set_recordset((*rsets)[0]);
} else {
logDebug2("Running without considering existing rsets\n");
exec_sql_task->exec(sync, std::bind(&SqlEditorForm::do_exec_sql, this, weak_ptr_from(this), shared_sql, editor,
flags, RecordsetsRef()));
}
return true;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::update_live_schema_tree(const std::string &sql) {
bec::GRTManager::get()->run_once_when_idle(this, std::bind(&SqlEditorForm::handle_command_side_effects, this, sql));
}
//----------------------------------------------------------------------------------------------------------------------
grt::StringRef SqlEditorForm::do_exec_sql(Ptr self_ptr, std::shared_ptr<std::string> sql, SqlEditorPanel *editor,
ExecFlags flags, RecordsetsRef result_list) {
logDebug("Background task for sql execution started\n");
bool use_non_std_delimiter = (flags & NeedNonStdDelimiter) != 0;
bool dont_add_limit_clause = (flags & DontAddLimitClause) != 0;
std::map<std::string, std::int64_t> ps_stats;
std::vector<PSStage> ps_stages;
std::vector<PSWait> ps_waits;
bool query_ps_stats = collect_ps_statement_events();
std::string query_ps_statement_events_error;
std::string statement;
int max_query_size_to_log = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:MaxQuerySizeToHistory", 0);
int limit_rows = 0;
if (bec::GRTManager::get()->get_app_option_int("SqlEditor:LimitRows") != 0)
limit_rows = (int)bec::GRTManager::get()->get_app_option_int("SqlEditor:LimitRowsCount", 0);
bec::GRTManager::get()->replace_status_text(_("Executing Query..."));
std::shared_ptr<SqlEditorForm> self_ref = (self_ptr).lock();
SqlEditorForm *self = (self_ref).get();
if (!self) {
logError("Couldn't aquire lock for SQL editor form\n");
return grt::StringRef("");
}
// add_log_message() will increment this variable on errors or warnings
_exec_sql_error_count = 0;
bool interrupted = true;
sql::Driver *dbc_driver = nullptr;
try {
RecMutexLock use_dbc_conn_mutex(ensure_valid_usr_connection());
dbc_driver = _usr_dbc_conn->ref->getDriver();
dbc_driver->threadInit();
bool is_running_query = true;
AutoSwap<bool> is_running_query_keeper(_is_running_query, is_running_query);
update_menu_and_toolbar();
_has_pending_log_messages = false;
base::ScopeExitTrigger schedule_log_messages_refresh(std::bind(&SqlEditorForm::refresh_log_messages, this, true));
SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms(rdbms());
Sql_syntax_check::Ref sql_syntax_check = sql_facade->sqlSyntaxCheck();
Sql_specifics::Ref sql_specifics = sql_facade->sqlSpecifics();
bool ran_set_sql_mode = false;
bool logging_queries;
std::vector<std::pair<std::size_t, std::size_t>> statement_ranges;
sql_facade->splitSqlScript(sql->c_str(), sql->size(),
use_non_std_delimiter ? sql_specifics->non_std_sql_delimiter() : ";", statement_ranges);
if (statement_ranges.size() > 1) {
query_ps_stats = false;
query_ps_statement_events_error = "Query stats can only be fetched when a single statement is executed.";
}
if (!max_query_size_to_log || max_query_size_to_log >= (int)sql->size()) {
logging_queries = true;
} else {
std::list<std::string> warning;
warning.push_back(base::strfmt("Skipping history entries for %li statements, total %li bytes",
(long)statement_ranges.size(), (long)sql->size()));
_history->add_entry(warning);
logging_queries = false;
}
// Intentionally allow any value. For values <= 0 show no result set at all.
ssize_t max_resultset_count = bec::GRTManager::get()->get_app_option_int("DbSqlEditor::MaxResultsets", 50);
ssize_t total_result_count = (editor != nullptr) ? editor->resultset_count() : 0; // Consider pinned result sets.
bool results_left = false;
for (auto &statement_range : statement_ranges) {
logDebug3("Executing statement range: %lu, %lu...\n", statement_range.first, statement_range.second);
statement = sql->substr(statement_range.first, statement_range.second);
std::list<std::string> sub_statements;
sql_facade->splitSqlScript(statement, sub_statements);
std::size_t multiple_statement_count = sub_statements.size();
bool is_multiple_statement = (1 < multiple_statement_count);
{
statement = strip_text(statement, false, true);
if (statement.empty())
continue;
Sql_syntax_check::Statement_type statement_type = sql_syntax_check->determine_statement_type(statement);
logDebug3("Determined statement type: %u\n", statement_type);
if (Sql_syntax_check::sql_empty == statement_type)
continue;
std::string schema_name;
std::string table_name;
if (logging_queries) {
std::list<std::string> statements;
statements.push_back(statement);
_history->add_entry(statements);
}
Recordset_cdbc_storage::Ref data_storage;
// for select queries add limit clause if specified by global option
if (!is_multiple_statement && (Sql_syntax_check::sql_select == statement_type)) {
data_storage = Recordset_cdbc_storage::create();
data_storage->set_gather_field_info(true);
data_storage->rdbms(rdbms());
data_storage->setUserConnectionGetter(
std::bind(&SqlEditorForm::getUserConnection, this, std::placeholders::_1, std::placeholders::_2));
data_storage->setAuxConnectionGetter(
std::bind(&SqlEditorForm::getAuxConnection, this, std::placeholders::_1, std::placeholders::_2));
SqlFacade::String_tuple_list column_names;
if (!table_name.empty() ||
sql_facade->parseSelectStatementForEdit(statement, schema_name, table_name, column_names)) {
data_storage->schema_name(schema_name.empty() ? _usr_dbc_conn->active_schema : schema_name);
data_storage->table_name(table_name);
logDebug3("Result will be editable\n");
} else {
data_storage->readonly_reason(
"Statement must be a SELECT for columns of a single table with a primary key for its results to be "
"editable.");
logDebug3("Result will not be editable\n");
}
data_storage->sql_query(statement);
{
bool do_limit = !dont_add_limit_clause && limit_rows > 0;
data_storage->limit_rows(do_limit);
if (limit_rows > 0)
data_storage->limit_rows_count(limit_rows);
}
statement = data_storage->decorated_sql_query();
}
{
RowId log_message_index = add_log_message(DbSqlEditorLog::BusyMsg, _("Running..."), statement,
((Sql_syntax_check::sql_select == statement_type) ? "? / ?" : "?"));
bool statement_failed = false;
long long updated_rows_count = -1;
Timer statement_exec_timer(false);
Timer statement_fetch_timer(false);
std::shared_ptr<sql::Statement> dbc_statement(_usr_dbc_conn->ref->createStatement());
bool is_result_set_first = false;
if (_usr_dbc_conn->is_stop_query_requested)
throw std::runtime_error(
_("Query execution has been stopped, the connection to the DB server was not restarted, any open "
"transaction remains open"));
try {
{
base::ScopeExitTrigger schedule_statement_exec_timer_stop(std::bind(&Timer::stop, &statement_exec_timer));
statement_exec_timer.run();
is_result_set_first = dbc_statement->execute(statement);
}
logDebug3("Query executed successfully\n");
updated_rows_count = dbc_statement->getUpdateCount();
// XXX: coalesce all the special queries here and act on them *after* all queries have run.
// Especially the drop command is redirected twice to idle tasks, kicking so in totally asynchronously
// and killing any intermittent USE commands.
// Updating the UI during a run of many commands is not useful either.
if (Sql_syntax_check::sql_use == statement_type)
cache_active_schema_name();
if (Sql_syntax_check::sql_set == statement_type && statement.find("@sql_mode") != std::string::npos)
ran_set_sql_mode = true;
if (Sql_syntax_check::sql_drop == statement_type)
update_live_schema_tree(statement);
} catch (sql::SQLException &e) {
std::string err_msg;
// safe mode
switch (e.getErrorCode()) {
case 1046: // not default DB selected
err_msg = strfmt(_("Error Code: %i. %s\nSelect the default DB to be used by double-clicking its name "
"in the SCHEMAS list in the sidebar."),
e.getErrorCode(), e.what());
break;
case 1175: // safe mode
err_msg = strfmt(_("Error Code: %i. %s\nTo disable safe mode, toggle the option in Preferences -> SQL "
"Editor and reconnect."),
e.getErrorCode(), e.what());
break;
default:
err_msg = strfmt(_("Error Code: %i. %s"), e.getErrorCode(), e.what());
break;
}
set_log_message(log_message_index, DbSqlEditorLog::ErrorMsg, err_msg, statement,
statement_exec_timer.duration_formatted());
statement_failed = true;
} catch (std::exception &e) {
std::string err_msg = strfmt(_("Error: %s"), e.what());
set_log_message(log_message_index, DbSqlEditorLog::ErrorMsg, err_msg, statement,
statement_exec_timer.duration_formatted());
statement_failed = true;
}
if (statement_failed) {
if (_continueOnError)
continue; // goto next statement
else
goto stop_processing_sql_script;
}
sql::mysql::MySQL_Connection *mysql_connection =
dynamic_cast<sql::mysql::MySQL_Connection *>(dbc_statement->getConnection());
sql::SQLString last_statement_info;
if (mysql_connection != nullptr)
last_statement_info = mysql_connection->getLastStatementInfo();
if (updated_rows_count >= 0) {
std::string message = strfmt(_("%lli row(s) affected"), updated_rows_count);
bool has_warning = false;
if (flags & ShowWarnings) {
std::string warnings_message;
const sql::SQLWarning *warnings = dbc_statement->getWarnings();
if (warnings) {
int count = 0;
const sql::SQLWarning *w = warnings;
while (w) {
warnings_message.append(strfmt("\n%i %s", w->getErrorCode(), w->getMessage().c_str()));
count++;
w = w->getNextWarning();
}
message.append(strfmt(_(", %i warning(s):"), count));
has_warning = true;
}
if (!warnings_message.empty())
message.append(warnings_message);
}
if (!last_statement_info->empty())
message.append("\n").append(last_statement_info);
set_log_message(log_message_index, has_warning ? DbSqlEditorLog::WarningMsg : DbSqlEditorLog::OKMsg,
message, statement, statement_exec_timer.duration_formatted());
}
logDebug2("Processing result sets\n");
int resultset_count = 0;
bool more_results = is_result_set_first;
bool reuse_log_msg = false;
if ((updated_rows_count < 0) || is_multiple_statement) {
for (std::size_t processed_substatements_count = 0;
processed_substatements_count < multiple_statement_count; ++processed_substatements_count) {
do {
if (more_results) {
if (total_result_count == max_resultset_count) {
int result = mforms::Utilities::show_warning(
_("Maximum result count reached"),
"No further result tabs will be displayed as the maximm number has been reached. \nYou may stop "
"the operation, leaving the connection out of sync. You'll have to got o 'Query->Reconnect to "
"server' menu item to reset the state.\n\n Do you want to cancel the operation?",
"Yes", "No");
if (result == mforms::ResultOk) {
add_log_message(DbSqlEditorLog::ErrorMsg,
"No more results could be displayed. Operation canceled by user.", statement,
"");
dbc_statement->cancel();
dbc_statement->close();
return grt::StringRef("");
}
add_log_message(
DbSqlEditorLog::WarningMsg,
"No more results will be displayed because the maximum number of result sets was reached.",
statement, "");
}
if (!reuse_log_msg && ((updated_rows_count >= 0) || (resultset_count)))
log_message_index = add_log_message(DbSqlEditorLog::BusyMsg, _("Fetching..."), statement, "- / ?");
else
set_log_message(log_message_index, DbSqlEditorLog::BusyMsg, _("Fetching..."), statement,
statement_exec_timer.duration_formatted() + " / ?");
reuse_log_msg = false;
std::shared_ptr<sql::ResultSet> dbc_resultset;
{
base::ScopeExitTrigger schedule_statement_fetch_timer_stop(
std::bind(&Timer::stop, &statement_fetch_timer));
statement_fetch_timer.run();
// need a separate exception catcher here, because sometimes a query error
// will only throw an exception after fetching starts, which causes the busy spinner
// to be active forever, since the exception is logged in a new log_id/row
// XXX this could also be caused by a bug in Connector/C++
try {
dbc_resultset.reset(dbc_statement->getResultSet());
} catch (sql::SQLException &e) {
std::string err_msg;
// safe mode
switch (e.getErrorCode()) {
case 1046: // not default DB selected
err_msg = strfmt(_("Error Code: %i. %s\nSelect the default DB to be used by double-clicking "
"its name in the SCHEMAS list in the sidebar."),
e.getErrorCode(), e.what());
break;
case 1175: // safe mode
err_msg = strfmt(_("Error Code: %i. %s\nTo disable safe mode, toggle the option in "
"Preferences -> SQL Editor and reconnect."),
e.getErrorCode(), e.what());
break;
default:
err_msg = strfmt(_("Error Code: %i. %s"), e.getErrorCode(), e.what());
break;
}
set_log_message(log_message_index, DbSqlEditorLog::ErrorMsg, err_msg, statement,
statement_exec_timer.duration_formatted());
if (_continueOnError)
continue; // goto next statement
else
goto stop_processing_sql_script;
}
}
std::string exec_and_fetch_durations =
(((updated_rows_count >= 0) || (resultset_count)) ? std::string("-")
: statement_exec_timer.duration_formatted()) +
" / " + statement_fetch_timer.duration_formatted();
if (total_result_count >= max_resultset_count)
set_log_message(log_message_index, DbSqlEditorLog::OKMsg, "Row count could not be verified",
statement, exec_and_fetch_durations);
else if (dbc_resultset) {
if (!data_storage) {
data_storage = Recordset_cdbc_storage::create();
data_storage->set_gather_field_info(true);
data_storage->rdbms(rdbms());
data_storage->setUserConnectionGetter(std::bind(&SqlEditorForm::getUserConnection, this,
std::placeholders::_1, std::placeholders::_2));
data_storage->setAuxConnectionGetter(std::bind(&SqlEditorForm::getAuxConnection, this,
std::placeholders::_1, std::placeholders::_2));
if (table_name.empty())
data_storage->sql_query(statement);
data_storage->schema_name(schema_name);
data_storage->table_name(table_name);
}
data_storage->dbc_statement(dbc_statement);
data_storage->dbc_resultset(dbc_resultset);
data_storage->reloadable(!is_multiple_statement &&
(Sql_syntax_check::sql_select == statement_type));
logDebug3("Creation and setup of a new result set...\n");
Recordset::Ref rs = Recordset::create(exec_sql_task);
rs->is_field_value_truncation_enabled(true);
rs->setPreserveRowFilter(
bec::GRTManager::get()->get_app_option_int("SqlEditor:PreserveRowFilter") == 1);
rs->apply_changes_cb =
std::bind(&SqlEditorForm::apply_changes_to_recordset, this, Recordset::Ptr(rs));
rs->generator_query(statement);
{
if (query_ps_stats) {
query_ps_statistics(_usr_dbc_conn->id, ps_stats);
ps_stages = query_ps_stages(ps_stats["EVENT_ID"]);
ps_waits = query_ps_waits(ps_stats["EVENT_ID"]);
query_ps_stats = false;
}
RecordsetData *rdata = new RecordsetData();
rdata->duration = statement_exec_timer.duration();
rdata->ps_stat_error = query_ps_statement_events_error;
rdata->ps_stat_info = ps_stats;
rdata->ps_stage_info = ps_stages;
rdata->ps_wait_info = ps_waits;
rs->set_client_data(rdata);
}
rs->data_storage(data_storage);
rs->reset(true);
if (data_storage->valid()) // query statement
{
if (result_list)
result_list->push_back(rs);
if (editor)
editor->add_panel_for_recordset_from_main(rs);
std::string statement_res_msg = std::to_string(rs->row_count()) + _(" row(s) returned");
if (!last_statement_info->empty())
statement_res_msg.append("\n").append(last_statement_info);
set_log_message(log_message_index, DbSqlEditorLog::OKMsg, statement_res_msg, statement,
exec_and_fetch_durations);
}
++resultset_count;
} else {
reuse_log_msg = true;
}
++total_result_count;
data_storage.reset();
}
} while ((more_results = dbc_statement->getMoreResults()));
}
}
if ((updated_rows_count < 0) && !(resultset_count))
set_log_message(log_message_index, DbSqlEditorLog::OKMsg, _("OK"), statement,
statement_exec_timer.duration_formatted());
}
}
} // statement range loop
if (results_left) {
exec_sql_task->execute_in_main_thread(
std::bind(&mforms::Utilities::show_warning, _("Result set limit reached"),
_("There were more results than "
"result tabs could be opened, because the set maximum limit was reached. You can change this "
"limit in the preferences."),
_("OK"), "", ""),
true, false);
}
bec::GRTManager::get()->replace_status_text(_("Query Completed"));
interrupted = false;
stop_processing_sql_script:
if (interrupted)
bec::GRTManager::get()->replace_status_text(_("Query interrupted"));
// try to minimize the times this is called, since this will change the state of the connection
// after a user query is ran (eg, it will reset all warnings)
if (ran_set_sql_mode)
cache_sql_mode();
}
CATCH_ANY_EXCEPTION_AND_DISPATCH(statement)
if (dbc_driver)
dbc_driver->threadEnd();
logDebug("SQL execution finished\n");
update_menu_and_toolbar();
_usr_dbc_conn->is_stop_query_requested = false;
return grt::StringRef("");
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::exec_management_sql(const std::string &sql, bool log) {
sql::Dbc_connection_handler::Ref conn;
base::RecMutexLock lock(ensure_valid_aux_connection(conn));
if (conn) {
RowId rid = log ? add_log_message(DbSqlEditorLog::BusyMsg, _("Executing "), sql, "- / ?") : 0;
const std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
Timer statement_exec_timer(false);
try {
stmt->execute(sql);
} catch (sql::SQLException &e) {
if (log)
set_log_message(rid, DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()),
sql, "");
throw;
}
CATCH_EXCEPTION_AND_DISPATCH(sql);
if (log)
set_log_message(rid, DbSqlEditorLog::OKMsg, _("OK"), sql, statement_exec_timer.duration_formatted());
handle_command_side_effects(sql);
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::exec_main_sql(const std::string &sql, bool log) {
base::RecMutexLock lock(ensure_valid_usr_connection());
if (_usr_dbc_conn) {
RowId rid = log ? add_log_message(DbSqlEditorLog::BusyMsg, _("Executing "), sql, "- / ?") : 0;
const std::unique_ptr<sql::Statement> stmt(_usr_dbc_conn->ref->createStatement());
Timer statement_exec_timer(false);
try {
stmt->execute(sql);
} catch (sql::SQLException &e) {
if (log)
set_log_message(rid, DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()),
sql, "");
throw;
}
CATCH_EXCEPTION_AND_DISPATCH(sql);
if (log)
set_log_message(rid, DbSqlEditorLog::OKMsg, _("OK"), sql, statement_exec_timer.duration_formatted());
handle_command_side_effects(sql);
}
}
//----------------------------------------------------------------------------------------------------------------------
static wb::LiveSchemaTree::ObjectType str_to_object_type(const std::string &object_type) {
if (object_type == "db.Table")
return LiveSchemaTree::Table;
else if (object_type == "db.View")
return LiveSchemaTree::View;
else if (object_type == "db.StoredProcedure")
return LiveSchemaTree::Procedure;
else if (object_type == "db.Function")
return LiveSchemaTree::Function;
else if (object_type == "db.Index")
return LiveSchemaTree::Index;
else if (object_type == "db.Trigger")
return LiveSchemaTree::Trigger;
else if (object_type == "db.Schema")
return LiveSchemaTree::Schema;
return LiveSchemaTree::NoneType;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::handle_command_side_effects(const std::string &sql) {
SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms(rdbms());
std::string object_type;
std::string schema_name = active_schema();
std::vector<std::pair<std::string, std::string>> object_names;
// special hack, check for some special commands and update UI accordingly
if (sql_facade->parseDropStatement(sql, object_type, object_names) && !object_names.empty()) {
wb::LiveSchemaTree::ObjectType obj = str_to_object_type(object_type);
if (obj != wb::LiveSchemaTree::NoneType) {
std::vector<std::pair<std::string, std::string>>::reverse_iterator rit;
if (obj == wb::LiveSchemaTree::Schema) {
for (rit = object_names.rbegin(); rit != object_names.rend(); ++rit)
_live_tree->refresh_live_object_in_overview(obj, (*rit).first, (*rit).first, "");
if (!object_names.empty())
schema_name = object_names.back().first;
if ((schema_name.size() > 0) && (active_schema() == schema_name) && connection_descriptor().is_valid()) {
std::string default_schema = connection_descriptor()->parameterValues().get_string("schema", "");
if (schema_name == default_schema)
default_schema = "";
bec::GRTManager::get()->run_once_when_idle(this,
std::bind(&set_active_schema, shared_from_this(), default_schema));
}
} else {
for (rit = object_names.rbegin(); rit != object_names.rend(); ++rit)
_live_tree->refresh_live_object_in_overview(obj, (*rit).first.empty() ? schema_name : (*rit).first,
(*rit).second, "");
}
}
}
}
//----------------------------------------------------------------------------------------------------------------------
db_query_ResultsetRef SqlEditorForm::exec_management_query(const std::string &sql, bool log) {
sql::Dbc_connection_handler::Ref conn;
base::RecMutexLock lock(ensure_valid_aux_connection(conn));
if (conn) {
RowId rid = log ? add_log_message(DbSqlEditorLog::BusyMsg, _("Executing "), sql, "- / ?") : 0;
const std::unique_ptr<sql::Statement> stmt(conn->ref->createStatement());
Timer statement_exec_timer(false);
try {
std::shared_ptr<sql::ResultSet> results(stmt->executeQuery(sql));
if (log)
set_log_message(rid, DbSqlEditorLog::OKMsg, _("OK"), sql, statement_exec_timer.duration_formatted());
return grtwrap_recordset(grtobj(), results);
} catch (sql::SQLException &e) {
if (log)
set_log_message(rid, DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()),
sql, "");
throw;
}
}
return db_query_ResultsetRef();
}
//----------------------------------------------------------------------------------------------------------------------
db_query_ResultsetRef SqlEditorForm::exec_main_query(const std::string &sql, bool log) {
base::RecMutexLock lock(ensure_valid_usr_connection());
if (_usr_dbc_conn) {
RowId rid = log ? add_log_message(DbSqlEditorLog::BusyMsg, _("Executing "), sql, "- / ?") : 0;
const std::unique_ptr<sql::Statement> stmt(_usr_dbc_conn->ref->createStatement());
Timer statement_exec_timer(false);
try {
std::shared_ptr<sql::ResultSet> results(stmt->executeQuery(sql));
if (log)
set_log_message(rid, DbSqlEditorLog::OKMsg, _("OK"), sql, statement_exec_timer.duration_formatted());
return grtwrap_recordset(grtobj(), results);
} catch (sql::SQLException &e) {
if (log)
set_log_message(rid, DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()),
sql, "");
throw;
}
}
return db_query_ResultsetRef();
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::is_running_query() {
return _is_running_query;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::continue_on_error(bool val) {
if (_continueOnError == val)
return;
_continueOnError = val;
bec::GRTManager::get()->set_app_option("DbSqlEditor:ContinueOnError", grt::IntegerRef((int)_continueOnError));
if (_menu)
_menu->set_item_checked("query.continueOnError", continue_on_error());
set_editor_tool_items_checked("query.continueOnError", continue_on_error());
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::send_message_keep_alive() {
try {
logDebug3("KeepAliveInterval tick\n");
// ping server and reset connection timeout counter
// this also checks the connection state and restores it if possible
ensure_valid_aux_connection();
ensure_valid_usr_connection();
} catch (const std::exception &) {
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::apply_changes_to_recordset(Recordset::Ptr rs_ptr) {
RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, rs_ptr, rs)
try {
bool auto_commit = false;
// we need transaction to enforce atomicity of change set
// so if autocommit is currently enabled disable it temporarily
{
RecMutexLock usr_dbc_conn_mutex = ensure_valid_usr_connection();
auto_commit = _usr_dbc_conn->ref->getAutoCommit();
}
ScopeExitTrigger autocommit_mode_keeper;
int res = -2;
if (!auto_commit) {
res = mforms::Utilities::show_warning(
_("Apply Changes to Recordset"),
_("Autocommit is currently disabled and a transaction might be open.\n"
"Recordset changes will be applied within that transaction and will be left uncommited until you explicitly "
"commit it manually.\n"
"If you want it to be executed separately, click Cancel and commit the transaction first."),
_("Apply"), _("Cancel"));
} else {
autocommit_mode_keeper.slot = std::bind(&sql::Connection::setAutoCommit, _usr_dbc_conn->ref.get(), auto_commit);
RecMutexLock usr_dbc_conn_mutex = ensure_valid_usr_connection();
_usr_dbc_conn->ref->setAutoCommit(false);
}
if (res != mforms::ResultCancel) // only if not canceled
{
on_sql_script_run_error.disconnect_all_slots();
on_sql_script_run_progress.disconnect_all_slots();
on_sql_script_run_statistics.disconnect_all_slots();
Recordset_data_storage::Ref data_storage_ref = rs->data_storage();
Recordset_sql_storage *sql_storage = dynamic_cast<Recordset_sql_storage *>(data_storage_ref.get());
scoped_connection c1(
on_sql_script_run_error.connect(std::bind(&SqlEditorForm::add_log_message, this, DbSqlEditorLog::ErrorMsg,
std::placeholders::_2, std::placeholders::_3, "")));
bool skip_commit;
if (auto_commit)
skip_commit = false;
else
skip_commit = true; // if we're in an open tx, then do not commit
bool is_data_changes_commit_wizard_enabled =
(0 != bec::GRTManager::get()->get_app_option_int("DbSqlEditor:IsDataChangesCommitWizardEnabled", 1));
if (is_data_changes_commit_wizard_enabled) {
run_data_changes_commit_wizard(rs_ptr, skip_commit);
} else {
sql_storage->is_sql_script_substitute_enabled(false);
scoped_connection on_sql_script_run_error_conn(
sql_storage->on_sql_script_run_error.connect(on_sql_script_run_error));
rs->do_apply_changes(rs_ptr, Recordset_data_storage::Ptr(data_storage_ref), skip_commit);
}
// Since many messages could have been added it is possible the
// the action log has not been refresh, this triggers a refresh
refresh_log_messages(true);
}
}
CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Apply changes to recordset"))
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::run_data_changes_commit_wizard(Recordset::Ptr rs_ptr, bool skip_commit) {
RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, rs_ptr, rs, false)
// set underlying recordset data storage to use sql substitute (potentially modified by user)
// instead of generating sql based on swap db contents
Recordset_data_storage::Ref data_storage_ref = rs->data_storage();
Recordset_sql_storage *sql_storage = dynamic_cast<Recordset_sql_storage *>(data_storage_ref.get());
if (!sql_storage)
return false;
sql_storage->init_sql_script_substitute(rs_ptr, true);
sql_storage->is_sql_script_substitute_enabled(true);
const Sql_script &sql_script = sql_storage->sql_script_substitute();
;
std::string sql_script_text = Recordset_sql_storage::statements_as_sql_script(sql_script.statements);
// No need for online DDL settings or callback as we are dealing with data here, not metadata.
SqlScriptRunWizard wizard(rdbms_version(), "", "");
scoped_connection c1(
on_sql_script_run_error.connect(std::bind(&SqlScriptApplyPage::on_error, wizard.apply_page, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3)));
scoped_connection c2(on_sql_script_run_progress.connect(
std::bind(&SqlScriptApplyPage::on_exec_progress, wizard.apply_page, std::placeholders::_1)));
scoped_connection c3(on_sql_script_run_statistics.connect(
std::bind(&SqlScriptApplyPage::on_exec_stat, wizard.apply_page, std::placeholders::_1, std::placeholders::_2)));
wizard.values().gset("sql_script", sql_script_text);
wizard.apply_page->apply_sql_script =
std::bind(&SqlEditorForm::apply_data_changes_commit, this, std::placeholders::_1, rs_ptr, skip_commit);
wizard.run_modal();
return !wizard.has_errors();
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::apply_object_alter_script(const std::string &alter_script, bec::DBObjectEditorBE *obj_editor,
RowId log_id) {
set_log_message(
log_id, DbSqlEditorLog::BusyMsg, "",
obj_editor ? strfmt(_("Applying changes to %s..."), obj_editor->get_name().c_str()) : _("Applying changes..."), "");
SqlFacade::Ref sql_splitter = SqlFacade::instance_for_rdbms(rdbms());
std::list<std::string> statements;
sql_splitter->splitSqlScript(alter_script, statements);
int max_query_size_to_log = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:MaxQuerySizeToHistory", 0);
sql::SqlBatchExec sql_batch_exec;
sql_batch_exec.stop_on_error(true);
sql_batch_exec.error_cb(std::ref(on_sql_script_run_error));
sql_batch_exec.batch_exec_progress_cb(std::ref(on_sql_script_run_progress));
sql_batch_exec.batch_exec_stat_cb(std::ref(on_sql_script_run_statistics));
long sql_batch_exec_err_count = 0;
{
try {
RecMutexLock usr_dbc_conn_mutex(ensure_valid_usr_connection(true));
std::unique_ptr<sql::Statement> stmt(_usr_dbc_conn->ref->createStatement());
sql_batch_exec_err_count = sql_batch_exec(stmt.get(), statements);
} catch (sql::SQLException &e) {
set_log_message(log_id, DbSqlEditorLog::ErrorMsg, strfmt(SQL_EXCEPTION_MSG_FORMAT, e.getErrorCode(), e.what()),
strfmt(_("Apply ALTER script for %s"), obj_editor->get_name().c_str()), "");
throw; // re-throw exception so that the wizard will see that something went wrong
} catch (base::mutex_busy_error &) {
set_log_message(log_id, DbSqlEditorLog::ErrorMsg,
strfmt(EXCEPTION_MSG_FORMAT, "Your connection to MySQL is currently busy. Please retry later."),
strfmt(_("Apply ALTER script for %s"), obj_editor->get_name().c_str()), "");
throw std::runtime_error("Connection to MySQL currently busy.");
} catch (std::exception &e) {
set_log_message(log_id, DbSqlEditorLog::ErrorMsg, strfmt(EXCEPTION_MSG_FORMAT, e.what()),
strfmt(_("Apply ALTER script for %s"), obj_editor->get_name().c_str()), "");
throw;
}
}
if (!max_query_size_to_log || max_query_size_to_log >= (int)alter_script.size())
_history->add_entry(sql_batch_exec.sql_log());
// refresh object's state only on success, to not lose changes made by user
if (obj_editor && (0 == sql_batch_exec_err_count)) {
db_DatabaseObjectRef db_object = obj_editor->get_dbobject();
set_log_message(log_id, DbSqlEditorLog::OKMsg, strfmt(_("Changes applied to %s"), obj_editor->get_name().c_str()),
"", "");
// refresh state of created/altered object in physical overview
{
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;
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;
}
//_live_tree->refresh_live_object_in_overview(db_object_type, schema_name, db_object->oldName(),
//db_object->name());
// Run refresh on main thread, but only if there's not another refresh pending already.
if (!_overviewRefreshPending.connected()) {
_overviewRefreshPending = bec::GRTManager::get()->run_once_when_idle(
this, std::bind(&SqlEditorTreeController::refresh_live_object_in_overview, _live_tree, db_object_type,
schema_name, db_object->oldName(), db_object->name()));
}
}
//_live_tree->refresh_live_object_in_editor(obj_editor, false);
if (!_editorRefreshPending.connected()) {
_editorRefreshPending = bec::GRTManager::get()->run_once_when_idle(
this, std::bind(&SqlEditorTreeController::refresh_live_object_in_editor, _live_tree, obj_editor, false));
}
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::apply_data_changes_commit(const std::string &sql_script_text, Recordset::Ptr rs_ptr,
bool skip_commit) {
RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, rs_ptr, rs);
// this lock is supposed to be acquired lower in call-stack by SqlEditorForm::apply_changes_to_recordset
// MutexLock usr_conn_mutex= ensure_valid_usr_connection();
Recordset_data_storage::Ref data_storage_ref = rs->data_storage();
Recordset_sql_storage *sql_storage = dynamic_cast<Recordset_sql_storage *>(data_storage_ref.get());
if (!sql_storage)
return;
int max_query_size_to_log = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:MaxQuerySizeToHistory", 0);
Sql_script sql_script = sql_storage->sql_script_substitute();
sql_script.statements.clear();
SqlFacade::Ref sql_splitter = SqlFacade::instance_for_rdbms(rdbms());
sql_splitter->splitSqlScript(sql_script_text, sql_script.statements);
scoped_connection on_sql_script_run_error_conn(sql_storage->on_sql_script_run_error.connect(on_sql_script_run_error));
scoped_connection on_sql_script_run_progress_conn(
sql_storage->on_sql_script_run_progress.connect(on_sql_script_run_progress));
scoped_connection on_sql_script_run_statistics_conn(
sql_storage->on_sql_script_run_statistics.connect(on_sql_script_run_statistics));
sql_storage->sql_script_substitute(sql_script);
rs->setPreserveRowFilter(bec::GRTManager::get()->get_app_option_int("SqlEditor:PreserveRowFilter") == 1);
rs->do_apply_changes(rs_ptr, Recordset_data_storage::Ptr(data_storage_ref), skip_commit);
if (!max_query_size_to_log || max_query_size_to_log >= (int)sql_script_text.size())
_history->add_entry(sql_script.statements);
}
//----------------------------------------------------------------------------------------------------------------------
std::string SqlEditorForm::active_schema() const {
return (_usr_dbc_conn) ? _usr_dbc_conn->active_schema : std::string();
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::schemaListRefreshed(std::vector<std::string> const &schemas) {
std::unique_lock<std::mutex> lock(_pimplMutex->_symbolsMutex);
_databaseSymbols.clear(); // Doesn't clear the dependencies.
for (auto schema : schemas) {
_databaseSymbols.addNewSymbol<SchemaSymbol>(nullptr, schema);
}
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Reads all relevant built-in symbols like engines and collations in our static server symbols list.
*/
void SqlEditorForm::readStaticServerSymbols() {
std::unique_lock<std::mutex> lock(_pimplMutex->_symbolsMutex); // Probably not needed, as this runs during startup.
if (_usr_dbc_conn->ref.get() != nullptr) {
const std::unique_ptr<sql::Statement> statement(_usr_dbc_conn->ref.get()->createStatement());
{
const std::unique_ptr<sql::ResultSet> rs(statement->executeQuery("show engines"));
while (rs->next()) {
std::string name = rs->getString(1);
std::string support = rs->getString(2);
if (support != "NO") { // Can be YES, NO or DEFAULT.
_staticServerSymbols.addNewSymbol<EngineSymbol>(nullptr, name);
}
}
}
{
const std::unique_ptr<sql::ResultSet> rs(statement->executeQuery("show charset"));
while (rs->next()) {
_staticServerSymbols.addNewSymbol<CharsetSymbol>(nullptr, rs->getString(1));
}
}
{
const std::unique_ptr<sql::ResultSet> rs(statement->executeQuery("show collation"));
while (rs->next()) {
_staticServerSymbols.addNewSymbol<CollationSymbol>(nullptr, rs->getString(1));
}
}
{
const std::unique_ptr<sql::ResultSet> rs(statement->executeQuery("show variables"));
while (rs->next()) {
_staticServerSymbols.addNewSymbol<SystemVariableSymbol>(nullptr, "@@" + rs->getString(1));
}
}
}
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Notification from the tree controller that (some) schema meta data has been refreshed. We use this
* info to update the database symbol table.
*/
void SqlEditorForm::schema_meta_data_refreshed(const std::string &schema_name, base::StringListPtr tables,
base::StringListPtr views, base::StringListPtr procedures,
base::StringListPtr functions) {
std::unique_lock<std::mutex> lock(_pimplMutex->_symbolsMutex);
std::unique_ptr<sql::Statement> statement;
RecMutexLock usr_dbc_conn_mutex(ensure_valid_usr_connection());
if (_usr_dbc_conn->ref.get() != nullptr)
statement.reset(_usr_dbc_conn->ref->createStatement());
auto schemaSymbols = _databaseSymbols.getSymbolsOfType<SchemaSymbol>();
bool hasPerformanceSchema = std::find_if(schemaSymbols.begin(), schemaSymbols.end(), [](auto symbol) -> bool {
return symbol->name == "performance_schema";
}) != schemaSymbols.end();
for (SchemaSymbol *schemaSymbol : schemaSymbols) {
if (schemaSymbol->name == schema_name) {
schemaSymbol->clear();
for (auto table : *tables) {
TableSymbol *tableSymbol = _databaseSymbols.addNewSymbol<TableSymbol>(schemaSymbol, table);
// Fetch column info for each table.
if (statement != nullptr) {
std::unique_ptr<sql::ResultSet> rs(statement->executeQuery(
std::string(base::sqlstring("SHOW FULL COLUMNS FROM !.!", 0) << schema_name << table)));
while (rs->next()) {
_databaseSymbols.addNewSymbol<ColumnSymbol>(tableSymbol, rs->getString(1), nullptr);
}
}
}
for (auto view : *views) {
ViewSymbol *viewSymbol = _databaseSymbols.addNewSymbol<ViewSymbol>(schemaSymbol, view);
// Same for each view.
if (statement != nullptr) {
std::unique_ptr<sql::ResultSet> rs(statement->executeQuery(
std::string(base::sqlstring("SHOW FULL COLUMNS FROM !.!", 0) << schema_name << view)));
while (rs->next()) {
_databaseSymbols.addNewSymbol<ColumnSymbol>(viewSymbol, rs->getString(1), nullptr);
}
}
}
for (auto procedure : *procedures) {
_databaseSymbols.addNewSymbol<StoredRoutineSymbol>(schemaSymbol, procedure, nullptr);
}
for (auto function : *functions) {
_databaseSymbols.addNewSymbol<StoredRoutineSymbol>(schemaSymbol, function, nullptr);
}
if (statement != nullptr) {
auto metaInfo = _usr_dbc_conn->ref->getMetaData();
if (hasPerformanceSchema && (metaInfo->getDatabaseMajorVersion() > 7
|| (metaInfo->getDatabaseMajorVersion() == 5 && metaInfo->getDatabaseMinorVersion() > 6))) {
std::unique_ptr<sql::ResultSet> rs(
statement->executeQuery("SELECT VARIABLE_NAME FROM performance_schema.user_variables_by_thread")
);
while (rs->next()) {
_databaseSymbols.addNewSymbol<UserVariableSymbol>(nullptr, "@" + rs->getString(1), nullptr);
}
}
}
return;
}
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::cache_active_schema_name() {
std::string schema = _usr_dbc_conn->ref->getSchema();
_usr_dbc_conn->active_schema = schema;
_aux_dbc_conn->active_schema = schema;
exec_sql_task->execute_in_main_thread(std::bind(&SqlEditorForm::update_editor_title_schema, this, schema), false,
true);
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::active_schema(const std::string &value) {
try {
if (value == active_schema())
return;
{
RecMutexLock aux_dbc_conn_mutex(ensure_valid_aux_connection());
if (!value.empty())
_aux_dbc_conn->ref->setSchema(value);
_aux_dbc_conn->active_schema = value;
}
{
RecMutexLock usr_dbc_conn_mutex(ensure_valid_usr_connection());
if (!value.empty())
_usr_dbc_conn->ref->setSchema(value);
_usr_dbc_conn->active_schema = value;
}
if (_tabdock) {
// set current schema for the editors to notify the autocompleter
for (int c = _tabdock->view_count(), i = 0; i < c; i++) {
SqlEditorPanel *panel = sql_editor_panel(i);
if (panel)
panel->editor_be()->set_current_schema(value);
}
}
_live_tree->on_active_schema_change(value);
// remember active schema
_connection->parameterValues().gset("DbSqlEditor:LastDefaultSchema", value);
update_editor_title_schema(value);
if (value.empty())
bec::GRTManager::get()->replace_status_text(_("Active schema was cleared"));
else
bec::GRTManager::get()->replace_status_text(strfmt(_("Active schema changed to %s"), value.c_str()));
grt::GRT::get()->call_module_function("Workbench", "saveConnections", grt::BaseListRef());
}
CATCH_ANY_EXCEPTION_AND_DISPATCH(_("Set active schema"))
}
//----------------------------------------------------------------------------------------------------------------------
db_mgmt_RdbmsRef SqlEditorForm::rdbms() {
if (_connection.is_valid()) {
if (!_connection->driver().is_valid())
throw std::runtime_error("Connection has invalid driver, check connection parameters.");
return db_mgmt_RdbmsRef::cast_from(_connection->driver()->owner());
} else
return db_mgmt_RdbmsRef::cast_from(grt::GRT::get()->get("/wb/rdbmsMgmt/rdbms/0/"));
}
//----------------------------------------------------------------------------------------------------------------------
int SqlEditorForm::count_connection_editors(const std::string &conn_name) {
int count = 0;
std::weak_ptr<SqlEditorForm> editor;
std::list<std::weak_ptr<SqlEditorForm>>::iterator index, end;
end = _wbsql->get_open_editors()->end();
for (index = _wbsql->get_open_editors()->begin(); index != end; index++) {
SqlEditorForm::Ref editor((*index).lock());
if (editor->_connection.is_valid()) {
std::string editor_connection = editor->_connection->name();
if (editor_connection == conn_name)
count++;
}
}
return count;
}
//----------------------------------------------------------------------------------------------------------------------
std::string SqlEditorForm::create_title() {
std::string caption;
std::string editor_connection = get_session_name();
if (_connection.is_valid()) {
if (!editor_connection.empty())
caption += strfmt("%s", editor_connection.c_str());
else {
if (_connection->driver()->name() == "MysqlNativeSocket")
caption += "localhost";
else
caption += strfmt("%s", truncate_text(editor_connection, 21).c_str());
}
// only show schema name if there's more than 1 tab to the same connection, to save space
if (!_usr_dbc_conn->active_schema.empty() && count_connection_editors(editor_connection) > 1)
caption += strfmt(" (%s)", truncate_text(_usr_dbc_conn->active_schema, 20).c_str());
if (_connection_details.find("dbmsProductVersion") != _connection_details.end() &&
!bec::is_supported_mysql_version(_connection_details["dbmsProductVersion"]))
caption += " - Warning - not supported";
} else
caption = editor_connection;
return caption;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::update_title() {
std::string temp_title = create_title();
if (_title != temp_title) {
_title = temp_title;
title_changed();
}
}
//----------------------------------------------------------------------------------------------------------------------
int SqlEditorForm::getTunnelPort() const {
if (_tunnel)
return _tunnel->getConfig().localport;
return -1;
}
//----------------------------------------------------------------------------------------------------------------------
GrtVersionRef SqlEditorForm::rdbms_version() const {
return _version;
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Returns the current server version (or a reasonable default if not connected) in compact form
* as needed for parsing on various occasions (context help, auto completion, error parsing).
*/
int SqlEditorForm::server_version() {
GrtVersionRef version = rdbms_version();
// Create a server version of the form "Mmmrr" as long int for quick comparisons.
if (version.is_valid())
return (int)(version->majorNumber() * 10000 + version->minorNumber() * 100 + version->releaseNumber());
else
return 50503;
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Returns a list of valid charsets for this connection as needed for parsing.
*/
std::set<std::string> SqlEditorForm::valid_charsets() {
if (_charsets.empty()) {
grt::ListRef<db_CharacterSet> list = rdbms()->characterSets();
for (std::size_t i = 0; i < list->count(); i++)
_charsets.insert(base::tolower(*list[i]->name()));
// 3 character sets were added in version 5.5.3. Remove them from the list if the current version
// is lower than that.
if (server_version() < 50503) {
_charsets.erase("utf8mb4");
_charsets.erase("utf16");
_charsets.erase("utf32");
}
}
return _charsets;
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::save_snippet() {
SqlEditorPanel *panel = active_sql_editor_panel();
if (!panel)
return false;
std::string text;
std::size_t start, end;
if (panel->editor_be()->selected_range(start, end))
text = panel->editor_be()->selected_text();
else
text = panel->editor_be()->current_statement();
if (text.empty())
return false;
DbSqlEditorSnippets::get_instance()->add_snippet("", text, true);
bec::GRTManager::get()->replace_status_text("SQL saved to snippets list.");
_side_palette->refresh_snippets();
bec::GRTManager::get()->run_once_when_idle(this, std::bind(&QuerySidePalette::edit_last_snippet, _side_palette));
return true;
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::can_close() {
return can_close_(true);
}
//----------------------------------------------------------------------------------------------------------------------
bool SqlEditorForm::can_close_(bool interactive) {
if (exec_sql_task && exec_sql_task->is_busy()) {
bec::GRTManager::get()->replace_status_text(_("Cannot close SQL IDE while being busy"));
return false;
}
if (!bec::UIForm::can_close())
return false;
_live_tree->prepare_close();
bec::GRTManager::get()->set_app_option("DbSqlEditor:ActiveSidePaletteTab",
grt::IntegerRef(_side_palette->get_active_tab()));
bool check_scratch_editors = true;
bool save_workspace_on_close = false;
// if Save of workspace on close is enabled, we don't need to check whether there are unsaved
// SQL editors but other stuff should be checked.
grt::ValueRef option(bec::GRTManager::get()->get_app_option("workbench:SaveSQLWorkspaceOnClose"));
if (option.is_valid() && *grt::IntegerRef::cast_from(option)) {
save_workspace_on_close = true;
check_scratch_editors = false;
}
bool editor_needs_review = false;
if (interactive) {
ConfirmSaveDialog dialog(
0, "Close SQL Editor",
"The following files/resultsets have unsaved changes.\nDo you want to review these changes before closing?");
for (int i = 0; i < sql_editor_count(); i++) {
SqlEditorPanel *panel = sql_editor_panel(i);
if (!panel)
continue;
bool check_editor = !panel->is_scratch() || check_scratch_editors;
if (panel->filename().empty() && save_workspace_on_close)
check_editor = false;
if (panel->is_dirty() && check_editor) {
editor_needs_review = true;
dialog.add_item("Script Buffers", panel->get_title());
}
std::list<SqlEditorResult *> rset(panel->dirty_result_panels());
for (auto *r : rset) {
dialog.add_item("Resultset", r->caption());
}
}
bool review = false;
if (dialog.change_count() > 1) {
switch (dialog.run()) {
case ConfirmSaveDialog::ReviewChanges:
review = true;
break;
case ConfirmSaveDialog::DiscardChanges:
review = false;
break;
case ConfirmSaveDialog::Cancel:
return false;
}
} else if (dialog.change_count() == 1)
review = true;
// review changes 1 by 1
if (review && editor_needs_review) {
_closing = true;
for (int i = 0; i < sql_editor_count(); i++) {
SqlEditorPanel *panel = sql_editor_panel(i);
if (panel && !panel->can_close()) {
_closing = false;
return false;
}
}
}
} else { // !interactive, return false if there's any unsaved edits in editor or resultsets
for (int i = 0; i < sql_editor_count(); i++) {
SqlEditorPanel *panel = sql_editor_panel(i);
if (panel) {
if (editor_needs_review && panel->is_dirty())
return false;
if (!panel->dirty_result_panels().empty())
return false;
}
}
}
return true;
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::check_external_file_changes() {
for (int i = 0; i < sql_editor_count(); i++) {
SqlEditorPanel *panel = sql_editor_panel(i);
if (panel)
panel->check_external_file_changes();
}
}
//----------------------------------------------------------------------------------------------------------------------
void SqlEditorForm::update_editor_title_schema(const std::string &schema) {
_live_tree->on_active_schema_change(schema);
// Gets the editor label including the schema name only if
// the number of opened editors to the same host is > 1
update_title();
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Called whenever a connection to a server is opened, whether it succeeds or not.
*
* Call this when a connection to the server is opened. If the connection succeeded, pass 0 as
* the error and if it fails, pass the error code.
*
* The error will be used to determine whether the connection failed because the server is possibly
* down (or doesn't exist) or some other reason (like wrong password).
*/
void SqlEditorForm::note_connection_open_outcome(int error) {
ServerState newState;
switch (error) {
case 0:
newState = RunningState; // success = running;
break;
case 2002: // CR_CONNECTION_ERROR
case 2003: // CR_CONN_HOST_ERROR
newState = PossiblyStoppedState;
break;
case 2013: // Lost packet blabla, can happen on failure when using ssh tunnel
newState = PossiblyStoppedState;
break;
default:
// there may be other errors that could indicate server stopped and maybe
// some errors that can't tell anything about the server state
newState = RunningState;
break;
}
if (_last_server_running_state != newState && newState != UnknownState) {
grt::DictRef info(true);
_last_server_running_state = newState;
if (newState == RunningState)
info.gset("state", 1);
else if (newState == OfflineState)
info.gset("state", -1);
else
info.gset("state", 0);
info.set("connection", connection_descriptor());
logDebug2("Notifying server state change of %s to %s\n", connection_descriptor()->hostIdentifier().c_str(),
(newState == RunningState || newState == OfflineState) ? "running" : "not running");
GRTNotificationCenter::get()->send_grt("GRNServerStateChanged", grtobj(), info);
}
}
//----------------------------------------------------------------------------------------------------------------------